From e5614fea6eba1c57ecb39c39f7b7aeb055204da4 Mon Sep 17 00:00:00 2001 From: Stefano Date: Thu, 6 Apr 2023 17:06:00 +0200 Subject: [PATCH] Using an object for the solver output. --- src/hippopt/base/opti_solver.py | 8 +++-- src/hippopt/base/optimization_solver.py | 20 +++++++++-- test/test_optimization_problem.py | 44 +++++++++++++------------ 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/hippopt/base/opti_solver.py b/src/hippopt/base/opti_solver.py index d52632a9..ae0f1f39 100644 --- a/src/hippopt/base/opti_solver.py +++ b/src/hippopt/base/opti_solver.py @@ -7,7 +7,7 @@ from hippopt.base.continuous_variable import ContinuousVariable from hippopt.base.optimization_object import OptimizationObject, TOptimizationObject -from hippopt.base.optimization_solver import OptimizationSolver +from hippopt.base.optimization_solver import OptimizationSolver, SolverOutput from hippopt.base.parameter import Parameter @@ -395,12 +395,14 @@ def set_opti_options( self._inner_solver, self._options_plugin, self._options_solver ) - def solve(self) -> Tuple[TOptimizationObject, float]: + def solve(self) -> SolverOutput: self._solver.minimize(self._cost) self._opti_solution = self._solver.solve() self._output_cost = self._opti_solution.value(self._cost) self._output_solution = self._generate_solution_output(self._variables) - return self._output_solution, self._output_cost + return SolverOutput( + _values=self._output_solution, _cost_value=self._output_cost + ) def get_solution(self) -> TOptimizationObject | List[TOptimizationObject]: if self._output_solution is None: diff --git a/src/hippopt/base/optimization_solver.py b/src/hippopt/base/optimization_solver.py index 5a861376..9d8e00e4 100644 --- a/src/hippopt/base/optimization_solver.py +++ b/src/hippopt/base/optimization_solver.py @@ -1,12 +1,28 @@ import abc import dataclasses -from typing import List, Tuple, TypeVar +from typing import Generic, List, Tuple, TypeVar import casadi as cs from hippopt.base.optimization_object import TOptimizationObject TOptimizationSolver = TypeVar("TOptimizationSolver", bound="OptimizationSolver") +TGenericOptimizationObject = TypeVar("TGenericOptimizationObject") + + +@dataclasses.dataclass +class SolverOutput(Generic[TGenericOptimizationObject]): + values: TGenericOptimizationObject = dataclasses.field(default=None) + cost_value: float = None + + _values: dataclasses.InitVar[TGenericOptimizationObject] = dataclasses.field( + default=None + ) + _cost_value: dataclasses.InitVar[float] = dataclasses.field(default=None) + + def __post_init__(self, _values: TGenericOptimizationObject, _cost_value: float): + self.values = _values + self.cost_value = _cost_value @dataclasses.dataclass @@ -24,7 +40,7 @@ def set_initial_guess( pass @abc.abstractmethod - def solve(self) -> Tuple[TOptimizationObject, float]: + def solve(self) -> SolverOutput: pass @abc.abstractmethod diff --git a/test/test_optimization_problem.py b/test/test_optimization_problem.py index 3e8d777c..06e218da 100644 --- a/test/test_optimization_problem.py +++ b/test/test_optimization_problem.py @@ -46,7 +46,7 @@ def test_opti_solver(): expression=(var.variable[k] >= c[k] for k in range(size)), # noqa ) - output, cost_value = problem.solver().solve() + output = problem.solver().solve() expected_x = np.zeros(size) expected_cost = 0 @@ -59,8 +59,8 @@ def test_opti_solver(): else a[i] * (c[i] ** 2) + b[i] * c[i] ) - assert output.variable == pytest.approx(expected_x) - assert cost_value == pytest.approx(expected_cost) + assert output.values.variable == pytest.approx(expected_x) + assert output.cost_value == pytest.approx(expected_cost) assert problem.solver().get_solution().variable == pytest.approx(expected_x) assert problem.solver().get_cost_value() == pytest.approx(expected_cost) @@ -104,7 +104,7 @@ def test_opti_solver_with_parameters(): problem.solver().set_initial_guess(initial_guess=initial_guess) - output, cost_value = problem.solver().solve() + output = problem.solver().solve() expected_x = np.zeros(3) expected_cost = 0 @@ -117,9 +117,9 @@ def test_opti_solver_with_parameters(): else a[i] * (c[i] ** 2) + b[i] * c[i] ) - assert output.composite.variable == pytest.approx(expected_x) - assert cost_value == pytest.approx(expected_cost) - assert output.parameter == pytest.approx(c) + assert output.values.composite.variable == pytest.approx(expected_x) + assert output.cost_value == pytest.approx(expected_cost) + assert output.values.parameter == pytest.approx(c) assert problem.solver().get_solution().composite.variable == pytest.approx( expected_x @@ -161,7 +161,7 @@ def test_opti_solver_with_parameters_and_lists(): problem.solver().set_initial_guess(initial_guess=initial_guess) - output, cost_value = problem.solver().solve() + output = problem.solver().solve() expected_x = np.zeros(3) expected_cost = 0 @@ -175,10 +175,10 @@ def test_opti_solver_with_parameters_and_lists(): else a[i][j] * (c[i][j] ** 2) + b[i][j] * c[i][j] ) - assert output[i].composite.variable == pytest.approx(expected_x) - assert output[i].parameter == pytest.approx(c[i]) + assert output.values[i].composite.variable == pytest.approx(expected_x) + assert output.values[i].parameter == pytest.approx(c[i]) - assert cost_value == pytest.approx(expected_cost) + assert output.cost_value == pytest.approx(expected_cost) assert problem.solver().get_cost_value() == pytest.approx(expected_cost) @@ -203,10 +203,10 @@ def test_switch_costs(): initial_problem.add_expression( ExpressionType.subject_to, variables.x + variables.y == a - 1 ) # noqa - output, cost_value = initial_problem.solver().solve() + output = initial_problem.solver().solve() expected_cost = a + (a - 2) ** 2 - assert cost_value == pytest.approx(expected=expected_cost, rel=0.1) - assert output.x == pytest.approx(a - 2, rel=0.1) + assert output.cost_value == pytest.approx(expected=expected_cost, rel=0.1) + assert output.values.x == pytest.approx(a - 2, rel=0.1) new_problem = OptimizationProblem() new_variables = new_problem.generate_optimization_objects(SwitchVar()) @@ -221,10 +221,10 @@ def test_switch_costs(): new_variables.x * new_variables.x + 1, expected_value=1, ) - output, cost_value = new_problem.solver().solve() + output = new_problem.solver().solve() expected_cost = a * (a - 1) ** 2 - assert cost_value == pytest.approx(expected=expected_cost, rel=0.1) - assert output.x == pytest.approx(0, abs=1e-4) + assert output.cost_value == pytest.approx(expected=expected_cost, rel=0.1) + assert output.values.x == pytest.approx(0, abs=1e-4) def test_switch_constraints(): @@ -238,7 +238,7 @@ def test_switch_constraints(): initial_problem.add_expression( ExpressionType.subject_to, variables.x + variables.y == a - 1 ) # noqa - initial_output, initial_cost_value = initial_problem.solver().solve() + initial_output = initial_problem.solver().solve() new_problem = OptimizationProblem() new_variables = new_problem.generate_optimization_objects(SwitchVar()) @@ -251,6 +251,8 @@ def test_switch_constraints(): new_problem.add_expression( ExpressionType.minimize, new_variables.x == 5, scaling=1.0 ) - output, cost_value = new_problem.solver().solve() - assert cost_value == pytest.approx(expected=initial_cost_value, rel=0.1) - assert output.x == pytest.approx(initial_output.x) + output = new_problem.solver().solve() + assert output.cost_value == pytest.approx( + expected=initial_output.cost_value, rel=0.1 + ) + assert output.values.x == pytest.approx(initial_output.values.x)