Source code for slsqp_jax.results
"""SLSQP-specific termination codes.
This module defines :class:`RESULTS`, a subclass of
``optimistix.RESULTS`` that adds finer-grained classification for the
SLSQP solver's failure modes. The base class' members
(``successful``, ``nonlinear_max_steps_reached``,
``nonlinear_divergence``, ``nonfinite``, ...) are inherited; the
SLSQP-specific failure cases below replace what previously all mapped
to ``optx.RESULTS.nonlinear_divergence``.
.. note::
Cross-class equality between members of ``optx.RESULTS`` and
``slsqp_jax.RESULTS`` raises ``ValueError`` per equinox's
``Enumeration`` semantics::
sol.result == optx.RESULTS.successful # raises ValueError
sol.result == slsqp_jax.RESULTS.successful # works
Use :func:`is_successful` if you want a comparison that is robust
to the parent/subclass distinction, or call
``slsqp_jax.RESULTS.promote(parent_result)`` to lift an
``optx.RESULTS`` value into this enumeration.
"""
from __future__ import annotations
from typing import Any
import optimistix as optx
[docs]
class RESULTS(optx.RESULTS): # type: ignore[misc] # ty: ignore[subclass-of-final-class]
"""SLSQP-specific termination codes.
Subclasses :class:`optimistix.RESULTS` and adds finer
classifications for the failure modes that the upstream solver
lumps into :attr:`optimistix.RESULTS.nonlinear_divergence`.
.. note::
:class:`equinox.Enumeration` reports its concrete subclasses
as ``@final`` via the metaclass, but optimistix itself does
the same trick (``optx.RESULTS`` subclasses ``lx.RESULTS``)
and the docstring example for ``Enumeration.promote`` shows
the pattern is supported. ``ty: ignore[subclass-of-final-class]``
suppresses the false positive.
"""
merit_stagnation = (
"The L1 merit function did not improve over the patience "
"window (max_steps // 10 iterations). The iterate is "
"primally feasible but stationarity could not be driven below "
"rtol; this typically signals L-BFGS multiplier-recovery "
"noise, a degenerate vertex, or an ill-conditioned problem. "
"Try loosening rtol, switching to use_exact_hvp_in_qp=True, "
"or checking constraint LICQ."
)
line_search_failure = (
"Consecutive line-search failures exceeded "
"2 * ls_failure_patience. The QP direction is not a descent "
"direction for the L1 merit even after escalating L-BFGS "
"soft-then-identity resets. Try MinresQLPSolver, exact HVPs, "
"or a larger initial penalty parameter."
)
iterate_blowup = (
"The L1 merit grew by more than divergence_factor times the "
"best-seen merit (or returned NaN/Inf) for divergence_patience "
"consecutive steps. The returned iterate is the best-merit "
"iterate seen so far; subsequent steps were diverging."
)
infeasible = (
"Termination at a primally infeasible iterate. Either the "
"constraints are infeasible or the active-set machinery could "
"not satisfy them within atol. Inspect c_eq / c_ineq at the "
"returned iterate and consider relaxing constraints or "
"providing a feasible initial point."
)
qp_subproblem_failure = (
"Consecutive QP-subproblem failures exceeded "
"2 * qp_failure_patience. Even after L-BFGS soft-then-identity "
"resets, the inner QP solver could not produce a usable "
"descent direction. Likely a rank-deficient equality "
"Jacobian or extreme conditioning."
)
[docs]
def is_successful(result: Any) -> bool:
"""Return ``True`` if ``result`` represents successful convergence.
Robust to both :class:`optimistix.RESULTS` and
:class:`slsqp_jax.RESULTS` inputs by promoting the value before
comparison. Useful for downstream code that may receive results
from either enumeration::
from slsqp_jax import is_successful
if is_successful(sol.result):
...
Args:
result: A ``RESULTS`` member from either ``optx.RESULTS`` or
its :class:`RESULTS` subclass.
Returns:
``True`` iff the result equals :attr:`RESULTS.successful`.
"""
if isinstance(result, RESULTS):
promoted = result
else:
promoted = RESULTS.promote(result)
return bool(promoted == RESULTS.successful)