| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240 |
- import warnings
- import numpy as np
- from scipy.optimize import lsq_linear
- from .models import Models, Quadratic
- from .settings import Options, Constants
- from .subsolvers import (
- cauchy_geometry,
- spider_geometry,
- normal_byrd_omojokun,
- tangential_byrd_omojokun,
- constrained_tangential_byrd_omojokun,
- )
- from .subsolvers.optim import qr_tangential_byrd_omojokun
- from .utils import get_arrays_tol
- TINY = np.finfo(float).tiny
- EPS = np.finfo(float).eps
- class TrustRegion:
- """
- Trust-region framework.
- """
- def __init__(self, pb, options, constants):
- """
- Initialize the trust-region framework.
- Parameters
- ----------
- pb : `cobyqa.problem.Problem`
- Problem to solve.
- options : dict
- Options of the solver.
- constants : dict
- Constants of the solver.
- Raises
- ------
- `cobyqa.utils.MaxEvalError`
- If the maximum number of evaluations is reached.
- `cobyqa.utils.TargetSuccess`
- If a nearly feasible point has been found with an objective
- function value below the target.
- `cobyqa.utils.FeasibleSuccess`
- If a feasible point has been found for a feasibility problem.
- `numpy.linalg.LinAlgError`
- If the initial interpolation system is ill-defined.
- """
- # Set the initial penalty parameter.
- self._penalty = 0.0
- # Initialize the models.
- self._pb = pb
- self._models = Models(self._pb, options, self.penalty)
- self._constants = constants
- # Set the index of the best interpolation point.
- self._best_index = 0
- self.set_best_index()
- # Set the initial Lagrange multipliers.
- self._lm_linear_ub = np.zeros(self.m_linear_ub)
- self._lm_linear_eq = np.zeros(self.m_linear_eq)
- self._lm_nonlinear_ub = np.zeros(self.m_nonlinear_ub)
- self._lm_nonlinear_eq = np.zeros(self.m_nonlinear_eq)
- self.set_multipliers(self.x_best)
- # Set the initial trust-region radius and the resolution.
- self._resolution = options[Options.RHOBEG]
- self._radius = self.resolution
- @property
- def n(self):
- """
- Number of variables.
- Returns
- -------
- int
- Number of variables.
- """
- return self._pb.n
- @property
- def m_linear_ub(self):
- """
- Number of linear inequality constraints.
- Returns
- -------
- int
- Number of linear inequality constraints.
- """
- return self._pb.m_linear_ub
- @property
- def m_linear_eq(self):
- """
- Number of linear equality constraints.
- Returns
- -------
- int
- Number of linear equality constraints.
- """
- return self._pb.m_linear_eq
- @property
- def m_nonlinear_ub(self):
- """
- Number of nonlinear inequality constraints.
- Returns
- -------
- int
- Number of nonlinear inequality constraints.
- """
- return self._pb.m_nonlinear_ub
- @property
- def m_nonlinear_eq(self):
- """
- Number of nonlinear equality constraints.
- Returns
- -------
- int
- Number of nonlinear equality constraints.
- """
- return self._pb.m_nonlinear_eq
- @property
- def radius(self):
- """
- Trust-region radius.
- Returns
- -------
- float
- Trust-region radius.
- """
- return self._radius
- @radius.setter
- def radius(self, radius):
- """
- Set the trust-region radius.
- Parameters
- ----------
- radius : float
- New trust-region radius.
- """
- self._radius = radius
- if (
- self.radius
- <= self._constants[Constants.DECREASE_RADIUS_THRESHOLD]
- * self.resolution
- ):
- self._radius = self.resolution
- @property
- def resolution(self):
- """
- Resolution of the trust-region framework.
- The resolution is a lower bound on the trust-region radius.
- Returns
- -------
- float
- Resolution of the trust-region framework.
- """
- return self._resolution
- @resolution.setter
- def resolution(self, resolution):
- """
- Set the resolution of the trust-region framework.
- Parameters
- ----------
- resolution : float
- New resolution of the trust-region framework.
- """
- self._resolution = resolution
- @property
- def penalty(self):
- """
- Penalty parameter.
- Returns
- -------
- float
- Penalty parameter.
- """
- return self._penalty
- @property
- def models(self):
- """
- Models of the objective function and constraints.
- Returns
- -------
- `cobyqa.models.Models`
- Models of the objective function and constraints.
- """
- return self._models
- @property
- def best_index(self):
- """
- Index of the best interpolation point.
- Returns
- -------
- int
- Index of the best interpolation point.
- """
- return self._best_index
- @property
- def x_best(self):
- """
- Best interpolation point.
- Its value is interpreted as relative to the origin, not the base point.
- Returns
- -------
- `numpy.ndarray`
- Best interpolation point.
- """
- return self.models.interpolation.point(self.best_index)
- @property
- def fun_best(self):
- """
- Value of the objective function at `x_best`.
- Returns
- -------
- float
- Value of the objective function at `x_best`.
- """
- return self.models.fun_val[self.best_index]
- @property
- def cub_best(self):
- """
- Values of the nonlinear inequality constraints at `x_best`.
- Returns
- -------
- `numpy.ndarray`, shape (m_nonlinear_ub,)
- Values of the nonlinear inequality constraints at `x_best`.
- """
- return self.models.cub_val[self.best_index, :]
- @property
- def ceq_best(self):
- """
- Values of the nonlinear equality constraints at `x_best`.
- Returns
- -------
- `numpy.ndarray`, shape (m_nonlinear_eq,)
- Values of the nonlinear equality constraints at `x_best`.
- """
- return self.models.ceq_val[self.best_index, :]
- def lag_model(self, x):
- """
- Evaluate the Lagrangian model at a given point.
- Parameters
- ----------
- x : `numpy.ndarray`, shape (n,)
- Point at which the Lagrangian model is evaluated.
- Returns
- -------
- float
- Value of the Lagrangian model at `x`.
- """
- return (
- self.models.fun(x)
- + self._lm_linear_ub
- @ (self._pb.linear.a_ub @ x - self._pb.linear.b_ub)
- + self._lm_linear_eq
- @ (self._pb.linear.a_eq @ x - self._pb.linear.b_eq)
- + self._lm_nonlinear_ub @ self.models.cub(x)
- + self._lm_nonlinear_eq @ self.models.ceq(x)
- )
- def lag_model_grad(self, x):
- """
- Evaluate the gradient of the Lagrangian model at a given point.
- Parameters
- ----------
- x : `numpy.ndarray`, shape (n,)
- Point at which the gradient of the Lagrangian model is evaluated.
- Returns
- -------
- `numpy.ndarray`, shape (n,)
- Gradient of the Lagrangian model at `x`.
- """
- return (
- self.models.fun_grad(x)
- + self._lm_linear_ub @ self._pb.linear.a_ub
- + self._lm_linear_eq @ self._pb.linear.a_eq
- + self._lm_nonlinear_ub @ self.models.cub_grad(x)
- + self._lm_nonlinear_eq @ self.models.ceq_grad(x)
- )
- def lag_model_hess(self):
- """
- Evaluate the Hessian matrix of the Lagrangian model at a given point.
- Returns
- -------
- `numpy.ndarray`, shape (n, n)
- Hessian matrix of the Lagrangian model at `x`.
- """
- hess = self.models.fun_hess()
- if self.m_nonlinear_ub > 0:
- hess += self._lm_nonlinear_ub @ self.models.cub_hess()
- if self.m_nonlinear_eq > 0:
- hess += self._lm_nonlinear_eq @ self.models.ceq_hess()
- return hess
- def lag_model_hess_prod(self, v):
- """
- Evaluate the right product of the Hessian matrix of the Lagrangian
- model with a given vector.
- Parameters
- ----------
- v : `numpy.ndarray`, shape (n,)
- Vector with which the Hessian matrix of the Lagrangian model is
- multiplied from the right.
- Returns
- -------
- `numpy.ndarray`, shape (n,)
- Right product of the Hessian matrix of the Lagrangian model with
- `v`.
- """
- return (
- self.models.fun_hess_prod(v)
- + self._lm_nonlinear_ub @ self.models.cub_hess_prod(v)
- + self._lm_nonlinear_eq @ self.models.ceq_hess_prod(v)
- )
- def lag_model_curv(self, v):
- """
- Evaluate the curvature of the Lagrangian model along a given direction.
- Parameters
- ----------
- v : `numpy.ndarray`, shape (n,)
- Direction along which the curvature of the Lagrangian model is
- evaluated.
- Returns
- -------
- float
- Curvature of the Lagrangian model along `v`.
- """
- return (
- self.models.fun_curv(v)
- + self._lm_nonlinear_ub @ self.models.cub_curv(v)
- + self._lm_nonlinear_eq @ self.models.ceq_curv(v)
- )
- def sqp_fun(self, step):
- """
- Evaluate the objective function of the SQP subproblem.
- Parameters
- ----------
- step : `numpy.ndarray`, shape (n,)
- Step along which the objective function of the SQP subproblem is
- evaluated.
- Returns
- -------
- float
- Value of the objective function of the SQP subproblem along `step`.
- """
- return step @ (
- self.models.fun_grad(self.x_best)
- + 0.5 * self.lag_model_hess_prod(step)
- )
- def sqp_cub(self, step):
- """
- Evaluate the linearization of the nonlinear inequality constraints.
- Parameters
- ----------
- step : `numpy.ndarray`, shape (n,)
- Step along which the linearization of the nonlinear inequality
- constraints is evaluated.
- Returns
- -------
- `numpy.ndarray`, shape (m_nonlinear_ub,)
- Value of the linearization of the nonlinear inequality constraints
- along `step`.
- """
- return (
- self.models.cub(self.x_best)
- + self.models.cub_grad(self.x_best) @ step
- )
- def sqp_ceq(self, step):
- """
- Evaluate the linearization of the nonlinear equality constraints.
- Parameters
- ----------
- step : `numpy.ndarray`, shape (n,)
- Step along which the linearization of the nonlinear equality
- constraints is evaluated.
- Returns
- -------
- `numpy.ndarray`, shape (m_nonlinear_ub,)
- Value of the linearization of the nonlinear equality constraints
- along `step`.
- """
- return (
- self.models.ceq(self.x_best)
- + self.models.ceq_grad(self.x_best) @ step
- )
- def merit(self, x, fun_val=None, cub_val=None, ceq_val=None):
- """
- Evaluate the merit function at a given point.
- Parameters
- ----------
- x : `numpy.ndarray`, shape (n,)
- Point at which the merit function is evaluated.
- fun_val : float, optional
- Value of the objective function at `x`. If not provided, the
- objective function is evaluated at `x`.
- cub_val : `numpy.ndarray`, shape (m_nonlinear_ub,), optional
- Values of the nonlinear inequality constraints. If not provided,
- the nonlinear inequality constraints are evaluated at `x`.
- ceq_val : `numpy.ndarray`, shape (m_nonlinear_eq,), optional
- Values of the nonlinear equality constraints. If not provided,
- the nonlinear equality constraints are evaluated at `x`.
- Returns
- -------
- float
- Value of the merit function at `x`.
- """
- if fun_val is None or cub_val is None or ceq_val is None:
- fun_val, cub_val, ceq_val = self._pb(x, self.penalty)
- m_val = fun_val
- if self._penalty > 0.0:
- c_val = self._pb.violation(x, cub_val=cub_val, ceq_val=ceq_val)
- if np.count_nonzero(c_val):
- m_val += self._penalty * np.linalg.norm(c_val)
- return m_val
- def get_constraint_linearizations(self, x):
- """
- Get the linearizations of the constraints at a given point.
- Parameters
- ----------
- x : `numpy.ndarray`, shape (n,)
- Point at which the linearizations of the constraints are evaluated.
- Returns
- -------
- `numpy.ndarray`, shape (m_linear_ub + m_nonlinear_ub, n)
- Left-hand side matrix of the linearized inequality constraints.
- `numpy.ndarray`, shape (m_linear_ub + m_nonlinear_ub,)
- Right-hand side vector of the linearized inequality constraints.
- `numpy.ndarray`, shape (m_linear_eq + m_nonlinear_eq, n)
- Left-hand side matrix of the linearized equality constraints.
- `numpy.ndarray`, shape (m_linear_eq + m_nonlinear_eq,)
- Right-hand side vector of the linearized equality constraints.
- """
- aub = np.block(
- [
- [self._pb.linear.a_ub],
- [self.models.cub_grad(x)],
- ]
- )
- bub = np.block(
- [
- self._pb.linear.b_ub - self._pb.linear.a_ub @ x,
- -self.models.cub(x),
- ]
- )
- aeq = np.block(
- [
- [self._pb.linear.a_eq],
- [self.models.ceq_grad(x)],
- ]
- )
- beq = np.block(
- [
- self._pb.linear.b_eq - self._pb.linear.a_eq @ x,
- -self.models.ceq(x),
- ]
- )
- return aub, bub, aeq, beq
- def get_trust_region_step(self, options):
- """
- Get the trust-region step.
- The trust-region step is computed by solving the derivative-free
- trust-region SQP subproblem using a Byrd-Omojokun composite-step
- approach. For more details, see Section 5.2.3 of [1]_.
- Parameters
- ----------
- options : dict
- Options of the solver.
- Returns
- -------
- `numpy.ndarray`, shape (n,)
- Normal step.
- `numpy.ndarray`, shape (n,)
- Tangential step.
- References
- ----------
- .. [1] 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.
- """
- # Evaluate the linearizations of the constraints.
- aub, bub, aeq, beq = self.get_constraint_linearizations(self.x_best)
- xl = self._pb.bounds.xl - self.x_best
- xu = self._pb.bounds.xu - self.x_best
- # Evaluate the normal step.
- radius = self._constants[Constants.BYRD_OMOJOKUN_FACTOR] * self.radius
- normal_step = normal_byrd_omojokun(
- aub,
- bub,
- aeq,
- beq,
- xl,
- xu,
- radius,
- options[Options.DEBUG],
- **self._constants,
- )
- if options[Options.DEBUG]:
- tol = get_arrays_tol(xl, xu)
- if (np.any(normal_step + tol < xl)
- or np.any(xu < normal_step - tol)):
- warnings.warn(
- "the normal step does not respect the bound constraint.",
- RuntimeWarning,
- 2,
- )
- if np.linalg.norm(normal_step) > 1.1 * radius:
- warnings.warn(
- "the normal step does not respect the trust-region "
- "constraint.",
- RuntimeWarning,
- 2,
- )
- # Evaluate the tangential step.
- radius = np.sqrt(self.radius**2.0 - normal_step @ normal_step)
- xl -= normal_step
- xu -= normal_step
- bub = np.maximum(bub - aub @ normal_step, 0.0)
- g_best = self.models.fun_grad(self.x_best) + self.lag_model_hess_prod(
- normal_step
- )
- if self._pb.type in ["unconstrained", "bound-constrained"]:
- tangential_step = tangential_byrd_omojokun(
- g_best,
- self.lag_model_hess_prod,
- xl,
- xu,
- radius,
- options[Options.DEBUG],
- **self._constants,
- )
- else:
- tangential_step = constrained_tangential_byrd_omojokun(
- g_best,
- self.lag_model_hess_prod,
- xl,
- xu,
- aub,
- bub,
- aeq,
- radius,
- options["debug"],
- **self._constants,
- )
- if options[Options.DEBUG]:
- tol = get_arrays_tol(xl, xu)
- if np.any(tangential_step + tol < xl) or np.any(
- xu < tangential_step - tol
- ):
- warnings.warn(
- "The tangential step does not respect the bound "
- "constraints.",
- RuntimeWarning,
- 2,
- )
- if (
- np.linalg.norm(normal_step + tangential_step)
- > 1.1 * np.sqrt(2.0) * self.radius
- ):
- warnings.warn(
- "The trial step does not respect the trust-region "
- "constraint.",
- RuntimeWarning,
- 2,
- )
- return normal_step, tangential_step
- def get_geometry_step(self, k_new, options):
- """
- Get the geometry-improving step.
- Three different geometry-improving steps are computed and the best one
- is returned. For more details, see Section 5.2.7 of [1]_.
- Parameters
- ----------
- k_new : int
- Index of the interpolation point to be modified.
- options : dict
- Options of the solver.
- Returns
- -------
- `numpy.ndarray`, shape (n,)
- Geometry-improving step.
- Raises
- ------
- `numpy.linalg.LinAlgError`
- If the computation of a determinant fails.
- References
- ----------
- .. [1] 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.
- """
- if options[Options.DEBUG]:
- assert (
- k_new != self.best_index
- ), "The index `k_new` must be different from the best index."
- # Build the k_new-th Lagrange polynomial.
- coord_vec = np.squeeze(np.eye(1, self.models.npt, k_new))
- lag = Quadratic(
- self.models.interpolation,
- coord_vec,
- options[Options.DEBUG],
- )
- g_lag = lag.grad(self.x_best, self.models.interpolation)
- # Compute a simple constrained Cauchy step.
- xl = self._pb.bounds.xl - self.x_best
- xu = self._pb.bounds.xu - self.x_best
- step = cauchy_geometry(
- 0.0,
- g_lag,
- lambda v: lag.curv(v, self.models.interpolation),
- xl,
- xu,
- self.radius,
- options[Options.DEBUG],
- )
- sigma = self.models.determinants(self.x_best + step, k_new)
- # Compute the solution on the straight lines joining the interpolation
- # points to the k-th one, and choose it if it provides a larger value
- # of the determinant of the interpolation system in absolute value.
- xpt = (
- self.models.interpolation.xpt
- - self.models.interpolation.xpt[:, self.best_index, np.newaxis]
- )
- xpt[:, [0, self.best_index]] = xpt[:, [self.best_index, 0]]
- step_alt = spider_geometry(
- 0.0,
- g_lag,
- lambda v: lag.curv(v, self.models.interpolation),
- xpt[:, 1:],
- xl,
- xu,
- self.radius,
- options[Options.DEBUG],
- )
- sigma_alt = self.models.determinants(self.x_best + step_alt, k_new)
- if abs(sigma_alt) > abs(sigma):
- step = step_alt
- sigma = sigma_alt
- # Compute a Cauchy step on the tangent space of the active constraints.
- if self._pb.type in [
- "linearly constrained",
- "nonlinearly constrained",
- ]:
- aub, bub, aeq, beq = (
- self.get_constraint_linearizations(self.x_best))
- tol_bd = get_arrays_tol(xl, xu)
- tol_ub = get_arrays_tol(bub)
- free_xl = xl <= -tol_bd
- free_xu = xu >= tol_bd
- free_ub = bub >= tol_ub
- # Compute the Cauchy step.
- n_act, q = qr_tangential_byrd_omojokun(
- aub,
- aeq,
- free_xl,
- free_xu,
- free_ub,
- )
- g_lag_proj = q[:, n_act:] @ (q[:, n_act:].T @ g_lag)
- norm_g_lag_proj = np.linalg.norm(g_lag_proj)
- if 0 < n_act < self._pb.n and norm_g_lag_proj > TINY * self.radius:
- step_alt = (self.radius / norm_g_lag_proj) * g_lag_proj
- if lag.curv(step_alt, self.models.interpolation) < 0.0:
- step_alt = -step_alt
- # Evaluate the constraint violation at the Cauchy step.
- cbd = np.block([xl - step_alt, step_alt - xu])
- cub = aub @ step_alt - bub
- ceq = aeq @ step_alt - beq
- maxcv_val = max(
- np.max(array, initial=0.0)
- for array in [cbd, cub, np.abs(ceq)]
- )
- # Accept the new step if it is nearly feasible and do not
- # drastically worsen the determinant of the interpolation
- # system in absolute value.
- tol = np.max(np.abs(step_alt[~free_xl]), initial=0.0)
- tol = np.max(np.abs(step_alt[~free_xu]), initial=tol)
- tol = np.max(np.abs(aub[~free_ub, :] @ step_alt), initial=tol)
- tol = min(10.0 * tol, 1e-2 * np.linalg.norm(step_alt))
- if maxcv_val <= tol:
- sigma_alt = self.models.determinants(
- self.x_best + step_alt, k_new
- )
- if abs(sigma_alt) >= 0.1 * abs(sigma):
- step = np.clip(step_alt, xl, xu)
- if options[Options.DEBUG]:
- tol = get_arrays_tol(xl, xu)
- if np.any(step + tol < xl) or np.any(xu < step - tol):
- warnings.warn(
- "The geometry step does not respect the bound "
- "constraints.",
- RuntimeWarning,
- 2,
- )
- if np.linalg.norm(step) > 1.1 * self.radius:
- warnings.warn(
- "The geometry step does not respect the "
- "trust-region constraint.",
- RuntimeWarning,
- 2,
- )
- return step
- def get_second_order_correction_step(self, step, options):
- """
- Get the second-order correction step.
- Parameters
- ----------
- step : `numpy.ndarray`, shape (n,)
- Trust-region step.
- options : dict
- Options of the solver.
- Returns
- -------
- `numpy.ndarray`, shape (n,)
- Second-order correction step.
- """
- # Evaluate the linearizations of the constraints.
- aub, bub, aeq, beq = self.get_constraint_linearizations(self.x_best)
- xl = self._pb.bounds.xl - self.x_best
- xu = self._pb.bounds.xu - self.x_best
- radius = np.linalg.norm(step)
- soc_step = normal_byrd_omojokun(
- aub,
- bub,
- aeq,
- beq,
- xl,
- xu,
- radius,
- options[Options.DEBUG],
- **self._constants,
- )
- if options[Options.DEBUG]:
- tol = get_arrays_tol(xl, xu)
- if np.any(soc_step + tol < xl) or np.any(xu < soc_step - tol):
- warnings.warn(
- "The second-order correction step does not "
- "respect the bound constraints.",
- RuntimeWarning,
- 2,
- )
- if np.linalg.norm(soc_step) > 1.1 * radius:
- warnings.warn(
- "The second-order correction step does not "
- "respect the trust-region constraint.",
- RuntimeWarning,
- 2,
- )
- return soc_step
- def get_reduction_ratio(self, step, fun_val, cub_val, ceq_val):
- """
- Get the reduction ratio.
- Parameters
- ----------
- step : `numpy.ndarray`, shape (n,)
- Trust-region step.
- fun_val : float
- Objective function value at the trial point.
- cub_val : `numpy.ndarray`, shape (m_nonlinear_ub,)
- Nonlinear inequality constraint values at the trial point.
- ceq_val : `numpy.ndarray`, shape (m_nonlinear_eq,)
- Nonlinear equality constraint values at the trial point.
- Returns
- -------
- float
- Reduction ratio.
- """
- merit_old = self.merit(
- self.x_best,
- self.fun_best,
- self.cub_best,
- self.ceq_best,
- )
- merit_new = self.merit(self.x_best + step, fun_val, cub_val, ceq_val)
- merit_model_old = self.merit(
- self.x_best,
- 0.0,
- self.models.cub(self.x_best),
- self.models.ceq(self.x_best),
- )
- merit_model_new = self.merit(
- self.x_best + step,
- self.sqp_fun(step),
- self.sqp_cub(step),
- self.sqp_ceq(step),
- )
- if abs(merit_model_old - merit_model_new) > TINY * abs(
- merit_old - merit_new
- ):
- return (merit_old - merit_new) / abs(
- merit_model_old - merit_model_new
- )
- else:
- return -1.0
- def increase_penalty(self, step):
- """
- Increase the penalty parameter.
- Parameters
- ----------
- step : `numpy.ndarray`, shape (n,)
- Trust-region step.
- """
- aub, bub, aeq, beq = self.get_constraint_linearizations(self.x_best)
- viol_diff = max(
- np.linalg.norm(
- np.block(
- [
- np.maximum(0.0, -bub),
- beq,
- ]
- )
- )
- - np.linalg.norm(
- np.block(
- [
- np.maximum(0.0, aub @ step - bub),
- aeq @ step - beq,
- ]
- )
- ),
- 0.0,
- )
- sqp_val = self.sqp_fun(step)
- threshold = np.linalg.norm(
- np.block(
- [
- self._lm_linear_ub,
- self._lm_linear_eq,
- self._lm_nonlinear_ub,
- self._lm_nonlinear_eq,
- ]
- )
- )
- if abs(viol_diff) > TINY * abs(sqp_val):
- threshold = max(threshold, sqp_val / viol_diff)
- best_index_save = self.best_index
- if (
- self._penalty
- <= self._constants[Constants.PENALTY_INCREASE_THRESHOLD]
- * threshold
- ):
- self._penalty = max(
- self._constants[Constants.PENALTY_INCREASE_FACTOR] * threshold,
- 1.0,
- )
- self.set_best_index()
- return best_index_save == self.best_index
- def decrease_penalty(self):
- """
- Decrease the penalty parameter.
- """
- self._penalty = min(self._penalty, self._get_low_penalty())
- self.set_best_index()
- def set_best_index(self):
- """
- Set the index of the best point.
- """
- best_index = self.best_index
- m_best = self.merit(
- self.x_best,
- self.models.fun_val[best_index],
- self.models.cub_val[best_index, :],
- self.models.ceq_val[best_index, :],
- )
- r_best = self._pb.maxcv(
- self.x_best,
- self.models.cub_val[best_index, :],
- self.models.ceq_val[best_index, :],
- )
- tol = (
- 10.0
- * EPS
- * max(self.models.n, self.models.npt)
- * max(abs(m_best), 1.0)
- )
- for k in range(self.models.npt):
- if k != self.best_index:
- x_val = self.models.interpolation.point(k)
- m_val = self.merit(
- x_val,
- self.models.fun_val[k],
- self.models.cub_val[k, :],
- self.models.ceq_val[k, :],
- )
- r_val = self._pb.maxcv(
- x_val,
- self.models.cub_val[k, :],
- self.models.ceq_val[k, :],
- )
- if m_val < m_best or (m_val < m_best + tol and r_val < r_best):
- best_index = k
- m_best = m_val
- r_best = r_val
- self._best_index = best_index
- def get_index_to_remove(self, x_new=None):
- """
- Get the index of the interpolation point to remove.
- If `x_new` is not provided, the index returned should be used during
- the geometry-improvement phase. Otherwise, the index returned is the
- best index for included `x_new` in the interpolation set.
- Parameters
- ----------
- x_new : `numpy.ndarray`, shape (n,), optional
- New point to be included in the interpolation set.
- Returns
- -------
- int
- Index of the interpolation point to remove.
- float
- Distance between `x_best` and the removed point.
- Raises
- ------
- `numpy.linalg.LinAlgError`
- If the computation of a determinant fails.
- """
- dist_sq = np.sum(
- (
- self.models.interpolation.xpt
- - self.models.interpolation.xpt[:, self.best_index, np.newaxis]
- )
- ** 2.0,
- axis=0,
- )
- if x_new is None:
- sigma = 1.0
- weights = dist_sq
- else:
- sigma = self.models.determinants(x_new)
- weights = (
- np.maximum(
- 1.0,
- dist_sq
- / max(
- self._constants[Constants.LOW_RADIUS_FACTOR]
- * self.radius,
- self.resolution,
- )
- ** 2.0,
- )
- ** 3.0
- )
- weights[self.best_index] = -1.0 # do not remove the best point
- k_max = np.argmax(weights * np.abs(sigma))
- return k_max, np.sqrt(dist_sq[k_max])
- def update_radius(self, step, ratio):
- """
- Update the trust-region radius.
- Parameters
- ----------
- step : `numpy.ndarray`, shape (n,)
- Trust-region step.
- ratio : float
- Reduction ratio.
- """
- s_norm = np.linalg.norm(step)
- if ratio <= self._constants[Constants.LOW_RATIO]:
- self.radius *= self._constants[Constants.DECREASE_RADIUS_FACTOR]
- elif ratio <= self._constants[Constants.HIGH_RATIO]:
- self.radius = max(
- self._constants[Constants.DECREASE_RADIUS_FACTOR]
- * self.radius,
- s_norm,
- )
- else:
- self.radius = min(
- self._constants[Constants.INCREASE_RADIUS_FACTOR]
- * self.radius,
- max(
- self._constants[Constants.DECREASE_RADIUS_FACTOR]
- * self.radius,
- self._constants[Constants.INCREASE_RADIUS_THRESHOLD]
- * s_norm,
- ),
- )
- def enhance_resolution(self, options):
- """
- Enhance the resolution of the trust-region framework.
- Parameters
- ----------
- options : dict
- Options of the solver.
- """
- if (
- self._constants[Constants.LARGE_RESOLUTION_THRESHOLD]
- * options[Options.RHOEND]
- < self.resolution
- ):
- self.resolution *= self._constants[
- Constants.DECREASE_RESOLUTION_FACTOR
- ]
- elif (
- self._constants[Constants.MODERATE_RESOLUTION_THRESHOLD]
- * options[Options.RHOEND]
- < self.resolution
- ):
- self.resolution = np.sqrt(self.resolution
- * options[Options.RHOEND])
- else:
- self.resolution = options[Options.RHOEND]
- # Reduce the trust-region radius.
- self._radius = max(
- self._constants[Constants.DECREASE_RADIUS_FACTOR] * self._radius,
- self.resolution,
- )
- def shift_x_base(self, options):
- """
- Shift the base point to `x_best`.
- Parameters
- ----------
- options : dict
- Options of the solver.
- """
- self.models.shift_x_base(np.copy(self.x_best), options)
- def set_multipliers(self, x):
- """
- Set the Lagrange multipliers.
- This method computes and set the Lagrange multipliers of the linear and
- nonlinear constraints to be the QP multipliers.
- Parameters
- ----------
- x : `numpy.ndarray`, shape (n,)
- Point at which the Lagrange multipliers are computed.
- """
- # Build the constraints of the least-squares problem.
- incl_linear_ub = self._pb.linear.a_ub @ x >= self._pb.linear.b_ub
- incl_nonlinear_ub = self.cub_best >= 0.0
- incl_xl = self._pb.bounds.xl >= x
- incl_xu = self._pb.bounds.xu <= x
- m_linear_ub = np.count_nonzero(incl_linear_ub)
- m_nonlinear_ub = np.count_nonzero(incl_nonlinear_ub)
- m_xl = np.count_nonzero(incl_xl)
- m_xu = np.count_nonzero(incl_xu)
- if (
- m_linear_ub + m_nonlinear_ub + self.m_linear_eq
- + self.m_nonlinear_eq > 0
- ):
- identity = np.eye(self._pb.n)
- c_jac = np.r_[
- -identity[incl_xl, :],
- identity[incl_xu, :],
- self._pb.linear.a_ub[incl_linear_ub, :],
- self.models.cub_grad(x, incl_nonlinear_ub),
- self._pb.linear.a_eq,
- self.models.ceq_grad(x),
- ]
- # Solve the least-squares problem.
- g_best = self.models.fun_grad(x)
- xl_lm = np.full(c_jac.shape[0], -np.inf)
- xl_lm[: m_xl + m_xu + m_linear_ub + m_nonlinear_ub] = 0.0
- res = lsq_linear(
- c_jac.T,
- -g_best,
- bounds=(xl_lm, np.inf),
- method="bvls",
- )
- # Extract the Lagrange multipliers.
- self._lm_linear_ub[incl_linear_ub] = res.x[
- m_xl + m_xu:m_xl + m_xu + m_linear_ub
- ]
- self._lm_linear_ub[~incl_linear_ub] = 0.0
- self._lm_nonlinear_ub[incl_nonlinear_ub] = res.x[
- m_xl
- + m_xu
- + m_linear_ub:m_xl
- + m_xu
- + m_linear_ub
- + m_nonlinear_ub
- ]
- self._lm_nonlinear_ub[~incl_nonlinear_ub] = 0.0
- self._lm_linear_eq[:] = res.x[
- m_xl
- + m_xu
- + m_linear_ub
- + m_nonlinear_ub:m_xl
- + m_xu
- + m_linear_ub
- + m_nonlinear_ub
- + self.m_linear_eq
- ]
- self._lm_nonlinear_eq[:] = res.x[
- m_xl + m_xu + m_linear_ub + m_nonlinear_ub + self.m_linear_eq:
- ]
- def _get_low_penalty(self):
- r_val_ub = np.c_[
- (
- self.models.interpolation.x_base[np.newaxis, :]
- + self.models.interpolation.xpt.T
- )
- @ self._pb.linear.a_ub.T
- - self._pb.linear.b_ub[np.newaxis, :],
- self.models.cub_val,
- ]
- r_val_eq = (
- self.models.interpolation.x_base[np.newaxis, :]
- + self.models.interpolation.xpt.T
- ) @ self._pb.linear.a_eq.T - self._pb.linear.b_eq[np.newaxis, :]
- r_val_eq = np.block(
- [
- r_val_eq,
- -r_val_eq,
- self.models.ceq_val,
- -self.models.ceq_val,
- ]
- )
- r_val = np.block([r_val_ub, r_val_eq])
- c_min = np.nanmin(r_val, axis=0)
- c_max = np.nanmax(r_val, axis=0)
- indices = (
- c_min
- < self._constants[Constants.THRESHOLD_RATIO_CONSTRAINTS] * c_max
- )
- if np.any(indices):
- f_min = np.nanmin(self.models.fun_val)
- f_max = np.nanmax(self.models.fun_val)
- c_min_neg = np.minimum(0.0, c_min[indices])
- c_diff = np.min(c_max[indices] - c_min_neg)
- if c_diff > TINY * (f_max - f_min):
- penalty = (f_max - f_min) / c_diff
- else:
- penalty = np.inf
- else:
- penalty = 0.0
- return penalty
|