| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506 |
- import warnings
- import numpy as np
- from scipy.optimize import (
- Bounds,
- LinearConstraint,
- NonlinearConstraint,
- OptimizeResult,
- )
- from .framework import TrustRegion
- from .problem import (
- ObjectiveFunction,
- BoundConstraints,
- LinearConstraints,
- NonlinearConstraints,
- Problem,
- )
- from .utils import (
- MaxEvalError,
- TargetSuccess,
- CallbackSuccess,
- FeasibleSuccess,
- exact_1d_array,
- )
- from .settings import (
- ExitStatus,
- Options,
- Constants,
- DEFAULT_OPTIONS,
- DEFAULT_CONSTANTS,
- PRINT_OPTIONS,
- )
- def minimize(
- fun,
- x0,
- args=(),
- bounds=None,
- constraints=(),
- callback=None,
- options=None,
- **kwargs,
- ):
- r"""
- Minimize a scalar function using the COBYQA method.
- The Constrained Optimization BY Quadratic Approximations (COBYQA) method is
- a derivative-free optimization method designed to solve general nonlinear
- optimization problems. A complete description of COBYQA is given in [3]_.
- Parameters
- ----------
- fun : {callable, None}
- Objective function to be minimized.
- ``fun(x, *args) -> float``
- where ``x`` is an array with shape (n,) and `args` is a tuple. If `fun`
- is ``None``, the objective function is assumed to be the zero function,
- resulting in a feasibility problem.
- x0 : array_like, shape (n,)
- Initial guess.
- args : tuple, optional
- Extra arguments passed to the objective function.
- bounds : {`scipy.optimize.Bounds`, array_like, shape (n, 2)}, optional
- Bound constraints of the problem. It can be one of the cases below.
- #. An instance of `scipy.optimize.Bounds`. For the time being, the
- argument ``keep_feasible`` is disregarded, and all the constraints
- are considered unrelaxable and will be enforced.
- #. An array with shape (n, 2). The bound constraints for ``x[i]`` are
- ``bounds[i][0] <= x[i] <= bounds[i][1]``. Set ``bounds[i][0]`` to
- :math:`-\infty` if there is no lower bound, and set ``bounds[i][1]``
- to :math:`\infty` if there is no upper bound.
- The COBYQA method always respect the bound constraints.
- constraints : {Constraint, list}, optional
- General constraints of the problem. It can be one of the cases below.
- #. An instance of `scipy.optimize.LinearConstraint`. The argument
- ``keep_feasible`` is disregarded.
- #. An instance of `scipy.optimize.NonlinearConstraint`. The arguments
- ``jac``, ``hess``, ``keep_feasible``, ``finite_diff_rel_step``, and
- ``finite_diff_jac_sparsity`` are disregarded.
- #. A list, each of whose elements are described in the cases above.
- callback : callable, optional
- A callback executed at each objective function evaluation. The method
- terminates if a ``StopIteration`` exception is raised by the callback
- function. Its signature can be one of the following:
- ``callback(intermediate_result)``
- where ``intermediate_result`` is a keyword parameter that contains an
- instance of `scipy.optimize.OptimizeResult`, with attributes ``x``
- and ``fun``, being the point at which the objective function is
- evaluated and the value of the objective function, respectively. The
- name of the parameter must be ``intermediate_result`` for the callback
- to be passed an instance of `scipy.optimize.OptimizeResult`.
- Alternatively, the callback function can have the signature:
- ``callback(xk)``
- where ``xk`` is the point at which the objective function is evaluated.
- Introspection is used to determine which of the signatures to invoke.
- options : dict, optional
- Options passed to the solver. Accepted keys are:
- disp : bool, optional
- Whether to print information about the optimization procedure.
- Default is ``False``.
- maxfev : int, optional
- Maximum number of function evaluations. Default is ``500 * n``.
- maxiter : int, optional
- Maximum number of iterations. Default is ``1000 * n``.
- target : float, optional
- Target on the objective function value. The optimization
- procedure is terminated when the objective function value of a
- feasible point is less than or equal to this target. Default is
- ``-numpy.inf``.
- feasibility_tol : float, optional
- Tolerance on the constraint violation. If the maximum
- constraint violation at a point is less than or equal to this
- tolerance, the point is considered feasible. Default is
- ``numpy.sqrt(numpy.finfo(float).eps)``.
- radius_init : float, optional
- Initial trust-region radius. Typically, this value should be in
- the order of one tenth of the greatest expected change to `x0`.
- Default is ``1.0``.
- radius_final : float, optional
- Final trust-region radius. It should indicate the accuracy
- required in the final values of the variables. Default is
- ``1e-6``.
- nb_points : int, optional
- Number of interpolation points used to build the quadratic
- models of the objective and constraint functions. Default is
- ``2 * n + 1``.
- scale : bool, optional
- Whether to scale the variables according to the bounds. Default
- is ``False``.
- filter_size : int, optional
- Maximum number of points in the filter. The filter is used to
- select the best point returned by the optimization procedure.
- Default is ``sys.maxsize``.
- store_history : bool, optional
- Whether to store the history of the function evaluations.
- Default is ``False``.
- history_size : int, optional
- Maximum number of function evaluations to store in the history.
- Default is ``sys.maxsize``.
- debug : bool, optional
- Whether to perform additional checks during the optimization
- procedure. This option should be used only for debugging
- purposes and is highly discouraged to general users. Default is
- ``False``.
- Other constants (from the keyword arguments) are described below. They
- are not intended to be changed by general users. They should only be
- changed by users with a deep understanding of the algorithm, who want
- to experiment with different settings.
- Returns
- -------
- `scipy.optimize.OptimizeResult`
- Result of the optimization procedure, with the following fields:
- message : str
- Description of the cause of the termination.
- success : bool
- Whether the optimization procedure terminated successfully.
- status : int
- Termination status of the optimization procedure.
- x : `numpy.ndarray`, shape (n,)
- Solution point.
- fun : float
- Objective function value at the solution point.
- maxcv : float
- Maximum constraint violation at the solution point.
- nfev : int
- Number of function evaluations.
- nit : int
- Number of iterations.
- If ``store_history`` is True, the result also has the following fields:
- fun_history : `numpy.ndarray`, shape (nfev,)
- History of the objective function values.
- maxcv_history : `numpy.ndarray`, shape (nfev,)
- History of the maximum constraint violations.
- A description of the termination statuses is given below.
- .. list-table::
- :widths: 25 75
- :header-rows: 1
- * - Exit status
- - Description
- * - 0
- - The lower bound for the trust-region radius has been reached.
- * - 1
- - The target objective function value has been reached.
- * - 2
- - All variables are fixed by the bound constraints.
- * - 3
- - The callback requested to stop the optimization procedure.
- * - 4
- - The feasibility problem received has been solved successfully.
- * - 5
- - The maximum number of function evaluations has been exceeded.
- * - 6
- - The maximum number of iterations has been exceeded.
- * - -1
- - The bound constraints are infeasible.
- * - -2
- - A linear algebra error occurred.
- Other Parameters
- ----------------
- decrease_radius_factor : float, optional
- Factor by which the trust-region radius is reduced when the reduction
- ratio is low or negative. Default is ``0.5``.
- increase_radius_factor : float, optional
- Factor by which the trust-region radius is increased when the reduction
- ratio is large. Default is ``numpy.sqrt(2.0)``.
- increase_radius_threshold : float, optional
- Threshold that controls the increase of the trust-region radius when
- the reduction ratio is large. Default is ``2.0``.
- decrease_radius_threshold : float, optional
- Threshold used to determine whether the trust-region radius should be
- reduced to the resolution. Default is ``1.4``.
- decrease_resolution_factor : float, optional
- Factor by which the resolution is reduced when the current value is far
- from its final value. Default is ``0.1``.
- large_resolution_threshold : float, optional
- Threshold used to determine whether the resolution is far from its
- final value. Default is ``250.0``.
- moderate_resolution_threshold : float, optional
- Threshold used to determine whether the resolution is close to its
- final value. Default is ``16.0``.
- low_ratio : float, optional
- Threshold used to determine whether the reduction ratio is low. Default
- is ``0.1``.
- high_ratio : float, optional
- Threshold used to determine whether the reduction ratio is high.
- Default is ``0.7``.
- very_low_ratio : float, optional
- Threshold used to determine whether the reduction ratio is very low.
- This is used to determine whether the models should be reset. Default
- is ``0.01``.
- penalty_increase_threshold : float, optional
- Threshold used to determine whether the penalty parameter should be
- increased. Default is ``1.5``.
- penalty_increase_factor : float, optional
- Factor by which the penalty parameter is increased. Default is ``2.0``.
- short_step_threshold : float, optional
- Factor used to determine whether the trial step is too short. Default
- is ``0.5``.
- low_radius_factor : float, optional
- Factor used to determine which interpolation point should be removed
- from the interpolation set at each iteration. Default is ``0.1``.
- byrd_omojokun_factor : float, optional
- Factor by which the trust-region radius is reduced for the computations
- of the normal step in the Byrd-Omojokun composite-step approach.
- Default is ``0.8``.
- threshold_ratio_constraints : float, optional
- Threshold used to determine which constraints should be taken into
- account when decreasing the penalty parameter. Default is ``2.0``.
- large_shift_factor : float, optional
- Factor used to determine whether the point around which the quadratic
- models are built should be updated. Default is ``10.0``.
- large_gradient_factor : float, optional
- Factor used to determine whether the models should be reset. Default is
- ``10.0``.
- resolution_factor : float, optional
- Factor by which the resolution is decreased. Default is ``2.0``.
- improve_tcg : bool, optional
- Whether to improve the steps computed by the truncated conjugate
- gradient method when the trust-region boundary is reached. Default is
- ``True``.
- References
- ----------
- .. [1] J. Nocedal and S. J. Wright. *Numerical Optimization*. Springer Ser.
- Oper. Res. Financ. Eng. Springer, New York, NY, USA, second edition,
- 2006. `doi:10.1007/978-0-387-40065-5
- <https://doi.org/10.1007/978-0-387-40065-5>`_.
- .. [2] M. J. D. Powell. A direct search optimization method that models the
- objective and constraint functions by linear interpolation. In S. Gomez
- and J.-P. Hennart, editors, *Advances in Optimization and Numerical
- Analysis*, volume 275 of Math. Appl., pages 51--67. Springer, Dordrecht,
- Netherlands, 1994. `doi:10.1007/978-94-015-8330-5_4
- <https://doi.org/10.1007/978-94-015-8330-5_4>`_.
- .. [3] T. M. Ragonneau. *Model-Based Derivative-Free Optimization Methods
- and Software*. PhD thesis, Department of Applied Mathematics, The Hong
- Kong Polytechnic University, Hong Kong, China, 2022. URL:
- https://theses.lib.polyu.edu.hk/handle/200/12294.
- Examples
- --------
- To demonstrate how to use `minimize`, we first minimize the Rosenbrock
- function implemented in `scipy.optimize` in an unconstrained setting.
- .. testsetup::
- import numpy as np
- np.set_printoptions(precision=3, suppress=True)
- >>> from cobyqa import minimize
- >>> from scipy.optimize import rosen
- To solve the problem using COBYQA, run:
- >>> x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
- >>> res = minimize(rosen, x0)
- >>> res.x
- array([1., 1., 1., 1., 1.])
- To see how bound and constraints are handled using `minimize`, we solve
- Example 16.4 of [1]_, defined as
- .. math::
- \begin{aligned}
- \min_{x \in \mathbb{R}^2} & \quad (x_1 - 1)^2 + (x_2 - 2.5)^2\\
- \text{s.t.} & \quad -x_1 + 2x_2 \le 2,\\
- & \quad x_1 + 2x_2 \le 6,\\
- & \quad x_1 - 2x_2 \le 2,\\
- & \quad x_1 \ge 0,\\
- & \quad x_2 \ge 0.
- \end{aligned}
- >>> import numpy as np
- >>> from scipy.optimize import Bounds, LinearConstraint
- Its objective function can be implemented as:
- >>> def fun(x):
- ... return (x[0] - 1.0)**2 + (x[1] - 2.5)**2
- This problem can be solved using `minimize` as:
- >>> x0 = [2.0, 0.0]
- >>> bounds = Bounds([0.0, 0.0], np.inf)
- >>> constraints = LinearConstraint([
- ... [-1.0, 2.0],
- ... [1.0, 2.0],
- ... [1.0, -2.0],
- ... ], -np.inf, [2.0, 6.0, 2.0])
- >>> res = minimize(fun, x0, bounds=bounds, constraints=constraints)
- >>> res.x
- array([1.4, 1.7])
- To see how nonlinear constraints are handled, we solve Problem (F) of [2]_,
- defined as
- .. math::
- \begin{aligned}
- \min_{x \in \mathbb{R}^2} & \quad -x_1 - x_2\\
- \text{s.t.} & \quad x_1^2 - x_2 \le 0,\\
- & \quad x_1^2 + x_2^2 \le 1.
- \end{aligned}
- >>> from scipy.optimize import NonlinearConstraint
- Its objective and constraint functions can be implemented as:
- >>> def fun(x):
- ... return -x[0] - x[1]
- >>>
- >>> def cub(x):
- ... return [x[0]**2 - x[1], x[0]**2 + x[1]**2]
- This problem can be solved using `minimize` as:
- >>> x0 = [1.0, 1.0]
- >>> constraints = NonlinearConstraint(cub, -np.inf, [0.0, 1.0])
- >>> res = minimize(fun, x0, constraints=constraints)
- >>> res.x
- array([0.707, 0.707])
- Finally, to see how to supply linear and nonlinear constraints
- simultaneously, we solve Problem (G) of [2]_, defined as
- .. math::
- \begin{aligned}
- \min_{x \in \mathbb{R}^3} & \quad x_3\\
- \text{s.t.} & \quad 5x_1 - x_2 + x_3 \ge 0,\\
- & \quad -5x_1 - x_2 + x_3 \ge 0,\\
- & \quad x_1^2 + x_2^2 + 4x_2 \le x_3.
- \end{aligned}
- Its objective and nonlinear constraint functions can be implemented as:
- >>> def fun(x):
- ... return x[2]
- >>>
- >>> def cub(x):
- ... return x[0]**2 + x[1]**2 + 4.0*x[1] - x[2]
- This problem can be solved using `minimize` as:
- >>> x0 = [1.0, 1.0, 1.0]
- >>> constraints = [
- ... LinearConstraint(
- ... [[5.0, -1.0, 1.0], [-5.0, -1.0, 1.0]],
- ... [0.0, 0.0],
- ... np.inf,
- ... ),
- ... NonlinearConstraint(cub, -np.inf, 0.0),
- ... ]
- >>> res = minimize(fun, x0, constraints=constraints)
- >>> res.x
- array([ 0., -3., -3.])
- """
- # Get basic options that are needed for the initialization.
- if options is None:
- options = {}
- else:
- options = dict(options)
- verbose = options.get(Options.VERBOSE, DEFAULT_OPTIONS[Options.VERBOSE])
- verbose = bool(verbose)
- feasibility_tol = options.get(
- Options.FEASIBILITY_TOL,
- DEFAULT_OPTIONS[Options.FEASIBILITY_TOL],
- )
- feasibility_tol = float(feasibility_tol)
- scale = options.get(Options.SCALE, DEFAULT_OPTIONS[Options.SCALE])
- scale = bool(scale)
- store_history = options.get(
- Options.STORE_HISTORY,
- DEFAULT_OPTIONS[Options.STORE_HISTORY],
- )
- store_history = bool(store_history)
- if Options.HISTORY_SIZE in options and options[Options.HISTORY_SIZE] <= 0:
- raise ValueError("The size of the history must be positive.")
- history_size = options.get(
- Options.HISTORY_SIZE,
- DEFAULT_OPTIONS[Options.HISTORY_SIZE],
- )
- history_size = int(history_size)
- if Options.FILTER_SIZE in options and options[Options.FILTER_SIZE] <= 0:
- raise ValueError("The size of the filter must be positive.")
- filter_size = options.get(
- Options.FILTER_SIZE,
- DEFAULT_OPTIONS[Options.FILTER_SIZE],
- )
- filter_size = int(filter_size)
- debug = options.get(Options.DEBUG, DEFAULT_OPTIONS[Options.DEBUG])
- debug = bool(debug)
- # Initialize the objective function.
- if not isinstance(args, tuple):
- args = (args,)
- obj = ObjectiveFunction(fun, verbose, debug, *args)
- # Initialize the bound constraints.
- if not hasattr(x0, "__len__"):
- x0 = [x0]
- n_orig = len(x0)
- bounds = BoundConstraints(_get_bounds(bounds, n_orig))
- # Initialize the constraints.
- linear_constraints, nonlinear_constraints = _get_constraints(constraints)
- linear = LinearConstraints(linear_constraints, n_orig, debug)
- nonlinear = NonlinearConstraints(nonlinear_constraints, verbose, debug)
- # Initialize the problem (and remove the fixed variables).
- pb = Problem(
- obj,
- x0,
- bounds,
- linear,
- nonlinear,
- callback,
- feasibility_tol,
- scale,
- store_history,
- history_size,
- filter_size,
- debug,
- )
- # Set the default options.
- _set_default_options(options, pb.n)
- constants = _set_default_constants(**kwargs)
- # Initialize the models and skip the computations whenever possible.
- if not pb.bounds.is_feasible:
- # The bound constraints are infeasible.
- return _build_result(
- pb,
- 0.0,
- False,
- ExitStatus.INFEASIBLE_ERROR,
- 0,
- options,
- )
- elif pb.n == 0:
- # All variables are fixed by the bound constraints.
- return _build_result(
- pb,
- 0.0,
- True,
- ExitStatus.FIXED_SUCCESS,
- 0,
- options,
- )
- if verbose:
- print("Starting the optimization procedure.")
- print(f"Initial trust-region radius: {options[Options.RHOBEG]}.")
- print(f"Final trust-region radius: {options[Options.RHOEND]}.")
- print(
- f"Maximum number of function evaluations: "
- f"{options[Options.MAX_EVAL]}."
- )
- print(f"Maximum number of iterations: {options[Options.MAX_ITER]}.")
- print()
- try:
- framework = TrustRegion(pb, options, constants)
- except TargetSuccess:
- # The target on the objective function value has been reached
- return _build_result(
- pb,
- 0.0,
- True,
- ExitStatus.TARGET_SUCCESS,
- 0,
- options,
- )
- except CallbackSuccess:
- # The callback raised a StopIteration exception.
- return _build_result(
- pb,
- 0.0,
- True,
- ExitStatus.CALLBACK_SUCCESS,
- 0,
- options,
- )
- except FeasibleSuccess:
- # The feasibility problem has been solved successfully.
- return _build_result(
- pb,
- 0.0,
- True,
- ExitStatus.FEASIBLE_SUCCESS,
- 0,
- options,
- )
- except MaxEvalError:
- # The maximum number of function evaluations has been exceeded.
- return _build_result(
- pb,
- 0.0,
- False,
- ExitStatus.MAX_ITER_WARNING,
- 0,
- options,
- )
- except np.linalg.LinAlgError:
- # The construction of the initial interpolation set failed.
- return _build_result(
- pb,
- 0.0,
- False,
- ExitStatus.LINALG_ERROR,
- 0,
- options,
- )
- # Start the optimization procedure.
- success = False
- n_iter = 0
- k_new = None
- n_short_steps = 0
- n_very_short_steps = 0
- n_alt_models = 0
- while True:
- # Stop the optimization procedure if the maximum number of iterations
- # has been exceeded. We do not write the main loop as a for loop
- # because we want to access the number of iterations outside the loop.
- if n_iter >= options[Options.MAX_ITER]:
- status = ExitStatus.MAX_ITER_WARNING
- break
- n_iter += 1
- # Update the point around which the quadratic models are built.
- if (
- np.linalg.norm(
- framework.x_best - framework.models.interpolation.x_base
- )
- >= constants[Constants.LARGE_SHIFT_FACTOR] * framework.radius
- ):
- framework.shift_x_base(options)
- # Evaluate the trial step.
- radius_save = framework.radius
- normal_step, tangential_step = framework.get_trust_region_step(options)
- step = normal_step + tangential_step
- s_norm = np.linalg.norm(step)
- # If the trial step is too short, we do not attempt to evaluate the
- # objective and constraint functions. Instead, we reduce the
- # trust-region radius and check whether the resolution should be
- # enhanced and whether the geometry of the interpolation set should be
- # improved. Otherwise, we entertain a classical iteration. The
- # criterion for performing an exceptional jump is taken from NEWUOA.
- if (
- s_norm
- <= constants[Constants.SHORT_STEP_THRESHOLD] * framework.resolution
- ):
- framework.radius *= constants[Constants.DECREASE_RESOLUTION_FACTOR]
- if radius_save > framework.resolution:
- n_short_steps = 0
- n_very_short_steps = 0
- else:
- n_short_steps += 1
- n_very_short_steps += 1
- if s_norm > 0.1 * framework.resolution:
- n_very_short_steps = 0
- enhance_resolution = n_short_steps >= 5 or n_very_short_steps >= 3
- if enhance_resolution:
- n_short_steps = 0
- n_very_short_steps = 0
- improve_geometry = False
- else:
- try:
- k_new, dist_new = framework.get_index_to_remove()
- except np.linalg.LinAlgError:
- status = ExitStatus.LINALG_ERROR
- break
- improve_geometry = dist_new > max(
- framework.radius,
- constants[Constants.RESOLUTION_FACTOR]
- * framework.resolution,
- )
- else:
- # Increase the penalty parameter if necessary.
- same_best_point = framework.increase_penalty(step)
- if same_best_point:
- # Evaluate the objective and constraint functions.
- try:
- fun_val, cub_val, ceq_val = _eval(
- pb,
- framework,
- step,
- options,
- )
- except TargetSuccess:
- status = ExitStatus.TARGET_SUCCESS
- success = True
- break
- except FeasibleSuccess:
- status = ExitStatus.FEASIBLE_SUCCESS
- success = True
- break
- except CallbackSuccess:
- status = ExitStatus.CALLBACK_SUCCESS
- success = True
- break
- except MaxEvalError:
- status = ExitStatus.MAX_EVAL_WARNING
- break
- # Perform a second-order correction step if necessary.
- merit_old = framework.merit(
- framework.x_best,
- framework.fun_best,
- framework.cub_best,
- framework.ceq_best,
- )
- merit_new = framework.merit(
- framework.x_best + step, fun_val, cub_val, ceq_val
- )
- if (
- pb.type == "nonlinearly constrained"
- and merit_new > merit_old
- and np.linalg.norm(normal_step)
- > constants[Constants.BYRD_OMOJOKUN_FACTOR] ** 2.0
- * framework.radius
- ):
- soc_step = framework.get_second_order_correction_step(
- step, options
- )
- if np.linalg.norm(soc_step) > 0.0:
- step += soc_step
- # Evaluate the objective and constraint functions.
- try:
- fun_val, cub_val, ceq_val = _eval(
- pb,
- framework,
- step,
- options,
- )
- except TargetSuccess:
- status = ExitStatus.TARGET_SUCCESS
- success = True
- break
- except FeasibleSuccess:
- status = ExitStatus.FEASIBLE_SUCCESS
- success = True
- break
- except CallbackSuccess:
- status = ExitStatus.CALLBACK_SUCCESS
- success = True
- break
- except MaxEvalError:
- status = ExitStatus.MAX_EVAL_WARNING
- break
- # Calculate the reduction ratio.
- ratio = framework.get_reduction_ratio(
- step,
- fun_val,
- cub_val,
- ceq_val,
- )
- # Choose an interpolation point to remove.
- try:
- k_new = framework.get_index_to_remove(
- framework.x_best + step
- )[0]
- except np.linalg.LinAlgError:
- status = ExitStatus.LINALG_ERROR
- break
- # Update the interpolation set.
- try:
- ill_conditioned = framework.models.update_interpolation(
- k_new, framework.x_best + step, fun_val, cub_val,
- ceq_val
- )
- except np.linalg.LinAlgError:
- status = ExitStatus.LINALG_ERROR
- break
- framework.set_best_index()
- # Update the trust-region radius.
- framework.update_radius(step, ratio)
- # Attempt to replace the models by the alternative ones.
- if framework.radius <= framework.resolution:
- if ratio >= constants[Constants.VERY_LOW_RATIO]:
- n_alt_models = 0
- else:
- n_alt_models += 1
- grad = framework.models.fun_grad(framework.x_best)
- try:
- grad_alt = framework.models.fun_alt_grad(
- framework.x_best
- )
- except np.linalg.LinAlgError:
- status = ExitStatus.LINALG_ERROR
- break
- if np.linalg.norm(grad) < constants[
- Constants.LARGE_GRADIENT_FACTOR
- ] * np.linalg.norm(grad_alt):
- n_alt_models = 0
- if n_alt_models >= 3:
- try:
- framework.models.reset_models()
- except np.linalg.LinAlgError:
- status = ExitStatus.LINALG_ERROR
- break
- n_alt_models = 0
- # Update the Lagrange multipliers.
- framework.set_multipliers(framework.x_best + step)
- # Check whether the resolution should be enhanced.
- try:
- k_new, dist_new = framework.get_index_to_remove()
- except np.linalg.LinAlgError:
- status = ExitStatus.LINALG_ERROR
- break
- improve_geometry = (
- ill_conditioned
- or ratio <= constants[Constants.LOW_RATIO]
- and dist_new
- > max(
- framework.radius,
- constants[Constants.RESOLUTION_FACTOR]
- * framework.resolution,
- )
- )
- enhance_resolution = (
- radius_save <= framework.resolution
- and ratio <= constants[Constants.LOW_RATIO]
- and not improve_geometry
- )
- else:
- # When increasing the penalty parameter, the best point so far
- # may change. In this case, we restart the iteration.
- enhance_resolution = False
- improve_geometry = False
- # Reduce the resolution if necessary.
- if enhance_resolution:
- if framework.resolution <= options[Options.RHOEND]:
- success = True
- status = ExitStatus.RADIUS_SUCCESS
- break
- framework.enhance_resolution(options)
- framework.decrease_penalty()
- if verbose:
- maxcv_val = pb.maxcv(
- framework.x_best, framework.cub_best, framework.ceq_best
- )
- _print_step(
- f"New trust-region radius: {framework.resolution}",
- pb,
- pb.build_x(framework.x_best),
- framework.fun_best,
- maxcv_val,
- pb.n_eval,
- n_iter,
- )
- print()
- # Improve the geometry of the interpolation set if necessary.
- if improve_geometry:
- try:
- step = framework.get_geometry_step(k_new, options)
- except np.linalg.LinAlgError:
- status = ExitStatus.LINALG_ERROR
- break
- # Evaluate the objective and constraint functions.
- try:
- fun_val, cub_val, ceq_val = _eval(pb, framework, step, options)
- except TargetSuccess:
- status = ExitStatus.TARGET_SUCCESS
- success = True
- break
- except FeasibleSuccess:
- status = ExitStatus.FEASIBLE_SUCCESS
- success = True
- break
- except CallbackSuccess:
- status = ExitStatus.CALLBACK_SUCCESS
- success = True
- break
- except MaxEvalError:
- status = ExitStatus.MAX_EVAL_WARNING
- break
- # Update the interpolation set.
- try:
- framework.models.update_interpolation(
- k_new,
- framework.x_best + step,
- fun_val,
- cub_val,
- ceq_val,
- )
- except np.linalg.LinAlgError:
- status = ExitStatus.LINALG_ERROR
- break
- framework.set_best_index()
- return _build_result(
- pb,
- framework.penalty,
- success,
- status,
- n_iter,
- options,
- )
- def _get_bounds(bounds, n):
- """
- Uniformize the bounds.
- """
- if bounds is None:
- return Bounds(np.full(n, -np.inf), np.full(n, np.inf))
- elif isinstance(bounds, Bounds):
- if bounds.lb.shape != (n,) or bounds.ub.shape != (n,):
- raise ValueError(f"The bounds must have {n} elements.")
- return Bounds(bounds.lb, bounds.ub)
- elif hasattr(bounds, "__len__"):
- bounds = np.asarray(bounds)
- if bounds.shape != (n, 2):
- raise ValueError(
- "The shape of the bounds is not compatible with "
- "the number of variables."
- )
- return Bounds(bounds[:, 0], bounds[:, 1])
- else:
- raise TypeError(
- "The bounds must be an instance of "
- "scipy.optimize.Bounds or an array-like object."
- )
- def _get_constraints(constraints):
- """
- Extract the linear and nonlinear constraints.
- """
- if isinstance(constraints, dict) or not hasattr(constraints, "__len__"):
- constraints = (constraints,)
- # Extract the linear and nonlinear constraints.
- linear_constraints = []
- nonlinear_constraints = []
- for constraint in constraints:
- if isinstance(constraint, LinearConstraint):
- lb = exact_1d_array(
- constraint.lb,
- "The lower bound of the linear constraints must be a vector.",
- )
- ub = exact_1d_array(
- constraint.ub,
- "The upper bound of the linear constraints must be a vector.",
- )
- linear_constraints.append(
- LinearConstraint(
- constraint.A,
- *np.broadcast_arrays(lb, ub),
- )
- )
- elif isinstance(constraint, NonlinearConstraint):
- lb = exact_1d_array(
- constraint.lb,
- "The lower bound of the "
- "nonlinear constraints must be a "
- "vector.",
- )
- ub = exact_1d_array(
- constraint.ub,
- "The upper bound of the "
- "nonlinear constraints must be a "
- "vector.",
- )
- nonlinear_constraints.append(
- NonlinearConstraint(
- constraint.fun,
- *np.broadcast_arrays(lb, ub),
- )
- )
- elif isinstance(constraint, dict):
- if "type" not in constraint or constraint["type"] not in (
- "eq",
- "ineq",
- ):
- raise ValueError('The constraint type must be "eq" or "ineq".')
- if "fun" not in constraint or not callable(constraint["fun"]):
- raise ValueError("The constraint function must be callable.")
- nonlinear_constraints.append(
- {
- "fun": constraint["fun"],
- "type": constraint["type"],
- "args": constraint.get("args", ()),
- }
- )
- else:
- raise TypeError(
- "The constraints must be instances of "
- "scipy.optimize.LinearConstraint, "
- "scipy.optimize.NonlinearConstraint, or dict."
- )
- return linear_constraints, nonlinear_constraints
- def _set_default_options(options, n):
- """
- Set the default options.
- """
- if Options.RHOBEG in options and options[Options.RHOBEG] <= 0.0:
- raise ValueError("The initial trust-region radius must be positive.")
- if Options.RHOEND in options and options[Options.RHOEND] < 0.0:
- raise ValueError("The final trust-region radius must be nonnegative.")
- if Options.RHOBEG in options and Options.RHOEND in options:
- if options[Options.RHOBEG] < options[Options.RHOEND]:
- raise ValueError(
- "The initial trust-region radius must be greater "
- "than or equal to the final trust-region radius."
- )
- elif Options.RHOBEG in options:
- options[Options.RHOEND.value] = np.min(
- [
- DEFAULT_OPTIONS[Options.RHOEND],
- options[Options.RHOBEG],
- ]
- )
- elif Options.RHOEND in options:
- options[Options.RHOBEG.value] = np.max(
- [
- DEFAULT_OPTIONS[Options.RHOBEG],
- options[Options.RHOEND],
- ]
- )
- else:
- options[Options.RHOBEG.value] = DEFAULT_OPTIONS[Options.RHOBEG]
- options[Options.RHOEND.value] = DEFAULT_OPTIONS[Options.RHOEND]
- options[Options.RHOBEG.value] = float(options[Options.RHOBEG])
- options[Options.RHOEND.value] = float(options[Options.RHOEND])
- if Options.NPT in options and options[Options.NPT] <= 0:
- raise ValueError("The number of interpolation points must be "
- "positive.")
- if (
- Options.NPT in options
- and options[Options.NPT] > ((n + 1) * (n + 2)) // 2
- ):
- raise ValueError(
- f"The number of interpolation points must be at most "
- f"{((n + 1) * (n + 2)) // 2}."
- )
- options.setdefault(Options.NPT.value, DEFAULT_OPTIONS[Options.NPT](n))
- options[Options.NPT.value] = int(options[Options.NPT])
- if Options.MAX_EVAL in options and options[Options.MAX_EVAL] <= 0:
- raise ValueError(
- "The maximum number of function evaluations must be positive."
- )
- options.setdefault(
- Options.MAX_EVAL.value,
- np.max(
- [
- DEFAULT_OPTIONS[Options.MAX_EVAL](n),
- options[Options.NPT] + 1,
- ]
- ),
- )
- options[Options.MAX_EVAL.value] = int(options[Options.MAX_EVAL])
- if Options.MAX_ITER in options and options[Options.MAX_ITER] <= 0:
- raise ValueError("The maximum number of iterations must be positive.")
- options.setdefault(
- Options.MAX_ITER.value,
- DEFAULT_OPTIONS[Options.MAX_ITER](n),
- )
- options[Options.MAX_ITER.value] = int(options[Options.MAX_ITER])
- options.setdefault(Options.TARGET.value, DEFAULT_OPTIONS[Options.TARGET])
- options[Options.TARGET.value] = float(options[Options.TARGET])
- options.setdefault(
- Options.FEASIBILITY_TOL.value,
- DEFAULT_OPTIONS[Options.FEASIBILITY_TOL],
- )
- options[Options.FEASIBILITY_TOL.value] = float(
- options[Options.FEASIBILITY_TOL]
- )
- options.setdefault(Options.VERBOSE.value, DEFAULT_OPTIONS[Options.VERBOSE])
- options[Options.VERBOSE.value] = bool(options[Options.VERBOSE])
- options.setdefault(Options.SCALE.value, DEFAULT_OPTIONS[Options.SCALE])
- options[Options.SCALE.value] = bool(options[Options.SCALE])
- options.setdefault(
- Options.FILTER_SIZE.value,
- DEFAULT_OPTIONS[Options.FILTER_SIZE],
- )
- options[Options.FILTER_SIZE.value] = int(options[Options.FILTER_SIZE])
- options.setdefault(
- Options.STORE_HISTORY.value,
- DEFAULT_OPTIONS[Options.STORE_HISTORY],
- )
- options[Options.STORE_HISTORY.value] = bool(options[Options.STORE_HISTORY])
- options.setdefault(
- Options.HISTORY_SIZE.value,
- DEFAULT_OPTIONS[Options.HISTORY_SIZE],
- )
- options[Options.HISTORY_SIZE.value] = int(options[Options.HISTORY_SIZE])
- options.setdefault(Options.DEBUG.value, DEFAULT_OPTIONS[Options.DEBUG])
- options[Options.DEBUG.value] = bool(options[Options.DEBUG])
- # Check whether they are any unknown options.
- for key in options:
- if key not in Options.__members__.values():
- warnings.warn(f"Unknown option: {key}.", RuntimeWarning, 3)
- def _set_default_constants(**kwargs):
- """
- Set the default constants.
- """
- constants = dict(kwargs)
- constants.setdefault(
- Constants.DECREASE_RADIUS_FACTOR.value,
- DEFAULT_CONSTANTS[Constants.DECREASE_RADIUS_FACTOR],
- )
- constants[Constants.DECREASE_RADIUS_FACTOR.value] = float(
- constants[Constants.DECREASE_RADIUS_FACTOR]
- )
- if (
- constants[Constants.DECREASE_RADIUS_FACTOR] <= 0.0
- or constants[Constants.DECREASE_RADIUS_FACTOR] >= 1.0
- ):
- raise ValueError(
- "The constant decrease_radius_factor must be in the interval "
- "(0, 1)."
- )
- constants.setdefault(
- Constants.INCREASE_RADIUS_THRESHOLD.value,
- DEFAULT_CONSTANTS[Constants.INCREASE_RADIUS_THRESHOLD],
- )
- constants[Constants.INCREASE_RADIUS_THRESHOLD.value] = float(
- constants[Constants.INCREASE_RADIUS_THRESHOLD]
- )
- if constants[Constants.INCREASE_RADIUS_THRESHOLD] <= 1.0:
- raise ValueError(
- "The constant increase_radius_threshold must be greater than 1."
- )
- if (
- Constants.INCREASE_RADIUS_FACTOR in constants
- and constants[Constants.INCREASE_RADIUS_FACTOR] <= 1.0
- ):
- raise ValueError(
- "The constant increase_radius_factor must be greater than 1."
- )
- if (
- Constants.DECREASE_RADIUS_THRESHOLD in constants
- and constants[Constants.DECREASE_RADIUS_THRESHOLD] <= 1.0
- ):
- raise ValueError(
- "The constant decrease_radius_threshold must be greater than 1."
- )
- if (
- Constants.INCREASE_RADIUS_FACTOR in constants
- and Constants.DECREASE_RADIUS_THRESHOLD in constants
- ):
- if (
- constants[Constants.DECREASE_RADIUS_THRESHOLD]
- >= constants[Constants.INCREASE_RADIUS_FACTOR]
- ):
- raise ValueError(
- "The constant decrease_radius_threshold must be "
- "less than increase_radius_factor."
- )
- elif Constants.INCREASE_RADIUS_FACTOR in constants:
- constants[Constants.DECREASE_RADIUS_THRESHOLD.value] = np.min(
- [
- DEFAULT_CONSTANTS[Constants.DECREASE_RADIUS_THRESHOLD],
- 0.5 * (1.0 + constants[Constants.INCREASE_RADIUS_FACTOR]),
- ]
- )
- elif Constants.DECREASE_RADIUS_THRESHOLD in constants:
- constants[Constants.INCREASE_RADIUS_FACTOR.value] = np.max(
- [
- DEFAULT_CONSTANTS[Constants.INCREASE_RADIUS_FACTOR],
- 2.0 * constants[Constants.DECREASE_RADIUS_THRESHOLD],
- ]
- )
- else:
- constants[Constants.INCREASE_RADIUS_FACTOR.value] = DEFAULT_CONSTANTS[
- Constants.INCREASE_RADIUS_FACTOR
- ]
- constants[Constants.DECREASE_RADIUS_THRESHOLD.value] = (
- DEFAULT_CONSTANTS[Constants.DECREASE_RADIUS_THRESHOLD])
- constants.setdefault(
- Constants.DECREASE_RESOLUTION_FACTOR.value,
- DEFAULT_CONSTANTS[Constants.DECREASE_RESOLUTION_FACTOR],
- )
- constants[Constants.DECREASE_RESOLUTION_FACTOR.value] = float(
- constants[Constants.DECREASE_RESOLUTION_FACTOR]
- )
- if (
- constants[Constants.DECREASE_RESOLUTION_FACTOR] <= 0.0
- or constants[Constants.DECREASE_RESOLUTION_FACTOR] >= 1.0
- ):
- raise ValueError(
- "The constant decrease_resolution_factor must be in the interval "
- "(0, 1)."
- )
- if (
- Constants.LARGE_RESOLUTION_THRESHOLD in constants
- and constants[Constants.LARGE_RESOLUTION_THRESHOLD] <= 1.0
- ):
- raise ValueError(
- "The constant large_resolution_threshold must be greater than 1."
- )
- if (
- Constants.MODERATE_RESOLUTION_THRESHOLD in constants
- and constants[Constants.MODERATE_RESOLUTION_THRESHOLD] <= 1.0
- ):
- raise ValueError(
- "The constant moderate_resolution_threshold must be greater than "
- "1."
- )
- if (
- Constants.LARGE_RESOLUTION_THRESHOLD in constants
- and Constants.MODERATE_RESOLUTION_THRESHOLD in constants
- ):
- if (
- constants[Constants.MODERATE_RESOLUTION_THRESHOLD]
- > constants[Constants.LARGE_RESOLUTION_THRESHOLD]
- ):
- raise ValueError(
- "The constant moderate_resolution_threshold "
- "must be at most large_resolution_threshold."
- )
- elif Constants.LARGE_RESOLUTION_THRESHOLD in constants:
- constants[Constants.MODERATE_RESOLUTION_THRESHOLD.value] = np.min(
- [
- DEFAULT_CONSTANTS[Constants.MODERATE_RESOLUTION_THRESHOLD],
- constants[Constants.LARGE_RESOLUTION_THRESHOLD],
- ]
- )
- elif Constants.MODERATE_RESOLUTION_THRESHOLD in constants:
- constants[Constants.LARGE_RESOLUTION_THRESHOLD.value] = np.max(
- [
- DEFAULT_CONSTANTS[Constants.LARGE_RESOLUTION_THRESHOLD],
- constants[Constants.MODERATE_RESOLUTION_THRESHOLD],
- ]
- )
- else:
- constants[Constants.LARGE_RESOLUTION_THRESHOLD.value] = (
- DEFAULT_CONSTANTS[Constants.LARGE_RESOLUTION_THRESHOLD]
- )
- constants[Constants.MODERATE_RESOLUTION_THRESHOLD.value] = (
- DEFAULT_CONSTANTS[Constants.MODERATE_RESOLUTION_THRESHOLD]
- )
- if Constants.LOW_RATIO in constants and (
- constants[Constants.LOW_RATIO] <= 0.0
- or constants[Constants.LOW_RATIO] >= 1.0
- ):
- raise ValueError(
- "The constant low_ratio must be in the interval (0, 1)."
- )
- if Constants.HIGH_RATIO in constants and (
- constants[Constants.HIGH_RATIO] <= 0.0
- or constants[Constants.HIGH_RATIO] >= 1.0
- ):
- raise ValueError(
- "The constant high_ratio must be in the interval (0, 1)."
- )
- if Constants.LOW_RATIO in constants and Constants.HIGH_RATIO in constants:
- if constants[Constants.LOW_RATIO] > constants[Constants.HIGH_RATIO]:
- raise ValueError(
- "The constant low_ratio must be at most high_ratio."
- )
- elif Constants.LOW_RATIO in constants:
- constants[Constants.HIGH_RATIO.value] = np.max(
- [
- DEFAULT_CONSTANTS[Constants.HIGH_RATIO],
- constants[Constants.LOW_RATIO],
- ]
- )
- elif Constants.HIGH_RATIO in constants:
- constants[Constants.LOW_RATIO.value] = np.min(
- [
- DEFAULT_CONSTANTS[Constants.LOW_RATIO],
- constants[Constants.HIGH_RATIO],
- ]
- )
- else:
- constants[Constants.LOW_RATIO.value] = DEFAULT_CONSTANTS[
- Constants.LOW_RATIO
- ]
- constants[Constants.HIGH_RATIO.value] = DEFAULT_CONSTANTS[
- Constants.HIGH_RATIO
- ]
- constants.setdefault(
- Constants.VERY_LOW_RATIO.value,
- DEFAULT_CONSTANTS[Constants.VERY_LOW_RATIO],
- )
- constants[Constants.VERY_LOW_RATIO.value] = float(
- constants[Constants.VERY_LOW_RATIO]
- )
- if (
- constants[Constants.VERY_LOW_RATIO] <= 0.0
- or constants[Constants.VERY_LOW_RATIO] >= 1.0
- ):
- raise ValueError(
- "The constant very_low_ratio must be in the interval (0, 1)."
- )
- if (
- Constants.PENALTY_INCREASE_THRESHOLD in constants
- and constants[Constants.PENALTY_INCREASE_THRESHOLD] < 1.0
- ):
- raise ValueError(
- "The constant penalty_increase_threshold must be "
- "greater than or equal to 1."
- )
- if (
- Constants.PENALTY_INCREASE_FACTOR in constants
- and constants[Constants.PENALTY_INCREASE_FACTOR] <= 1.0
- ):
- raise ValueError(
- "The constant penalty_increase_factor must be greater than 1."
- )
- if (
- Constants.PENALTY_INCREASE_THRESHOLD in constants
- and Constants.PENALTY_INCREASE_FACTOR in constants
- ):
- if (
- constants[Constants.PENALTY_INCREASE_FACTOR]
- < constants[Constants.PENALTY_INCREASE_THRESHOLD]
- ):
- raise ValueError(
- "The constant penalty_increase_factor must be "
- "greater than or equal to "
- "penalty_increase_threshold."
- )
- elif Constants.PENALTY_INCREASE_THRESHOLD in constants:
- constants[Constants.PENALTY_INCREASE_FACTOR.value] = np.max(
- [
- DEFAULT_CONSTANTS[Constants.PENALTY_INCREASE_FACTOR],
- constants[Constants.PENALTY_INCREASE_THRESHOLD],
- ]
- )
- elif Constants.PENALTY_INCREASE_FACTOR in constants:
- constants[Constants.PENALTY_INCREASE_THRESHOLD.value] = np.min(
- [
- DEFAULT_CONSTANTS[Constants.PENALTY_INCREASE_THRESHOLD],
- constants[Constants.PENALTY_INCREASE_FACTOR],
- ]
- )
- else:
- constants[Constants.PENALTY_INCREASE_THRESHOLD.value] = (
- DEFAULT_CONSTANTS[Constants.PENALTY_INCREASE_THRESHOLD]
- )
- constants[Constants.PENALTY_INCREASE_FACTOR.value] = DEFAULT_CONSTANTS[
- Constants.PENALTY_INCREASE_FACTOR
- ]
- constants.setdefault(
- Constants.SHORT_STEP_THRESHOLD.value,
- DEFAULT_CONSTANTS[Constants.SHORT_STEP_THRESHOLD],
- )
- constants[Constants.SHORT_STEP_THRESHOLD.value] = float(
- constants[Constants.SHORT_STEP_THRESHOLD]
- )
- if (
- constants[Constants.SHORT_STEP_THRESHOLD] <= 0.0
- or constants[Constants.SHORT_STEP_THRESHOLD] >= 1.0
- ):
- raise ValueError(
- "The constant short_step_threshold must be in the interval (0, 1)."
- )
- constants.setdefault(
- Constants.LOW_RADIUS_FACTOR.value,
- DEFAULT_CONSTANTS[Constants.LOW_RADIUS_FACTOR],
- )
- constants[Constants.LOW_RADIUS_FACTOR.value] = float(
- constants[Constants.LOW_RADIUS_FACTOR]
- )
- if (
- constants[Constants.LOW_RADIUS_FACTOR] <= 0.0
- or constants[Constants.LOW_RADIUS_FACTOR] >= 1.0
- ):
- raise ValueError(
- "The constant low_radius_factor must be in the interval (0, 1)."
- )
- constants.setdefault(
- Constants.BYRD_OMOJOKUN_FACTOR.value,
- DEFAULT_CONSTANTS[Constants.BYRD_OMOJOKUN_FACTOR],
- )
- constants[Constants.BYRD_OMOJOKUN_FACTOR.value] = float(
- constants[Constants.BYRD_OMOJOKUN_FACTOR]
- )
- if (
- constants[Constants.BYRD_OMOJOKUN_FACTOR] <= 0.0
- or constants[Constants.BYRD_OMOJOKUN_FACTOR] >= 1.0
- ):
- raise ValueError(
- "The constant byrd_omojokun_factor must be in the interval (0, 1)."
- )
- constants.setdefault(
- Constants.THRESHOLD_RATIO_CONSTRAINTS.value,
- DEFAULT_CONSTANTS[Constants.THRESHOLD_RATIO_CONSTRAINTS],
- )
- constants[Constants.THRESHOLD_RATIO_CONSTRAINTS.value] = float(
- constants[Constants.THRESHOLD_RATIO_CONSTRAINTS]
- )
- if constants[Constants.THRESHOLD_RATIO_CONSTRAINTS] <= 1.0:
- raise ValueError(
- "The constant threshold_ratio_constraints must be greater than 1."
- )
- constants.setdefault(
- Constants.LARGE_SHIFT_FACTOR.value,
- DEFAULT_CONSTANTS[Constants.LARGE_SHIFT_FACTOR],
- )
- constants[Constants.LARGE_SHIFT_FACTOR.value] = float(
- constants[Constants.LARGE_SHIFT_FACTOR]
- )
- if constants[Constants.LARGE_SHIFT_FACTOR] < 0.0:
- raise ValueError("The constant large_shift_factor must be "
- "nonnegative.")
- constants.setdefault(
- Constants.LARGE_GRADIENT_FACTOR.value,
- DEFAULT_CONSTANTS[Constants.LARGE_GRADIENT_FACTOR],
- )
- constants[Constants.LARGE_GRADIENT_FACTOR.value] = float(
- constants[Constants.LARGE_GRADIENT_FACTOR]
- )
- if constants[Constants.LARGE_GRADIENT_FACTOR] <= 1.0:
- raise ValueError(
- "The constant large_gradient_factor must be greater than 1."
- )
- constants.setdefault(
- Constants.RESOLUTION_FACTOR.value,
- DEFAULT_CONSTANTS[Constants.RESOLUTION_FACTOR],
- )
- constants[Constants.RESOLUTION_FACTOR.value] = float(
- constants[Constants.RESOLUTION_FACTOR]
- )
- if constants[Constants.RESOLUTION_FACTOR] <= 1.0:
- raise ValueError(
- "The constant resolution_factor must be greater than 1."
- )
- constants.setdefault(
- Constants.IMPROVE_TCG.value,
- DEFAULT_CONSTANTS[Constants.IMPROVE_TCG],
- )
- constants[Constants.IMPROVE_TCG.value] = bool(
- constants[Constants.IMPROVE_TCG]
- )
- # Check whether they are any unknown options.
- for key in kwargs:
- if key not in Constants.__members__.values():
- warnings.warn(f"Unknown constant: {key}.", RuntimeWarning, 3)
- return constants
- def _eval(pb, framework, step, options):
- """
- Evaluate the objective and constraint functions.
- """
- if pb.n_eval >= options[Options.MAX_EVAL]:
- raise MaxEvalError
- x_eval = framework.x_best + step
- fun_val, cub_val, ceq_val = pb(x_eval, framework.penalty)
- r_val = pb.maxcv(x_eval, cub_val, ceq_val)
- if (
- fun_val <= options[Options.TARGET]
- and r_val <= options[Options.FEASIBILITY_TOL]
- ):
- raise TargetSuccess
- if pb.is_feasibility and r_val <= options[Options.FEASIBILITY_TOL]:
- raise FeasibleSuccess
- return fun_val, cub_val, ceq_val
- def _build_result(pb, penalty, success, status, n_iter, options):
- """
- Build the result of the optimization process.
- """
- # Build the result.
- x, fun, maxcv = pb.best_eval(penalty)
- success = success and np.isfinite(fun) and np.isfinite(maxcv)
- if status not in [ExitStatus.TARGET_SUCCESS, ExitStatus.FEASIBLE_SUCCESS]:
- success = success and maxcv <= options[Options.FEASIBILITY_TOL]
- result = OptimizeResult()
- result.message = {
- ExitStatus.RADIUS_SUCCESS: "The lower bound for the trust-region "
- "radius has been reached",
- ExitStatus.TARGET_SUCCESS: "The target objective function value has "
- "been reached",
- ExitStatus.FIXED_SUCCESS: "All variables are fixed by the bound "
- "constraints",
- ExitStatus.CALLBACK_SUCCESS: "The callback requested to stop the "
- "optimization procedure",
- ExitStatus.FEASIBLE_SUCCESS: "The feasibility problem received has "
- "been solved successfully",
- ExitStatus.MAX_EVAL_WARNING: "The maximum number of function "
- "evaluations has been exceeded",
- ExitStatus.MAX_ITER_WARNING: "The maximum number of iterations has "
- "been exceeded",
- ExitStatus.INFEASIBLE_ERROR: "The bound constraints are infeasible",
- ExitStatus.LINALG_ERROR: "A linear algebra error occurred",
- }.get(status, "Unknown exit status")
- result.success = success
- result.status = status.value
- result.x = pb.build_x(x)
- result.fun = fun
- result.maxcv = maxcv
- result.nfev = pb.n_eval
- result.nit = n_iter
- if options[Options.STORE_HISTORY]:
- result.fun_history = pb.fun_history
- result.maxcv_history = pb.maxcv_history
- # Print the result if requested.
- if options[Options.VERBOSE]:
- _print_step(
- result.message,
- pb,
- result.x,
- result.fun,
- result.maxcv,
- result.nfev,
- result.nit,
- )
- return result
- def _print_step(message, pb, x, fun_val, r_val, n_eval, n_iter):
- """
- Print information about the current state of the optimization process.
- """
- print()
- print(f"{message}.")
- print(f"Number of function evaluations: {n_eval}.")
- print(f"Number of iterations: {n_iter}.")
- if not pb.is_feasibility:
- print(f"Least value of {pb.fun_name}: {fun_val}.")
- print(f"Maximum constraint violation: {r_val}.")
- with np.printoptions(**PRINT_OPTIONS):
- print(f"Corresponding point: {x}.")
|