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 MESMER-M integration tests #501

Open
wants to merge 78 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
4edd069
add mesmer_m calibration test
veni-vidi-vici-dormivi Aug 19, 2024
339f07b
add time to calibration results
veni-vidi-vici-dormivi Aug 19, 2024
f52cae8
update calibrate test
veni-vidi-vici-dormivi Aug 20, 2024
a42f690
add test for drawing
veni-vidi-vici-dormivi Aug 20, 2024
9a2bf0d
Merge branch 'main' into m_integrationtests
veni-vidi-vici-dormivi Aug 20, 2024
7d5de93
update emulations
veni-vidi-vici-dormivi Aug 20, 2024
1bd7c74
linting
veni-vidi-vici-dormivi Aug 20, 2024
3c13240
sneakily add nits to other tests
veni-vidi-vici-dormivi Aug 20, 2024
53688e2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 20, 2024
d1f97a8
switch do allclose for drawing
veni-vidi-vici-dormivi Aug 20, 2024
d7bacda
switch to allclose for calibrating
veni-vidi-vici-dormivi Aug 20, 2024
801086c
Merge branch 'main' into m_integrationtests
veni-vidi-vici-dormivi Aug 22, 2024
384624b
nit in example
veni-vidi-vici-dormivi Aug 22, 2024
49caa64
save params as netcdf
veni-vidi-vici-dormivi Aug 22, 2024
bdd0dee
fixes
veni-vidi-vici-dormivi Aug 22, 2024
347e7fe
relax atol some more
veni-vidi-vici-dormivi Aug 22, 2024
620c300
test whats wrong
veni-vidi-vici-dormivi Aug 22, 2024
44e4717
nits
veni-vidi-vici-dormivi Aug 22, 2024
f97ea6e
tr_solver exact
veni-vidi-vici-dormivi Aug 22, 2024
ec37c10
no
veni-vidi-vici-dormivi Aug 22, 2024
5cd11f6
try with more exact jacobian
veni-vidi-vici-dormivi Aug 22, 2024
ad029eb
try with even more exact jacobian
veni-vidi-vici-dormivi Aug 22, 2024
f0c622b
relax tol again
veni-vidi-vici-dormivi Aug 22, 2024
929c80b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 22, 2024
079de67
tighten convergence criteria
veni-vidi-vici-dormivi Aug 22, 2024
e9ca890
check hm coeffs again
veni-vidi-vici-dormivi Aug 22, 2024
74c61ad
tighten some more
veni-vidi-vici-dormivi Aug 22, 2024
1be4c6e
updated data from ch4
Aug 25, 2024
d6fcf6e
untighten
Aug 25, 2024
a641c0b
ch4 and mindeps
Aug 25, 2024
012f4f6
tr_solver = exact
veni-vidi-vici-dormivi Aug 25, 2024
3716db5
tr_solver = lsmr
veni-vidi-vici-dormivi Aug 25, 2024
c16182c
switch to curve_fit
veni-vidi-vici-dormivi Aug 25, 2024
5cb8efe
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 25, 2024
ab9f0b1
forgot false
veni-vidi-vici-dormivi Aug 25, 2024
78b6a3b
try trm method and three point jac
veni-vidi-vici-dormivi Aug 25, 2024
1b378bf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 25, 2024
91c2f95
relax tol
veni-vidi-vici-dormivi Aug 25, 2024
5e74e47
numpy close
veni-vidi-vici-dormivi Aug 25, 2024
718ffd4
back to original method
veni-vidi-vici-dormivi Aug 28, 2024
4f9969a
use lm as method
veni-vidi-vici-dormivi Aug 28, 2024
5f6882d
Merge branch 'main' into m_integrationtests
veni-vidi-vici-dormivi Aug 28, 2024
1615d17
leastsq
veni-vidi-vici-dormivi Aug 28, 2024
cc9be16
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 28, 2024
6f58608
fix
veni-vidi-vici-dormivi Aug 28, 2024
8275ba7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 28, 2024
30df937
check if the rest is fine
veni-vidi-vici-dormivi Aug 28, 2024
5c9d4ff
linting
veni-vidi-vici-dormivi Aug 28, 2024
bc89474
check xi0
veni-vidi-vici-dormivi Aug 28, 2024
388b46e
least_squares and lm
veni-vidi-vici-dormivi Aug 29, 2024
0f0f66c
try different pt method
veni-vidi-vici-dormivi Aug 30, 2024
0df1b71
powell
veni-vidi-vici-dormivi Aug 30, 2024
930dc28
rtol
veni-vidi-vici-dormivi Aug 30, 2024
f4e6a75
relax rtol
veni-vidi-vici-dormivi Aug 30, 2024
f86055b
relax rtol more
veni-vidi-vici-dormivi Aug 30, 2024
66394ae
nits
veni-vidi-vici-dormivi Sep 18, 2024
3d1a738
Merge branch 'main' into m_integrationtests
veni-vidi-vici-dormivi Sep 18, 2024
ac1cb05
remove dev leftovers
veni-vidi-vici-dormivi Sep 18, 2024
81df5a1
try exact again
veni-vidi-vici-dormivi Sep 18, 2024
03558de
assign coords to coeff dims
veni-vidi-vici-dormivi Sep 18, 2024
9036df2
update tests
veni-vidi-vici-dormivi Sep 18, 2024
8d18649
update test
veni-vidi-vici-dormivi Sep 18, 2024
6484592
relax atol
veni-vidi-vici-dormivi Sep 18, 2024
4cdb715
try with rtol
veni-vidi-vici-dormivi Sep 18, 2024
70c2ec0
unique naming
veni-vidi-vici-dormivi Sep 18, 2024
bb88e50
naming and rtol
veni-vidi-vici-dormivi Sep 18, 2024
ec87532
linting
veni-vidi-vici-dormivi Sep 18, 2024
1f5e816
test all params individually
veni-vidi-vici-dormivi Sep 18, 2024
abf634b
fix tests
veni-vidi-vici-dormivi Sep 18, 2024
f5d6423
adjust tol hm_coeffs
veni-vidi-vici-dormivi Sep 18, 2024
4a4ea70
adjust tol for lambda_coeffs
veni-vidi-vici-dormivi Sep 18, 2024
99a2519
linitng
veni-vidi-vici-dormivi Sep 18, 2024
79a70c3
adjust tol ar1_slope
veni-vidi-vici-dormivi Sep 18, 2024
8354e9f
adjust tol ar1_intercept
veni-vidi-vici-dormivi Sep 18, 2024
3b862a0
adjust tol localized cov
veni-vidi-vici-dormivi Sep 18, 2024
991f09c
adjusting rtol for lambda coeffs agian windows
veni-vidi-vici-dormivi Sep 18, 2024
b813543
comment
veni-vidi-vici-dormivi Sep 18, 2024
57a94be
Changelog
veni-vidi-vici-dormivi Sep 18, 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
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ that this led to some numerical changes compared to the MESMER-M publication
- move the harmonic model and power transformer functionalities to the stats module (
`#484 <https://github.com/MESMER-group/mesmer/pull/484>`_).
- add example script for MESMER-M workflow (`#491 <https://github.com/MESMER-group/mesmer/pull/491>`_)
- add integration tests for MESMER-M (`#501 <https://github.com/MESMER-group/mesmer/pull/501>`_)

