Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for harmonic model #431

Merged
merged 28 commits into from
May 16, 2024
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7352fde
add tests for harmonic model
veni-vidi-vici-dormivi Apr 30, 2024
6daf5a3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 30, 2024
57ef0f1
flaking
veni-vidi-vici-dormivi Apr 30, 2024
e05d9b2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 30, 2024
c10b73f
generalise tests
veni-vidi-vici-dormivi May 2, 2024
b34ce55
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 2, 2024
f95697e
add test for fit to bic xr, rearrange and remove order in generate fs
veni-vidi-vici-dormivi May 2, 2024
40a1f45
pandas datetime version compliance
veni-vidi-vici-dormivi May 2, 2024
a49ef68
Revert "pandas datetime version compliance"
veni-vidi-vici-dormivi May 2, 2024
0e83d91
actual pandas datetime compliance
veni-vidi-vici-dormivi May 2, 2024
cf62863
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 2, 2024
1717909
Merge branch 'main' into hm_testing
veni-vidi-vici-dormivi May 14, 2024
0828f57
Merge branch 'main' into hm_testing
veni-vidi-vici-dormivi May 14, 2024
25f535b
make coefficients a numpy array
veni-vidi-vici-dormivi May 14, 2024
fbfe113
extend tests on generate fourier series
veni-vidi-vici-dormivi May 15, 2024
6a5f60a
write out tolerance in test_fit_to_bic
veni-vidi-vici-dormivi May 15, 2024
f2fed33
write out max_order
veni-vidi-vici-dormivi May 15, 2024
24c2131
use time.dt.month for months
veni-vidi-vici-dormivi May 15, 2024
e5ee390
fix for the nan and zero wrangling
veni-vidi-vici-dormivi May 15, 2024
9d0fad2
verbose 0.1 in xr test
veni-vidi-vici-dormivi May 15, 2024
293cf2b
add tests for dataarray in xr func
veni-vidi-vici-dormivi May 15, 2024
140fc23
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 15, 2024
a549bb6
add test for numerical stability
veni-vidi-vici-dormivi May 15, 2024
cfd7ac4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 15, 2024
2d81771
test with non constant predictors
veni-vidi-vici-dormivi May 15, 2024
c1feab9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 15, 2024
471ad5d
remove comment about close residuals
veni-vidi-vici-dormivi May 16, 2024
1387d5d
add Note
veni-vidi-vici-dormivi May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions tests/unit/test_harmonic_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import numpy as np
import pandas as pd
import pytest
import xarray as xr
from packaging.version import Version

from mesmer.core.utils import upsample_yearly_data
from mesmer.mesmer_m.harmonic_model import (
fit_to_bic_np,
fit_to_bic_xr,
generate_fourier_series_np,
)
from mesmer.testing import trend_data_2D


def test_generate_fourier_series_np():
veni-vidi-vici-dormivi marked this conversation as resolved.
Show resolved Hide resolved
n_years = 10
n_months = n_years * 12

yearly_predictor = np.zeros(n_months)
months = np.tile(np.arange(1, 13), n_years)

# dummy yearly cycle
expected = -np.sin(2 * np.pi * (months) / 12) - 2 * np.cos(
2 * np.pi * (months) / 12
)
result = generate_fourier_series_np(
yearly_predictor, np.array([0, -1, 0, -2]), months
veni-vidi-vici-dormivi marked this conversation as resolved.
Show resolved Hide resolved
)
np.testing.assert_equal(result, expected)

yearly_predictor = np.ones(n_months)
result = generate_fourier_series_np(
yearly_predictor, np.array([0, -1, 0, -2]), months
)
# NOTE: yearly_predictor is added to the Fourier series
expected += 1
veni-vidi-vici-dormivi marked this conversation as resolved.
Show resolved Hide resolved
np.testing.assert_equal(result, expected)

result = generate_fourier_series_np(
yearly_predictor, np.array([3.14, -1, 1, -2]), months
)
expected += 3.14 * np.sin(np.pi * months / 6) + 1 * np.cos(np.pi * months / 6)
np.testing.assert_allclose(result, expected, atol=1e-10)


@pytest.mark.parametrize(
"coefficients",
[
np.array([0, -1, 0, -2]),
np.array([1, 2, 3, 4, 5, 6, 7, 8]),
np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
],
)
@pytest.mark.parametrize(
"yearly_predictor",
[np.repeat([-1, 1], 10 * 6), np.linspace(-1, 1, 10 * 12) * 10],
)
veni-vidi-vici-dormivi marked this conversation as resolved.
Show resolved Hide resolved
def test_fit_to_bic_np(coefficients, yearly_predictor):
max_order = 6
months = np.tile(np.arange(1, 13), 10)

