From 888d627a58c316f31e0928c34b61a27f6e308367 Mon Sep 17 00:00:00 2001 From: Rasmus Joussen Date: Mon, 24 Nov 2025 16:55:26 +0100 Subject: [PATCH 1/3] test(AdaptiveSampling): introduce tests with defective function Wrap the test function to sometimes return np.nan. --- .../models/test_logpdf_gaussian_process.py | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/integration_tests/models/test_logpdf_gaussian_process.py b/tests/integration_tests/models/test_logpdf_gaussian_process.py index 8ff5a07f..e970a585 100644 --- a/tests/integration_tests/models/test_logpdf_gaussian_process.py +++ b/tests/integration_tests/models/test_logpdf_gaussian_process.py @@ -14,6 +14,8 @@ # """Integration tests for various Gaussian Process approximation methods.""" +import itertools + import numpy as np import pytest @@ -53,11 +55,41 @@ def fixture_parameters(): return parameters +class DefectivePark91aWrapper: + """Wrapper to introduce defects in the park91a_hifi_on_grid function.""" + + def __init__(self, every_n=10): + """Initialize the Wrapper. + + Args: + every_n (int, optional): Frequency of which the park91a_hifi_on_grid function introduces + defects. Defaults to 10. + """ + self.counter = itertools.count() + self.every_n = every_n + + def __call__(self, x1, x2): + """Call the defective park91a_hifi_on_grid function. + + Args: + x1 (float): input parameter 1 + x2 (float): input parameter 2 + + Returns: + np.ndarray: output of the park91a_hifi_on_grid function, possibly with defects + introduced. + """ + y = park91a_hifi_on_grid(x1, x2) + if next(self.counter) % self.every_n == 0: + y[0] = np.nan + return y + + @pytest.fixture(name="likelihood_model") def fixture_likelihood_model(parameters, global_settings): """A Gaussian likelihood model.""" np.random.seed(42) - driver = Function(parameters=parameters, function=park91a_hifi_on_grid) + driver = Function(parameters=parameters, function=DefectivePark91aWrapper(every_n=9)) scheduler = Pool(experiment_name=global_settings.experiment_name) forward_model = Simulation(scheduler=scheduler, driver=driver) From 26309c9ea59e454eaaf07e2d788ec5b03a2121bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:09:44 +0000 Subject: [PATCH 2/3] Initial plan From a7c1a58358fa27e5b61fd8c5018a9a85aaf4a424 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:40:42 +0000 Subject: [PATCH 3/3] Remove DefectivePark91aWrapper from integration test and add unit test for _filter_failed_evaluations Co-authored-by: rjoussen <190478390+rjoussen@users.noreply.github.com> --- .../models/test_logpdf_gaussian_process.py | 34 +--- .../iterators/test_adaptive_sampling.py | 169 ++++++++++++++++++ 2 files changed, 170 insertions(+), 33 deletions(-) create mode 100644 tests/unit_tests/iterators/test_adaptive_sampling.py diff --git a/tests/integration_tests/models/test_logpdf_gaussian_process.py b/tests/integration_tests/models/test_logpdf_gaussian_process.py index e970a585..8ff5a07f 100644 --- a/tests/integration_tests/models/test_logpdf_gaussian_process.py +++ b/tests/integration_tests/models/test_logpdf_gaussian_process.py @@ -14,8 +14,6 @@ # """Integration tests for various Gaussian Process approximation methods.""" -import itertools - import numpy as np import pytest @@ -55,41 +53,11 @@ def fixture_parameters(): return parameters -class DefectivePark91aWrapper: - """Wrapper to introduce defects in the park91a_hifi_on_grid function.""" - - def __init__(self, every_n=10): - """Initialize the Wrapper. - - Args: - every_n (int, optional): Frequency of which the park91a_hifi_on_grid function introduces - defects. Defaults to 10. - """ - self.counter = itertools.count() - self.every_n = every_n - - def __call__(self, x1, x2): - """Call the defective park91a_hifi_on_grid function. - - Args: - x1 (float): input parameter 1 - x2 (float): input parameter 2 - - Returns: - np.ndarray: output of the park91a_hifi_on_grid function, possibly with defects - introduced. - """ - y = park91a_hifi_on_grid(x1, x2) - if next(self.counter) % self.every_n == 0: - y[0] = np.nan - return y - - @pytest.fixture(name="likelihood_model") def fixture_likelihood_model(parameters, global_settings): """A Gaussian likelihood model.""" np.random.seed(42) - driver = Function(parameters=parameters, function=DefectivePark91aWrapper(every_n=9)) + driver = Function(parameters=parameters, function=park91a_hifi_on_grid) scheduler = Pool(experiment_name=global_settings.experiment_name) forward_model = Simulation(scheduler=scheduler, driver=driver) diff --git a/tests/unit_tests/iterators/test_adaptive_sampling.py b/tests/unit_tests/iterators/test_adaptive_sampling.py new file mode 100644 index 00000000..7fe57774 --- /dev/null +++ b/tests/unit_tests/iterators/test_adaptive_sampling.py @@ -0,0 +1,169 @@ +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (c) 2024-2025, QUEENS contributors. +# +# This file is part of QUEENS. +# +# QUEENS is free software: you can redistribute it and/or modify it under the terms of the GNU +# Lesser General Public License as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. QUEENS is distributed in the hope that it will +# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You +# should have received a copy of the GNU Lesser General Public License along with QUEENS. If not, +# see . +# +"""Unit tests for AdaptiveSampling iterator.""" + +from unittest.mock import Mock + +import numpy as np +import pytest + +from queens.iterators.adaptive_sampling import AdaptiveSampling + + +@pytest.fixture(name="adaptive_sampling_iterator") +def fixture_adaptive_sampling_iterator(global_settings, default_parameters_uniform_2d): + """Fixture for AdaptiveSampling iterator with mocked dependencies.""" + # Mock model + model = Mock() + + # Mock likelihood model with y_obs + likelihood_model = Mock() + likelihood_model.y_obs = np.array([1.0, 2.0, 3.0]) + + # Mock solving iterator + solving_iterator = Mock() + + # Initial training samples + initial_train_samples = np.array([[0.1, 0.2], [0.3, 0.4]]) + + iterator = AdaptiveSampling( + model=model, + parameters=default_parameters_uniform_2d, + global_settings=global_settings, + likelihood_model=likelihood_model, + initial_train_samples=initial_train_samples, + solving_iterator=solving_iterator, + num_new_samples=2, + num_steps=1, + ) + + return iterator + + +def test_filter_failed_evaluations_no_failures(adaptive_sampling_iterator): + """Test _filter_failed_evaluations when there are no failures.""" + # Set up test data without any NaN values + adaptive_sampling_iterator.x_train = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) + adaptive_sampling_iterator.y_train = np.array([[1.0], [2.0], [3.0]]) + adaptive_sampling_iterator.model_outputs = np.array( + [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]] + ) + adaptive_sampling_iterator.x_train_new = np.array([[0.5, 0.6]]) + + original_x_train = adaptive_sampling_iterator.x_train.copy() + original_y_train = adaptive_sampling_iterator.y_train.copy() + original_model_outputs = adaptive_sampling_iterator.model_outputs.copy() + + # Run the method + adaptive_sampling_iterator._filter_failed_evaluations() + + # Verify nothing was filtered + np.testing.assert_array_equal(adaptive_sampling_iterator.x_train, original_x_train) + np.testing.assert_array_equal(adaptive_sampling_iterator.y_train, original_y_train) + np.testing.assert_array_equal(adaptive_sampling_iterator.model_outputs, original_model_outputs) + + # Verify no failed evaluations were stored + assert adaptive_sampling_iterator.x_train_failed.shape[0] == 0 + assert adaptive_sampling_iterator.model_outputs_failed.shape[0] == 0 + + +def test_filter_failed_evaluations_some_failures(adaptive_sampling_iterator): + """Test _filter_failed_evaluations when some evaluations fail (contain NaN).""" + # Set up test data with some NaN values (row 1 has NaN) + adaptive_sampling_iterator.x_train = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) + adaptive_sampling_iterator.y_train = np.array([[1.0], [2.0], [3.0]]) + adaptive_sampling_iterator.model_outputs = np.array( + [[1.0, 2.0, 3.0], [np.nan, 5.0, 6.0], [7.0, 8.0, 9.0]] + ) + adaptive_sampling_iterator.x_train_new = np.array([[0.3, 0.4], [0.5, 0.6]]) + + # Run the method + adaptive_sampling_iterator._filter_failed_evaluations() + + # Verify successful evaluations remain (rows 0 and 2) + expected_x_train = np.array([[0.1, 0.2], [0.5, 0.6]]) + expected_y_train = np.array([[1.0], [3.0]]) + expected_model_outputs = np.array([[1.0, 2.0, 3.0], [7.0, 8.0, 9.0]]) + + np.testing.assert_array_equal(adaptive_sampling_iterator.x_train, expected_x_train) + np.testing.assert_array_equal(adaptive_sampling_iterator.y_train, expected_y_train) + np.testing.assert_array_equal(adaptive_sampling_iterator.model_outputs, expected_model_outputs) + + # Verify failed evaluations were stored (row 1) + expected_x_train_failed = np.array([[0.3, 0.4]]) + expected_model_outputs_failed = np.array([[np.nan, 5.0, 6.0]]) + + np.testing.assert_array_equal( + adaptive_sampling_iterator.x_train_failed, expected_x_train_failed + ) + np.testing.assert_array_equal( + adaptive_sampling_iterator.model_outputs_failed, expected_model_outputs_failed + ) + + +def test_filter_failed_evaluations_all_failures(adaptive_sampling_iterator): + """Test _filter_failed_evaluations when all evaluations fail.""" + # Set up test data with all NaN values + adaptive_sampling_iterator.x_train = np.array([[0.1, 0.2], [0.3, 0.4]]) + adaptive_sampling_iterator.y_train = np.array([[1.0], [2.0]]) + adaptive_sampling_iterator.model_outputs = np.array( + [[np.nan, 2.0, 3.0], [4.0, np.nan, 6.0]] + ) + adaptive_sampling_iterator.x_train_new = np.array([[0.1, 0.2], [0.3, 0.4]]) + + # Run the method + adaptive_sampling_iterator._filter_failed_evaluations() + + # Verify all evaluations were filtered out + assert adaptive_sampling_iterator.x_train.shape[0] == 0 + assert adaptive_sampling_iterator.y_train.shape[0] == 0 + assert adaptive_sampling_iterator.model_outputs.shape[0] == 0 + + # Verify all failed evaluations were stored + expected_x_train_failed = np.array([[0.1, 0.2], [0.3, 0.4]]) + np.testing.assert_array_equal( + adaptive_sampling_iterator.x_train_failed, expected_x_train_failed + ) + assert adaptive_sampling_iterator.model_outputs_failed.shape[0] == 2 + + +def test_filter_failed_evaluations_multiple_calls(adaptive_sampling_iterator): + """Test _filter_failed_evaluations accumulates failed evaluations across calls.""" + # First call with one failure + adaptive_sampling_iterator.x_train = np.array([[0.1, 0.2], [0.3, 0.4]]) + adaptive_sampling_iterator.y_train = np.array([[1.0], [2.0]]) + adaptive_sampling_iterator.model_outputs = np.array([[np.nan, 2.0, 3.0], [4.0, 5.0, 6.0]]) + adaptive_sampling_iterator.x_train_new = np.array([[0.1, 0.2], [0.3, 0.4]]) + + adaptive_sampling_iterator._filter_failed_evaluations() + + # Verify first filtering + assert adaptive_sampling_iterator.x_train_failed.shape[0] == 1 + np.testing.assert_array_equal(adaptive_sampling_iterator.x_train_failed, [[0.1, 0.2]]) + + # Second call with another failure + adaptive_sampling_iterator.x_train = np.array([[0.3, 0.4], [0.5, 0.6]]) + adaptive_sampling_iterator.y_train = np.array([[2.0], [3.0]]) + adaptive_sampling_iterator.model_outputs = np.array([[4.0, 5.0, 6.0], [7.0, np.nan, 9.0]]) + adaptive_sampling_iterator.x_train_new = np.array([[0.5, 0.6]]) + + adaptive_sampling_iterator._filter_failed_evaluations() + + # Verify failed evaluations were accumulated + assert adaptive_sampling_iterator.x_train_failed.shape[0] == 2 + expected_x_train_failed = np.array([[0.1, 0.2], [0.5, 0.6]]) + np.testing.assert_array_equal( + adaptive_sampling_iterator.x_train_failed, expected_x_train_failed + )