Source code for gemseo.scenarios.scenario_results.bilevel_scenario_result
# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program 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 this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""BiLevel scenario result."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Final
from gemseo.algos.optimization_result import OptimizationResult
from gemseo.scenarios.scenario_results.scenario_result import ScenarioResult
if TYPE_CHECKING:
from pathlib import Path
from gemseo.scenarios.base_scenario import BaseScenario
[docs]
class BiLevelScenarioResult(ScenarioResult):
"""The result of a :class:`.Scenario` using a :class:`.BiLevel` formulation."""
__SUB_LABEL_FORMATTER: Final[str] = "sub_{}"
"""The formatter to get the name of the key of a sub-problem from its index.
To be used as ``__SUB_LABEL_FORMATTER.format(i)`` where ``i`` is an integer.
"""
__n_sub_problems: int
"""The number of sub-optimization problems."""
def __init__(self, scenario: BaseScenario | str | Path) -> None: # noqa: D107
super().__init__(scenario)
formulation = scenario.formulation
scenario_adapters = formulation.scenario_adapters
main_problem = formulation.optimization_problem
y_opt = main_problem.database[main_problem.solution.x_opt]
optimal_local_design_values = {
variable_name: y_opt[variable_name]
for scenario_adapter in scenario_adapters
for variable_name in scenario_adapter.scenario.design_space.variable_names
}
self.design_variable_names_to_values.update(optimal_local_design_values)
self.__n_sub_problems = len(scenario_adapters)
if not scenario_adapters[0].databases:
return
i_opt = main_problem.database.get_iteration(main_problem.solution.x_opt) - 1
for index, scenario_adapter in enumerate(scenario_adapters):
sub_problem = scenario_adapter.scenario.formulation.optimization_problem
database = sub_problem.database
sub_problem.database = scenario_adapter.databases[i_opt]
result = OptimizationResult.from_optimization_problem(sub_problem)
label = self.__SUB_LABEL_FORMATTER.format(index)
self.optimization_problems_to_results[label] = result
sub_problem.database = database
[docs]
def get_top_optimization_result(self) -> OptimizationResult:
"""Return the optimization result of the top-level optimization problem."""
return self.optimization_result
[docs]
def get_sub_optimization_result(self, index: int) -> OptimizationResult | None:
"""Return the optimization result of a sub-optimization problem if any.
Args:
index: The index of the sub-optimization problem,
between 0 and N-1 where N is the number of sub-optimization problems.
Returns:
The optimization result of a sub-optimization problem, if any.
Raises:
ValueError: If the index is greater than N-1.
"""
max_index = self.__n_sub_problems - 1
if index > max_index:
msg = (
f"The index ({index}) of a sub-optimization result "
f"must be between 0 and {max_index}."
)
raise ValueError(msg)
return self.optimization_problems_to_results.get(
self.__SUB_LABEL_FORMATTER.format(index)
)