Auto-Regression
~~~~~~~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion examples/example_mesmer_m.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@
"metadata": {},
"source": [
"### time coordinate\n",
"We need to get the original time coordinate to be able to validate our results later on. If it is not needed to align the final emulations with the original data, this can be omitted, the time coordinates are then given by to forcing data set or can later be generated for example with \n",
"We need to get the original time coordinate to be able to validate our results later on. If it is not needed to align the final emulations with the original data, this can be omitted, the time coordinates can later be generated for example with \n",
"\n",
"\n",
"```python\n",
Expand Down
25 changes: 15 additions & 10 deletions mesmer/stats/_auto_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ def fit_auto_regression_monthly(monthly_data, time_dim="time"):
vectorize=True,
)

ar_params.append(xr.Dataset({"slope": slope, "intercept": intercept}))
ar_params.append(xr.Dataset({"ar1_slope": slope, "ar1_intercept": intercept}))
residuals.append(resids.assign_coords({time_dim: cur_month[time_dim]}))

month = xr.Variable("month", np.arange(1, 13))
Expand Down Expand Up @@ -742,8 +742,8 @@ def draw_auto_regression_monthly(
Dataset containing the estimated parameters of the AR1 process. Must contain the
following DataArray objects:

- intercept
- slope
- ar1_intercept
- ar1_slope

both of shape (12, n_gridpoints).
covariance : xr.DataArray of shape (12, n_gridpoints, n_gridpoints)
Expand Down Expand Up @@ -774,25 +774,30 @@ def draw_auto_regression_monthly(

"""
# check input
_check_dataset_form(ar_params, "ar_params", required_vars=("intercept", "slope"))
month_dim, gridcell_dim = ar_params.intercept.dims
n_months, size = ar_params.intercept.shape
_check_dataset_form(
ar_params, "ar_params", required_vars=("ar1_intercept", "ar1_slope")
)
month_dim, gridcell_dim = ar_params.ar1_intercept.dims
n_months, size = ar_params.ar1_intercept.shape
_check_dataarray_form(
ar_params.intercept,
ar_params.ar1_intercept,
"intercept",
ndim=2,
required_dims=(month_dim, gridcell_dim),
)
_check_dataarray_form(
ar_params.slope, "slope", ndim=2, required_dims=(month_dim, gridcell_dim)
ar_params.ar1_slope,
"ar1_slope",
ndim=2,
required_dims=(month_dim, gridcell_dim),
)
_check_dataarray_form(
covariance, "covariance", ndim=3, shape=(n_months, size, size)
)

result = _draw_ar_corr_monthly_xr_internal(
intercept=ar_params.intercept,
slope=ar_params.slope,
intercept=ar_params.ar1_intercept,
slope=ar_params.ar1_slope,
covariance=covariance,
time=time,
realisation=n_realisations,
Expand Down
14 changes: 9 additions & 5 deletions mesmer/stats/_harmonic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,16 @@ def _fit_fourier_coeffs_np(yearly_predictor, monthly_target, first_guess):

"""

def func(coeffs, yearly_predictor, mon_target):
def residuals_from_fourier_series(coeffs, yearly_predictor, mon_target):
return _generate_fourier_series_np(yearly_predictor, coeffs) - mon_target

# use least_squares to optimize the coefficients
minimize_result = sp.optimize.least_squares(
func,
residuals_from_fourier_series,
first_guess,
args=(yearly_predictor, monthly_target),
loss="linear",
method="lm",
)

coeffs = minimize_result.x
Expand Down Expand Up @@ -296,12 +298,14 @@ def fit_harmonic_model(yearly_predictor, monthly_target, max_order=6, time_dim="
kwargs={"max_order": max_order},
)

coeffs = coeffs.assign_coords({"coeff": np.arange(coeffs.sizes["coeff"])})

preds = yearly_predictor + preds

data_vars = {
"selected_order": selected_order,
"coeffs": coeffs,
"predictions": preds.transpose(time_dim, ...),
"hm_selected_order": selected_order,
"hm_coeffs": coeffs,
"hm_predictions": preds.transpose(time_dim, ...),
}

return xr.Dataset(data_vars)
35 changes: 17 additions & 18 deletions mesmer/stats/_power_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ def get_lambdas_from_covariates(coeffs, yearly_pred):

Parameters
----------
lambda_coeffs : ``xr.Dataset``
lambda_coeffs : ``xr.DataArray``
The parameters of the power transformation for each gridcell and month,
containing ``lambda_coeffs`` with dims (months, n_gridcells, coeff) calculated
containing ``lambda_coeffs`` with dims (months, coeff, n_gridcells) calculated
using ``fit_yeo_johnson_transform``.
yearly_pred : ``xr.DataArray``
yearly values used as predictors for the lambdas. Must have shape shape
Expand All @@ -207,15 +207,15 @@ def get_lambdas_from_covariates(coeffs, yearly_pred):
The parameters of the power transformation for each gridcell month and year

"""
if not isinstance(coeffs, xr.Dataset):
raise TypeError(f"Expected a `xr.Dataset`, got {type(coeffs)}")
if not isinstance(coeffs, xr.DataArray):
raise TypeError(f"Expected a `xr.DataArray`, got {type(coeffs)}")

if not isinstance(yearly_pred, xr.DataArray):
raise TypeError(f"Expected a `xr.DataArray`, got {type(yearly_pred)}")

lambdas = xr.apply_ufunc(
lambda_function,
coeffs.lambda_coeffs,
coeffs,
yearly_pred,
input_core_dims=[("coeff",), []],
output_core_dims=[[]],
Expand Down Expand Up @@ -248,10 +248,9 @@ def fit_yeo_johnson_transform(monthly_residuals, yearly_pred, time_dim="time"):

Returns
-------
:obj:`xr.Dataset`
Dataset containing the estimated coefficients xi_0 and xi_1 needed to estimate
lambda with dimensions (months, n_gridcells) and the lambdas themselves with
dimensions (months, n_gridcells, n_years).
:obj:`xr.DataArray`
Dataset containing the estimated coefficients needed to estimate
lambda with dimensions (months, coeff, n_gridcells).

"""
# TODO allow passing func instead of our fixed lambda_function?
Expand Down Expand Up @@ -279,7 +278,7 @@ def fit_yeo_johnson_transform(monthly_residuals, yearly_pred, time_dim="time"):
output_dtypes=[float],
vectorize=True,
)

res = res.assign_coords({"coeff": np.arange(len(res.coeff))})
coeffs.append(xr.Dataset({"lambda_coeffs": res}))

return xr.concat(coeffs, dim="month")
Expand All @@ -295,9 +294,9 @@ def yeo_johnson_transform(monthly_residuals, coeffs, yearly_pred):
monthly_residuals : ``xr.DataArray`` of shape (n_years*12, n_gridcells)
Monthly residuals after removing harmonic model fits, used to fit for the
optimal transformation parameters (lambdas).
coeffs : ``xr.Dataset``
coeffs : ``xr.DataArray``
The parameters of the power transformation containing ``lambda_coeffs`` of shape
(months, n_gridcells, coeff) for each gridcell, calculated using
(months, coeff, n_gridcells) for each gridcell, calculated using
:func:`lambda_function <mesmer.stats.lambda_function>`.
yearly_pred : ``xr.DataArray`` of shape (n_years, n_gridcells)
yearly values used as predictors for the lambdas.
Expand Down Expand Up @@ -330,8 +329,8 @@ def yeo_johnson_transform(monthly_residuals, coeffs, yearly_pred):
if not isinstance(yearly_pred, xr.DataArray):
raise TypeError(f"Expected a `xr.DataArray`, got {type(yearly_pred)}")

if not isinstance(coeffs, xr.Dataset):
raise TypeError(f"Expected a `xr.Dataset`, got {type(monthly_residuals)}")
if not isinstance(coeffs, xr.DataArray):
raise TypeError(f"Expected a `xr.DataArray`, got {type(monthly_residuals)}")

lambdas = get_lambdas_from_covariates(coeffs, yearly_pred).rename({"time": "year"})
lambdas_stacked = lambdas.stack(stack=["year", "month"])
Expand All @@ -356,9 +355,9 @@ def inverse_yeo_johnson_transform(monthly_residuals, coeffs, yearly_pred):
----------
monthly_residuals : ``xr.DataArray`` of shape (n_years, n_gridcells)
The data to be transformed back to the original scale.
coeffs : ``xr.Dataset``
coeffs : ``xr.DataArray``
The parameters of the power transformation containing ``lambda_coeffs`` of shape
(months, n_gridcells, coeff) for each gridcell, calculated using
(months, coeff, n_gridcells) for each gridcell, calculated using
:func:`lambda_function <mesmer.stats.lambda_function>`.
yearly_pred : ``xr.DataArray`` of shape (n_years, n_gridcells)
yearly values used as predictors for the lambdas.
Expand Down Expand Up @@ -390,8 +389,8 @@ def inverse_yeo_johnson_transform(monthly_residuals, coeffs, yearly_pred):
if not isinstance(yearly_pred, xr.DataArray):
raise TypeError(f"Expected a `xr.DataArray`, got {type(yearly_pred)}")

if not isinstance(coeffs, xr.Dataset):
raise TypeError(f"Expected a `xr.Dataset`, got {type(monthly_residuals)}")
if not isinstance(coeffs, xr.DataArray):
raise TypeError(f"Expected a `xr.DataArray`, got {type(monthly_residuals)}")

lambdas = get_lambdas_from_covariates(coeffs, yearly_pred).rename({"time": "year"})
lambdas_stacked = lambdas.stack(stack=["year", "month"])
Expand Down
175 changes: 175 additions & 0 deletions tests/integration/test_calibrate_mesmer_m.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import importlib

import pytest
import xarray as xr

import mesmer


def test_calibrate_mesmer_m(update_expected_files=False):
# define config values
THRESHOLD_LAND = 1 / 3

REFERENCE_PERIOD = slice("1850", "1900")

LOCALISATION_RADII = list(range(1250, 6251, 250)) + list(range(6500, 8501, 500))

esm = "IPSL-CM6A-LR"

# define paths and load data
TEST_DATA_PATH = importlib.resources.files("mesmer").parent / "tests" / "test-data"
TEST_PATH = TEST_DATA_PATH / "output" / "tas" / "mon"
cmip6_data_path = TEST_DATA_PATH / "calibrate-coarse-grid" / "cmip6-ng"

path_tas_ann = cmip6_data_path / "tas" / "ann" / "g025"
fN_hist_ann = path_tas_ann / f"tas_ann_{esm}_historical_r1i1p1f1_g025.nc"
fN_proj_ann = path_tas_ann / f"tas_ann_{esm}_ssp585_r1i1p1f1_g025.nc"

tas_y = xr.open_mfdataset(
[fN_hist_ann, fN_proj_ann],
combine="by_coords",
use_cftime=True,
combine_attrs="override",
data_vars="minimal",
compat="override",
coords="minimal",
drop_variables=["height", "file_qf"],
).load()

path_tas_mon = cmip6_data_path / "tas" / "mon" / "g025"
fN_hist_mon = path_tas_mon / f"tas_mon_{esm}_historical_r1i1p1f1_g025.nc"
fN_proj_mon = path_tas_mon / f"tas_mon_{esm}_ssp585_r1i1p1f1_g025.nc"
tas_m = xr.open_mfdataset(
[fN_hist_mon, fN_proj_mon],
combine="by_coords",
use_cftime=True,
combine_attrs="override",
data_vars="minimal",
compat="override",
coords="minimal",
drop_variables=["height", "file_qf"],
).load()

# data preprocessing
ref_y = tas_y.sel(time=REFERENCE_PERIOD).mean("time", keep_attrs=True)
ref_m = tas_m.sel(time=REFERENCE_PERIOD).mean("time", keep_attrs=True)

tas_y = tas_y - ref_y
tas_m = tas_m - ref_m

# create local gridded tas data
def mask_and_stack(ds, threshold_land):
ds = mesmer.mask.mask_ocean_fraction(ds, threshold_land)
ds = mesmer.mask.mask_antarctica(ds)
ds = mesmer.grid.stack_lat_lon(ds)
return ds

tas_stacked_y = mask_and_stack(tas_y, threshold_land=THRESHOLD_LAND)
tas_stacked_m = mask_and_stack(tas_m, threshold_land=THRESHOLD_LAND)

# we need to get the original time coordinate to be able to validate our results
m_time = tas_stacked_m.time

# fit harmonic model
harmonic_model_fit = mesmer.stats.fit_harmonic_model(
tas_stacked_y.tas, tas_stacked_m.tas
)

# train power transformer
resids_after_hm = tas_stacked_m - harmonic_model_fit.hm_predictions
pt_coefficients = mesmer.stats.fit_yeo_johnson_transform(
resids_after_hm.tas, tas_stacked_y.tas
)
transformed_hm_resids = mesmer.stats.yeo_johnson_transform(
resids_after_hm.tas, pt_coefficients.lambda_coeffs, tas_stacked_y.tas
)

# fit cyclo-stationary AR(1) process
AR1_fit = mesmer.stats.fit_auto_regression_monthly(
transformed_hm_resids.transformed, time_dim="time"
)

# work out covariance matrix
geodist = mesmer.geospatial.geodist_exact(tas_stacked_y.lon, tas_stacked_y.lat)

phi_gc_localizer = mesmer.stats.gaspari_cohn_correlation_matrices(
geodist, localisation_radii=LOCALISATION_RADII
)

weights = xr.ones_like(AR1_fit.residuals.isel(gridcell=0))
weights.name = "weights"

localized_ecov = mesmer.stats.find_localized_empirical_covariance_monthly(
AR1_fit.residuals, weights, phi_gc_localizer, "time", 30
)

# merge into one dataset
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a huge fan of this there and back renaming and merging. Would it be bad to save each Dataset as individual netCDF? Or merge into a datatree? (But maybe later).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of having one dataset with all the calibrated parameters per ESM. I did some renaming in the modules such that everything can be merged without problems. What do you think about that?

harmonic_model_fit = harmonic_model_fit.drop_vars("hm_predictions")
AR1_fit = AR1_fit.drop_vars("residuals")
localized_ecov = localized_ecov.drop_vars("covariance")
m_time = m_time.rename("monthly_time")
calibrated_params = xr.merge(
[harmonic_model_fit, pt_coefficients, AR1_fit, localized_ecov, m_time]
)

# save params
if update_expected_files:
calibrated_params.to_netcdf(TEST_PATH / "test-mesmer_m-params.nc")
pytest.skip("Updated test-mesmer_m-params.nc")

# testing
else:
# load expected values
expected_params = xr.open_dataset(
TEST_PATH / "test-mesmer_m-params.nc", use_cftime=True
)

# the following parameters should be exactly the same
exact_exp_params = xr.merge(
[
expected_params.hm_selected_order,
expected_params.localization_radius,
expected_params.monthly_time,
]
)
exact_cal_params = xr.merge(
[
calibrated_params.hm_selected_order,
calibrated_params.localization_radius,
calibrated_params.monthly_time,
]
)

xr.testing.assert_equal(exact_exp_params, exact_cal_params)

# compare the rest
# using numpy because it outputs the differences and how many values are off
import numpy as np

# the tols are set to the best we can do
# NOTE: it is always rather few values that are off
np.testing.assert_allclose(
expected_params.hm_coeffs,
calibrated_params.hm_coeffs,
atol=1e-5,
rtol=1 / 3,
)
# NOTE: would have to be atol is 1e12 here - not doing that
np.testing.assert_allclose(
expected_params.lambda_coeffs, calibrated_params.lambda_coeffs, rtol=0.6
)
np.testing.assert_allclose(
expected_params.ar1_slope, calibrated_params.ar1_slope, atol=1e-5, rtol=1e-4
)
np.testing.assert_allclose(
expected_params.ar1_intercept,
calibrated_params.ar1_intercept,
atol=1e-4,
rtol=1e-2 / 3,
)
np.testing.assert_allclose(
expected_params.localized_covariance,
calibrated_params.localized_covariance,
atol=1e-4,
rtol=1e-2,
)
Loading
Loading