diff --git a/pysteps/tests/test_verification_detcontscores_merge.py b/pysteps/tests/test_verification_detcontscores_merge.py new file mode 100644 index 000000000..58ca9db87 --- /dev/null +++ b/pysteps/tests/test_verification_detcontscores_merge.py @@ -0,0 +1,45 @@ +"""Merging reusable verification summaries must preserve their inputs.""" + +from copy import deepcopy + +import numpy as np +import pytest + +from pysteps.verification import detcontscores + + +@pytest.mark.parametrize("axis", [None, 0, (1, 2), -1]) +def test_merge_preserves_inputs(axis): + rng = np.random.default_rng(42) + pred = rng.uniform(0, 10, size=(2, 2, 3, 4)) + obs = rng.uniform(0, 10, size=pred.shape) + summaries = [] + expected = detcontscores.det_cont_fct_init(axis=axis) + for p, o in zip(pred, obs): + summary = detcontscores.det_cont_fct_init(axis=axis) + detcontscores.det_cont_fct_accum(summary, p, o) + detcontscores.det_cont_fct_accum(expected, p, o) + summaries.append(summary) + + before = deepcopy(summaries) + first, second = summaries + merged = detcontscores.det_cont_fct_merge(first, second) + for summary, original in zip(summaries, before): + for key in original: + np.testing.assert_equal(summary[key], original[key]) + + # The same partial summaries may be reused for another aggregate. + repeated = detcontscores.det_cont_fct_merge(first, second) + reverse = detcontscores.det_cont_fct_merge(second, first) + for key, value in expected.items(): + if isinstance(value, np.ndarray): + for result in (merged, repeated, reverse): + np.testing.assert_allclose(result[key], value) + assert not np.shares_memory(result[key], summaries[0][key]) + assert not np.shares_memory(result[key], summaries[1][key]) + + # Updating the merged object must also leave the partial summaries alone. + detcontscores.det_cont_fct_accum(merged, pred[0], obs[0]) + for summary, original in zip(summaries, before): + for key in original: + np.testing.assert_equal(summary[key], original[key]) diff --git a/pysteps/verification/detcontscores.py b/pysteps/verification/detcontscores.py index 25d989016..f483f1313 100644 --- a/pysteps/verification/detcontscores.py +++ b/pysteps/verification/detcontscores.py @@ -16,6 +16,7 @@ """ import collections +import copy import numpy as np from scipy.stats import spearmanr @@ -433,7 +434,7 @@ def det_cont_fct_merge(err_1, err_2): raise ValueError("cannot merge: no data found") # merge the two verification error objects - err = err_1.copy() + err = copy.deepcopy(err_1) # update variances _parallel_var(