From f6ae13e0ee7f1d4c1d5132d4db3092327adaf1e8 Mon Sep 17 00:00:00 2001 From: Silas Date: Tue, 17 Mar 2026 12:42:05 +0100 Subject: [PATCH 1/4] More test mockup --- tests/mock_problem.py | 10 -------- tests/mockup.py | 40 ++++++++++++++++++++++++++++++ tests/test_cma_optimizer.py | 2 +- tests/test_sequential_optimizer.py | 2 +- 4 files changed, 42 insertions(+), 12 deletions(-) delete mode 100644 tests/mock_problem.py create mode 100644 tests/mockup.py diff --git a/tests/mock_problem.py b/tests/mock_problem.py deleted file mode 100644 index 9309315..0000000 --- a/tests/mock_problem.py +++ /dev/null @@ -1,10 +0,0 @@ -# pylint: disable=too-few-public-methods -class MockProblem: - '''Problem case for test purposes. Will evaluate an objective by forwarding parameters''' - def __init__(self, parameters, objective_fn): - self.parameters = parameters - self.objective_fn = objective_fn - - def __call__(self, parameter_values): - named_parameters = { p.name : value for p, value in zip(self.parameters, parameter_values) } - return { 'mock' : self.objective_fn(**named_parameters) } diff --git a/tests/mockup.py b/tests/mockup.py new file mode 100644 index 0000000..a54d331 --- /dev/null +++ b/tests/mockup.py @@ -0,0 +1,40 @@ +# pylint: disable=too-few-public-methods +from subprocess import CompletedProcess +from daisypy.optim.file_generator import FileGenerator + +class MockFileGenerator(FileGenerator): + '''Mock file generator that always generates the paths it was constructed with''' + def __init__(self, paths): + self.paths = paths + + def __call__(self, output_directory, params, tagged=True): + return self.paths + + +class MockRunner: + '''Mock runner always returning a CompletedProcess with a specified returncode''' + def __init__(self, args=None, returncode=0): + self.args = args if args is not None else [] + self.returncode = returncode + + def __call__(self, dai_file, output_directory): + return CompletedProcess(self.args, self.returncode) + +class MockObjective: + '''Mock objective always returning a specific value''' + def __init__(self, value=0): + self.value = value + + def __call__(self, daisy_output_directory): + return { 'mock' : self.value } + + +class MockProblem: + '''Problem case for test purposes. Will evaluate an objective by forwarding parameters''' + def __init__(self, parameters, objective_fn): + self.parameters = parameters + self.objective_fn = objective_fn + + def __call__(self, parameter_values): + named_parameters = { p.name : value for p, value in zip(self.parameters, parameter_values) } + return { 'mock' : self.objective_fn(**named_parameters) } diff --git a/tests/test_cma_optimizer.py b/tests/test_cma_optimizer.py index 17fc71f..0fd0da4 100644 --- a/tests/test_cma_optimizer.py +++ b/tests/test_cma_optimizer.py @@ -5,7 +5,7 @@ DefaultLogger, DaisyCMAOptimizer, ) -from .mock_problem import MockProblem +from .mockup import MockProblem from .test_objectives import beale_function def test_cma_optimizer(): diff --git a/tests/test_sequential_optimizer.py b/tests/test_sequential_optimizer.py index 94ae4f4..d1485d6 100644 --- a/tests/test_sequential_optimizer.py +++ b/tests/test_sequential_optimizer.py @@ -6,7 +6,7 @@ DefaultLogger, DaisySequentialOptimizer, ) -from .mock_problem import MockProblem +from .mockup import MockProblem class Objective: # pylint: disable=too-few-public-methods From 14e980682f9854884bc694f34a52a1e7c329b13c Mon Sep 17 00:00:00 2001 From: Silas Date: Tue, 17 Mar 2026 12:44:19 +0100 Subject: [PATCH 2/4] Test that problem always returns a dict of results --- tests/test_problem.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_problem.py diff --git a/tests/test_problem.py b/tests/test_problem.py new file mode 100644 index 0000000..c31f3c7 --- /dev/null +++ b/tests/test_problem.py @@ -0,0 +1,33 @@ +import numpy as np +from daisypy.optim import DaisyOptimizationProblem, ContinuousParameter +from .mockup import (MockRunner, MockFileGenerator, MockObjective) + + +def test_runner_succeds(tmp_path): + '''Test that the return value is as expected when the runner succeds''' + file_generator = MockFileGenerator({'dai' : ''}) + runner = MockRunner() + parameters = { 'dai' : [ContinuousParameter('p', 0, (-1, 1))] } + out_dir = tmp_path + objective = MockObjective(123) + + problem = DaisyOptimizationProblem( + runner, file_generator, objective, parameters, out_dir + ) + result = problem([-1]) + assert result['mock'] == objective.value + + +def test_runner_fails(tmp_path): + '''Test that the return value is nan when the runner fails''' + file_generator = MockFileGenerator({'dai' : ''}) + runner = MockRunner(returncode=1) + parameters = { 'dai' : [ContinuousParameter('p', 0, (-1, 1))] } + out_dir = tmp_path + objective = MockObjective(123) + + problem = DaisyOptimizationProblem( + runner, file_generator, objective, parameters, out_dir + ) + result = problem([-1]) + assert np.isnan(result['mock']) From 37135faa28448c0f73cd134822a51fa0743a5333 Mon Sep 17 00:00:00 2001 From: Silas Date: Tue, 17 Mar 2026 12:49:53 +0100 Subject: [PATCH 3/4] Test problem with MultiObjective --- tests/mockup.py | 5 +++-- tests/test_problem.py | 22 +++++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/mockup.py b/tests/mockup.py index a54d331..56e78cf 100644 --- a/tests/mockup.py +++ b/tests/mockup.py @@ -22,11 +22,12 @@ def __call__(self, dai_file, output_directory): class MockObjective: '''Mock objective always returning a specific value''' - def __init__(self, value=0): + def __init__(self, name='mock', value=0): + self.name = name self.value = value def __call__(self, daisy_output_directory): - return { 'mock' : self.value } + return { self.name : self.value } class MockProblem: diff --git a/tests/test_problem.py b/tests/test_problem.py index c31f3c7..7a73313 100644 --- a/tests/test_problem.py +++ b/tests/test_problem.py @@ -1,5 +1,5 @@ import numpy as np -from daisypy.optim import DaisyOptimizationProblem, ContinuousParameter +from daisypy.optim import DaisyOptimizationProblem, ContinuousParameter, MultiObjective from .mockup import (MockRunner, MockFileGenerator, MockObjective) @@ -9,7 +9,7 @@ def test_runner_succeds(tmp_path): runner = MockRunner() parameters = { 'dai' : [ContinuousParameter('p', 0, (-1, 1))] } out_dir = tmp_path - objective = MockObjective(123) + objective = MockObjective('mock', 123) problem = DaisyOptimizationProblem( runner, file_generator, objective, parameters, out_dir @@ -24,10 +24,26 @@ def test_runner_fails(tmp_path): runner = MockRunner(returncode=1) parameters = { 'dai' : [ContinuousParameter('p', 0, (-1, 1))] } out_dir = tmp_path - objective = MockObjective(123) + objective = MockObjective('mock', 123) problem = DaisyOptimizationProblem( runner, file_generator, objective, parameters, out_dir ) result = problem([-1]) assert np.isnan(result['mock']) + +def test_multi_objective(tmp_path): + '''Test that the return value is as expected for multiple objectives''' + file_generator = MockFileGenerator({'dai' : ''}) + runner = MockRunner() + parameters = { 'dai' : [ContinuousParameter('p', 0, (-1, 1))] } + out_dir = tmp_path + objectives = [ MockObjective(f'mock-{i}', i*123) for i in range(3) ] + objective = MultiObjective('multi', objectives) + + problem = DaisyOptimizationProblem( + runner, file_generator, objective, parameters, out_dir + ) + result = problem([0]) + for obj in objectives: + assert result[obj.name] == obj.value From 4700128e7a7f5252572ee542d94b9dbce364dd24 Mon Sep 17 00:00:00 2001 From: Silas Date: Tue, 17 Mar 2026 13:04:42 +0100 Subject: [PATCH 4/4] Make problem return dict when runner fails --- daisypy/optim/problem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daisypy/optim/problem.py b/daisypy/optim/problem.py index 2ab0b95..973ac72 100644 --- a/daisypy/optim/problem.py +++ b/daisypy/optim/problem.py @@ -93,6 +93,7 @@ def __init__( self.debug = debug def __call__(self, parameter_values): + # TODO: Rewrite to accept a dict of parameters. This is too brittle """Run Daisy with the given parameters and evaluate the objective. The return value depends on @@ -129,5 +130,5 @@ def _run(self, output_directory, named_parameters): sim_result = self.runner(dai_file, output_directory) if sim_result.returncode != 0: print(sim_result) - return np.nan + return { self.objective_fn.name : np.nan } return self.objective_fn(output_directory)