monthly_target = generate_fourier_series_np(yearly_predictor, coefficients, months)
selected_order, estimated_coefficients, predictions = fit_to_bic_np(
yearly_predictor, monthly_target, max_order=max_order
)

# assert selected_order == int(len(coefficients) / 4)
# the model does not necessarily select the "correct" order
# (i.e. the third coef array in combination with the second predictor)
# but the coefficients of higher orders should be close to zero

# fill up all coefficient arrays with zeros to have the same length 4*max_order
# to also be able to compare coefficients of higher orders than the original one
original_coefficients = np.concatenate(
[coefficients, np.zeros(4 * max_order - len(coefficients))]
)
estimated_coefficients = np.nan_to_num(
estimated_coefficients,
0,
)
# NOTE: if we would use a constant predictor only the linear combination of coefficients needs to be close
# np.testing.assert_allclose(
# [
# original_coefficients[i] * yearly_predictor[i]
# + original_coefficients[i + 1]
# for i in range(0, len(original_coefficients), 2)
# ],
# [
# estimated_coefficients[i] * yearly_predictor[i]
# + estimated_coefficients[i + 1]
# for i in range(0, len(estimated_coefficients), 2)
# ],
# atol=1e-2,
# )

np.testing.assert_allclose(original_coefficients, estimated_coefficients, atol=1e-2)

np.testing.assert_allclose(predictions, monthly_target, atol=0.1)


def test_fit_to_bic_numerical_stability():
coefficients = np.array([1, 2, 3, 4, 5, 6, 7, 8])
n_years = 3
yearly_predictor = np.ones(12 * n_years)

max_order = 6
months = np.tile(np.arange(1, 13), n_years)

monthly_target = generate_fourier_series_np(yearly_predictor, coefficients, months)
selected_order, estimated_coefficients, predictions = fit_to_bic_np(
yearly_predictor, monthly_target, max_order=max_order
)

assert selected_order == 2

expected_coefficients = np.full(4 * max_order, np.nan)
expected_coefficients[: selected_order * 4] = np.array(
[
1.49981711,
1.49981711,
3.49957326,
3.49957326,
5.4993294,
5.4993294,
7.49908555,
7.49908555,
]
)
expected_predictions = np.array(
[
25.58545928,
9.12336508,
-10.99853688,
-16.9260173,
-5.58765396,
8.99902459,
10.46294769,
-3.07130031,
-16.99780532,
-15.12238966,
3.53558919,
22.99731761,
25.58545928,
9.12336508,
-10.99853688,
-16.9260173,
-5.58765396,
8.99902459,
10.46294769,
-3.07130031,
-16.99780532,
-15.12238966,
3.53558919,
22.99731761,
25.58545928,
9.12336508,
-10.99853688,
-16.9260173,
-5.58765396,
8.99902459,
10.46294769,
-3.07130031,
-16.99780532,
-15.12238966,
3.53558919,
22.99731761,
]
)

np.testing.assert_allclose(expected_coefficients, estimated_coefficients)
np.testing.assert_allclose(predictions, expected_predictions)


@pytest.mark.parametrize(
"coefficients",
[
np.array([0, -1, 0, -2]),
np.array([1, 2, 3, 4, 5, 6, 7, 8]),
np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
],
)
def test_fit_to_bic_xr(coefficients):
yearly_predictor = trend_data_2D(n_timesteps=10, n_lat=3, n_lon=2)

freq = "AS" if Version(pd.__version__) < Version("2.2") else "YS"
yearly_predictor["time"] = xr.cftime_range(
start="2000-01-01", periods=10, freq=freq
)

time = xr.cftime_range(start="2000-01-01", periods=10 * 12, freq="MS")
monthly_time = xr.DataArray(
time,
dims=["time"],
coords={"time": time},
)
upsampled_yearly_predictor = upsample_yearly_data(yearly_predictor, monthly_time)

months = upsampled_yearly_predictor.time.dt.month
monthly_target = xr.apply_ufunc(
generate_fourier_series_np,
upsampled_yearly_predictor,
input_core_dims=[["time"]],
output_core_dims=[["time"]],
vectorize=True,
output_dtypes=[float],
kwargs={"coeffs": coefficients, "months": months},
)

result = fit_to_bic_xr(yearly_predictor, monthly_target)

xr.testing.assert_allclose(result["predictions"], monthly_target, atol=0.1)


def test_fit_to_bix_xr_instance_checks():
yearly_predictor = trend_data_2D(n_timesteps=10, n_lat=3, n_lon=2)
monthly_target = trend_data_2D(n_timesteps=10 * 12, n_lat=3, n_lon=2)

with pytest.raises(TypeError):
fit_to_bic_xr(yearly_predictor.values, monthly_target)

with pytest.raises(TypeError):
fit_to_bic_xr(yearly_predictor, monthly_target.values)
Loading