Source code for gemseo.algos.opt.multi_start.multi_start

# 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.
"""Multi-start optimization."""

from __future__ import annotations

from copy import deepcopy
from typing import TYPE_CHECKING
from typing import ClassVar

from gemseo.algos.doe.factory import DOELibraryFactory
from gemseo.algos.opt.base_optimization_library import BaseOptimizationLibrary
from gemseo.algos.opt.base_optimization_library import OptimizationAlgorithmDescription
from gemseo.algos.opt.factory import OptimizationLibraryFactory
from gemseo.algos.opt.multi_start.settings.multi_start_settings import (
    MultiStart_Settings,
)
from gemseo.algos.optimization_problem import OptimizationProblem
from gemseo.utils.multiprocessing.execution import execute

if TYPE_CHECKING:
    from gemseo.typing import RealArray


[docs] class MultiStart(BaseOptimizationLibrary[MultiStart_Settings]): """Multi-start optimization.""" ALGORITHM_INFOS: ClassVar[dict[str, OptimizationAlgorithmDescription]] = { "MultiStart": OptimizationAlgorithmDescription( "Multi-start optimization", "MultiStart", description=( "The optimization algorithm ``multistart`` " "generates starting points using a DOE algorithm" "and run a sub-optimization algorithm from each starting point." "Depending on the sub-optimization algorithm," "``multistart`` can handle integer design variables," "equality and inequality constraints" "as well as multi-objective functions." ), handle_multiobjective=True, handle_integer_variables=True, handle_equality_constraints=True, handle_inequality_constraints=True, Settings=MultiStart_Settings, ) } def __init__(self, algo_name: str = "MultiStart") -> None: # noqa: D107 super().__init__(algo_name) def _run(self, problem: OptimizationProblem) -> None: design_space = problem.design_space # We decrement the maximum number of iterations by one # as a first iteration has already been done in OptimizationLibrary._pre_run. max_iter = self._settings.max_iter - 1 n_processes = self._settings.n_processes n_start = self._settings.n_start opt_algo_max_iter = self._settings.opt_algo_max_iter if opt_algo_max_iter == 0: if max_iter < n_start: msg = ( "Multi-start optimization: " f"the maximum number of iterations ({max_iter + 1}) " f"must be greater than the number of initial points ({n_start})." ) raise ValueError(msg) n = int(max_iter / n_start) opt_algo_max_iter = [n] * n_start for i in range(max_iter - n * n_start): opt_algo_max_iter[i] += 1 else: opt_algo_max_iter = [opt_algo_max_iter] * n_start sum_max_iter = sum(opt_algo_max_iter) if sum_max_iter > max_iter: msg = ( "Multi-start optimization: " f"the sum of the maximum number of iterations ({sum_max_iter}) " f"related to the sub-optimizations " f"is greater than the limit ({max_iter + 1}-1={max_iter})." ) raise ValueError(msg) doe_algo = DOELibraryFactory().create(self._settings.doe_algo_name) if ( "n_samples" in doe_algo.ALGORITHM_INFOS[ self._settings.doe_algo_name ].Settings.model_fields ): self._settings.doe_algo_settings["n_samples"] = n_start samples = doe_algo.compute_doe(design_space, **self._settings.doe_algo_settings) problems = execute( self._optimize, (), n_processes, list(zip(samples, opt_algo_max_iter, strict=False)), ) for problem in problems: database = problem.database f_hist, x_hist = database.get_function_history( self._problem.objective.name, with_x_vect=True ) for xi, fi in zip(x_hist, f_hist, strict=False): self._problem.database.store(xi, {self._problem.objective.name: fi}) for functions in [self._problem.constraints, self._problem.observables]: for f in functions: f_hist, x_hist = database.get_function_history( f.name, with_x_vect=True ) for xi, fi in zip(x_hist, f_hist, strict=False): self._problem.database.store(xi, {f.name: fi}) file_path = self._settings.multistart_file_path if file_path: problem = OptimizationProblem(design_space) problem.objective = self._problem.objective problem.constraints = self._problem.constraints for sub_problem in problems: x_opt = self._get_result(sub_problem, None, None).x_opt problem.database.store(x_opt, sub_problem.database[x_opt]) problem.to_hdf(file_path) def _optimize(self, data: tuple[RealArray, int]) -> OptimizationProblem: """Solve the sub-optimization problem from an initial design value. Args: data: The initial design value and the maximum number of iterations. Returns: The sub-optimization problem. """ initial_point, max_iter = data design_space = deepcopy(self._problem.design_space) design_space.set_current_value(initial_point) problem = OptimizationProblem(design_space) problem.differentiation_method = self._problem.differentiation_method problem.objective = self._problem.objective.original problem.constraints = (c.original for c in self._problem.constraints) problem.observables = (o.original for o in self._problem.observables) factory = OptimizationLibraryFactory() opt_algo = factory.create(self._settings.opt_algo_name) opt_algo.execute( problem, max_iter=max_iter, **self._settings.opt_algo_settings, ) return problem