Skip to content

Commit

Permalink
lint: fix NPY002 in tests/*
Browse files Browse the repository at this point in the history
To reproduce the errors:
```
ruff check --select "NPY" tests
```

This commit updates the tests code so to make them to use np.random.default_rng with a certain seed, rather than relying on global random seed handling, i.e. using np.random.seed.

To do so, a new fixture, 'rng', has been added so to avoid the tests code to initialize a np.random.Generator instance manually.

This allowed to remove the special case for macOS platforms in TestCalibrate.test_calibrator_calibrate tests. Fix #49.
  • Loading branch information
marcofavorito authored and marcofavoritobi committed Sep 20, 2023
1 parent 4e33aa1 commit e180ee0
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 172 deletions.
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import inspect
from pathlib import Path

import numpy as np
import pytest

CUR_PATH = Path(inspect.getfile(inspect.currentframe())).parent # type: ignore[arg-type]
ROOT_DIR = Path(CUR_PATH, "..").resolve().absolute()
DOCS_DIR = ROOT_DIR / "docs"
Expand All @@ -27,3 +30,9 @@
EXAMPLE_SAVING_FOLDER = ROOT_DIR / "examples" / "saving_folder"

DEFAULT_SUBPROCESS_TIMEOUT = 100.0


@pytest.fixture()
def rng() -> np.random.Generator:
"""Return random number generator."""
return np.random.default_rng(seed=11)
93 changes: 25 additions & 68 deletions tests/test_calibrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""This module contains tests for the Calibrator.calibrate method."""
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

Expand Down Expand Up @@ -45,74 +44,38 @@ class TestCalibrate:

expected_params = np.array(
[
[0.59, 0.36],
[0.63, 0.41],
[0.18, 0.39],
[0.52, 0.01],
[0.56, 0.37],
[0.61, 0.53],
[0.54, 0.32],
[0.74, 0.32],
[0.59, 0.36],
[0.55, 0.46],
[0.53, 0.46],
[0.55, 0.62],
[0.71, 0.35],
[0.32, 0.93],
[0.8, 0.06],
[0.99, 1.0],
[0.06, 0.98],
],
)

expected_losses = [
0.33400294,
0.55274918,
0.55798021,
0.61712034,
0.78963342,
1.31118518,
1.51682355,
1.55503666,
1.68181078,
1.70075834,
1.79905545,
2.07605975,
2.25201126,
2.97360386,
]

darwin_expected_params = np.array(
[
[0.59, 0.36],
[0.63, 0.41],
[0.18, 0.39],
[0.56, 0.37],
[0.63, 0.31],
[0.54, 0.32],
[0.37, 0.59],
[0.74, 0.32],
[0.53, 0.46],
[0.57, 0.39],
[0.6, 0.42],
[0.18, 0.39],
[0.51, 0.33],
[0.04, 0.99],
[0.32, 0.93],
[0.03, 0.36],
[0.8, 0.06],
[0.06, 0.98],
[0.56, 0.24],
],
)

darwin_expected_losses = [
0.33400294,
0.55274918,
0.55798021,
0.61712034,
0.89679611,
1.31118518,
1.3961825,
1.51682355,
1.55503666,
1.65968375,
1.79905545,
1.92174866,
2.07605975,
2.97360386,
expected_losses = [
0.8795007,
0.99224516,
1.15590624,
1.24380484,
1.76330622,
1.88165325,
2.30766018,
2.55676207,
2.86482802,
2.88057794,
2.90585611,
3.77705872,
4.47466328,
5.79615295,
]

def setup(self) -> None:
Expand Down Expand Up @@ -171,14 +134,8 @@ def test_calibrator_calibrate(self, n_jobs: int) -> None:

params, losses = cal.calibrate(14)

# This is a temporary workaround to make tests to run also on Windows.
# See: https://github.com/bancaditalia/black-it/issues/49
if sys.platform == "darwin":
assert np.allclose(params, self.darwin_expected_params)
assert np.allclose(losses, self.darwin_expected_losses)
else:
assert np.allclose(params, self.expected_params)
assert np.allclose(losses, self.expected_losses)
assert np.allclose(params, self.expected_params)
assert np.allclose(losses, self.expected_losses)

def test_calibrator_with_check_convergence(
self,
Expand Down
14 changes: 6 additions & 8 deletions tests/test_losses/test_fourier.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,30 @@
)


def test_fourier_ideal_low_pass() -> None:
def test_fourier_ideal_low_pass(rng: np.random.Generator) -> None:
"""Test the Fourier loss with the ideal low-pass filter."""
np.random.seed(11)
series_real = np.sin(np.linspace(0, 50, 1000))[:, None]
series_sim = series_real + np.random.normal(0, 0.1, series_real.shape)
series_sim = series_real + rng.normal(0, 0.1, series_real.shape)
euclidean_loss = np.sqrt(np.sum((series_sim - series_real) ** 2))

# check that for no filter (f=1.0) this loss is approximately equivalent to
# the Euclidean loss and that with increasingly aggressive filters (up to
# f=0.01) the loss goes towards zero.
expected_losses = [euclidean_loss, 2.23, 0.97, 0.27]
expected_losses = [euclidean_loss, 2.21, 0.97, 0.187]
for i, f in enumerate([1.0, 0.5, 0.1, 0.01]):
loss_func = FourierLoss(f=f, frequency_filter=ideal_low_pass_filter)
loss = loss_func.compute_loss(series_sim[None, :, :], series_real)
assert np.isclose(expected_losses[i], loss, atol=0.01)


def test_fourier_gaussian_low_pass() -> None:
def test_fourier_gaussian_low_pass(rng: np.random.Generator) -> None:
"""Test the Fourier loss with the gaussian low-pass filter."""
np.random.seed(11)
series_real = np.sin(np.linspace(0, 50, 1000))[:, None]
series_sim = series_real + np.random.normal(0, 0.1, series_real.shape)
series_sim = series_real + rng.normal(0, 0.1, series_real.shape)

# check that with increasingly aggressive filters (up to f=0.01) the loss
# goes towards zero.
expected_losses = [2.75, 0.95, 0.27]
expected_losses = [2.73, 0.95, 0.19]
for i, f in enumerate([1.0, 0.1, 0.01]):
loss_func = FourierLoss(f=f, frequency_filter=gaussian_low_pass_filter)
loss = loss_func.compute_loss(series_sim[None, :, :], series_real)
Expand Down
27 changes: 12 additions & 15 deletions tests/test_losses/test_gsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,41 +98,38 @@ def test_get_words(self, args: tuple) -> None: # noqa: PLR6301
GslDivLoss.get_words(*args)


def test_gsl_default() -> None:
def test_gsl_default(rng: np.random.Generator) -> None:
"""Test the Gsl-div loss function."""
expected_loss = 0.39737637181336855
expected_loss = 0.3972285978726733

np.random.seed(11)
series_sim = np.random.normal(0, 1, (100, 3))
series_real = np.random.normal(0, 1, (100, 3))
series_sim = rng.normal(0, 1, (100, 3))
series_real = rng.normal(0, 1, (100, 3))

loss_func = GslDivLoss()
loss = loss_func.compute_loss(series_sim[None, :, :], series_real)

assert np.isclose(expected_loss, loss)


def test_gsl_with_nb_values() -> None:
def test_gsl_with_nb_values(rng: np.random.Generator) -> None:
"""Test the Gsl-div loss function with nb_values set."""
expected_loss = 0.4353415724764564
expected_loss = 0.4354049587629579

np.random.seed(11)
series_sim = np.random.normal(0, 1, (2, 100, 3))
series_real = np.random.normal(0, 1, (100, 3))
series_sim = rng.normal(0, 1, (2, 100, 3))
series_real = rng.normal(0, 1, (100, 3))

loss_func = GslDivLoss(nb_values=10)
loss = loss_func.compute_loss(series_sim, series_real)

assert np.isclose(expected_loss, loss)


def test_gsl_with_nb_word_lengths() -> None:
def test_gsl_with_nb_word_lengths(rng: np.random.Generator) -> None:
"""Test the Gsl-div loss function with nb_word_lengths set."""
expected_loss = 0.7210261201578492
expected_loss = 0.7177347914787273

np.random.seed(11)
series_sim = np.random.normal(0, 1, (100, 3))
series_real = np.random.normal(0, 1, (100, 3))
series_sim = rng.normal(0, 1, (100, 3))
series_real = rng.normal(0, 1, (100, 3))

loss_func = GslDivLoss(nb_word_lengths=10)
loss = loss_func.compute_loss(series_sim[None, :, :], series_real)
Expand Down
21 changes: 9 additions & 12 deletions tests/test_losses/test_likelihood.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,49 +19,46 @@
from black_it.loss_functions.likelihood import LikelihoodLoss


def test_likelihood_1d() -> None:
def test_likelihood_1d(rng: np.random.Generator) -> None:
"""Test the computation of the Likelihood in the Likelihood loss in 1d."""
# sample from a Gaussian distribution.
np.random.seed(11)
real_data = np.random.normal(0, 1, size=(7, 1))
real_data = rng.normal(0, 1, size=(7, 1))

expected_neg_log_likelihood = -np.sum(
-0.5 * np.sum(real_data**2, axis=1) - 0.5 * np.log(2.0 * np.pi),
axis=0,
)
expected_likelihood = np.exp(-expected_neg_log_likelihood)

sim_data_ensemble = np.random.normal(0, 1, size=(3, 100000, 1))
sim_data_ensemble = rng.normal(0, 1, size=(3, 100000, 1))
loss = LikelihoodLoss(h="silverman")
neg_log_lik = loss.compute_loss(sim_data_ensemble, real_data)
lik = np.exp(-neg_log_lik)
assert np.isclose(lik, expected_likelihood, rtol=0.1)


def test_likelihood_2d() -> None:
def test_likelihood_2d(rng: np.random.Generator) -> None:
"""Test the computation of the Likelihood in the Likelihood loss in 2d."""
# sample from a Gaussian distribution.
np.random.seed(11)
real_data = np.random.normal(0, 1, size=(10, 2))
real_data = rng.normal(0, 1, size=(10, 2))

expected_neg_log_likelihood = -np.sum(
-0.5 * np.sum(real_data**2, axis=1) - 2.0 / 2.0 * np.log(2.0 * np.pi),
axis=0,
)
expected_likelihood = np.exp(-expected_neg_log_likelihood)
sim_data_ensemble = np.random.normal(0, 1, size=(1, 1000000, 2))
sim_data_ensemble = rng.normal(0, 1, size=(1, 1000000, 2))
loss = LikelihoodLoss(h=1.0)
neg_log_lik = loss.compute_loss(sim_data_ensemble, real_data)
lik = np.exp(-neg_log_lik)
assert np.isclose(lik, expected_likelihood, rtol=0.1)


def test_likelihood_2d_wsigma() -> None:
def test_likelihood_2d_wsigma(rng: np.random.Generator) -> None:
"""Test the computation of the Likelihood in the Likelihood loss in 2d."""
# sample from a Gaussian distribution.
np.random.seed(11)
sigma, d = 3.0, 2
real_data = np.random.normal(0, sigma, size=(10, d))
real_data = rng.normal(0, sigma, size=(10, d))

expected_neg_log_likelihood = -np.sum(
-0.5 / sigma**2 * np.sum(real_data**2, axis=1)
Expand All @@ -70,7 +67,7 @@ def test_likelihood_2d_wsigma() -> None:
)
expected_likelihood = np.exp(-expected_neg_log_likelihood)

sim_data_ensemble = np.random.normal(0, sigma, size=(1, 1000000, d))
sim_data_ensemble = rng.normal(0, sigma, size=(1, 1000000, d))
loss = LikelihoodLoss(h=sigma)
neg_log_lik = loss.compute_loss(sim_data_ensemble, real_data)
lik = np.exp(-neg_log_lik)
Expand Down
40 changes: 23 additions & 17 deletions tests/test_losses/test_msm.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@
from black_it.loss_functions.msm import MethodOfMomentsLoss


def test_msm_default() -> None:
def test_msm_default(rng: np.random.Generator) -> None:
"""Test the 'method of moments' loss."""
expected_loss = 2.830647081075395
expected_loss = 2.054721024744742

np.random.seed(11)
# ensemble size 2, time series length 100, number of variables 3
series_sim = np.random.normal(0, 1, (2, 100, 3))
series_real = np.random.normal(0, 1, (100, 3))
series_sim = rng.normal(0, 1, (2, 100, 3))
series_real = rng.normal(0, 1, (100, 3))

loss_func = MethodOfMomentsLoss()

Expand All @@ -39,15 +38,16 @@ def test_msm_default() -> None:
assert np.isclose(expected_loss, loss)


def test_msm_default_calculator_custom_covariance_matrix() -> None:
def test_msm_default_calculator_custom_covariance_matrix(
rng: np.random.Generator,
) -> None:
"""Test the MSM loss when the covariance matrix is not None."""
expected_loss = 16.49853079135471
expected_loss = 7.569748247731355

np.random.seed(11)
series_sim = np.random.normal(0, 1, (2, 100, 3))
series_real = np.random.normal(0, 1, (100, 3))
series_sim = rng.normal(0, 1, (2, 100, 3))
series_real = rng.normal(0, 1, (100, 3))

random_mat = np.random.rand(18, 18)
random_mat = rng.random(size=(18, 18))
covariance_matrix = random_mat.T.dot(random_mat)

loss_func = MethodOfMomentsLoss(covariance_mat=covariance_matrix)
Expand All @@ -57,19 +57,23 @@ def test_msm_default_calculator_custom_covariance_matrix() -> None:
assert np.isclose(expected_loss, loss)


def test_msm_default_calculator_non_symmetric_covariance_matrix() -> None:
def test_msm_default_calculator_non_symmetric_covariance_matrix(
rng: np.random.Generator,
) -> None:
"""Test the MSM loss raises error when the provided matrix is not symmetric."""
with pytest.raises(
ValueError,
match="the provided covariance matrix is not valid as it is not a symmetric matrix",
):
MethodOfMomentsLoss(covariance_mat=np.random.rand(2, 3))
MethodOfMomentsLoss(covariance_mat=rng.random(size=(2, 3)))


def test_msm_default_calculator_wrong_shape_covariance_matrix() -> None:
def test_msm_default_calculator_wrong_shape_covariance_matrix(
rng: np.random.Generator,
) -> None:
"""Test the MSM loss raises error when the covariance matrix has wrong shape."""
dimension = 20
random_mat = np.random.rand(dimension, dimension)
random_mat = rng.random(size=(dimension, dimension))
wrong_covariance_matrix = random_mat.T.dot(random_mat)
with pytest.raises(
ValueError,
Expand All @@ -93,7 +97,9 @@ def custom_moment_calculator(time_series: NDArray) -> NDArray:
assert np.isclose(expected_loss, loss)


def test_msm_custom_calculator_wrong_shape_covariance_matrix() -> None:
def test_msm_custom_calculator_wrong_shape_covariance_matrix(
rng: np.random.Generator,
) -> None:
"""Test the 'method of moments' loss with a custom calculator and a custom covariance of the wrong shape."""
series_sim = np.array([[0, 1]]).T
series_real = np.array([[1, 2]]).T
Expand All @@ -102,7 +108,7 @@ def custom_moment_calculator(time_series: NDArray) -> NDArray:
return np.array([np.mean(time_series)])

dimension = 3
random_mat = np.random.rand(dimension, dimension)
random_mat = rng.random(size=(dimension, dimension))
wrong_covariance_matrix = random_mat.T.dot(random_mat)

loss_func = MethodOfMomentsLoss(
Expand Down
Loading

0 comments on commit e180ee0

Please sign in to comment.