API reference
Generated from docstrings via mkdocstrings.
Everything documented here is importable directly from the top-level ipax
package; the fully-qualified module path is shown for reference only.
Entry point
The single function most users call. It resolves derivatives, selects a linear
solver, runs the interior-point loop, and returns a Result.
Solve problem starting from x0.
The interior-point path handles bounds, (nonlinear) inequalities, and
equalities through the condensed normal-equations / regularized-saddle route
(Breedveld 2017, eq. 18) with an injected :class:LinearSolver — the dense
reference solver, the matrix-free Krylov solver (linsolve="krylov",
auto-selected at scale), or the sparse-direct solver (linsolve="sparse",
which assembles and factors the saddle for bound/equality problems with an
assemblable Hessian) — monotone μ, fraction-to-boundary, the filter
line-search, and feasibility restoration.
With options.scaling="gradient-based" the objective and each
constraint are rescaled once at x0 so their gradients are O(1)
(IPOPT nlp_scaling_method); the returned x, objective, and
multipliers are reported in the original problem's scale, while
kkt_error/constraint_violation remain the scaled-space metrics that
drove convergence.
A full :class:~ipax.options.ScalingOptions object can be supplied for a
custom max_gradient.
warm_start, if given, seeds the interior-point iterate with supplied
slacks/multipliers instead of the default μ-complementarity start — pass a
:class:~ipax.result.WarmStart (e.g. WarmStart.from_result(prev)) and
the corresponding x0 when re-solving a perturbed problem. The values are
in the original problem's units; with scaling enabled they are rescaled
internally to match the scaled subproblem.
callback, if given, is invoked once per iteration with an
:class:~ipax.result.IterationInfo snapshot (the iteration record plus the
current primal/dual iterate). Returning a truthy value stops the solve
early with :attr:~ipax.result.Status.STOPPED. Progress logging is emitted
through the "ipax" logger; options.verbose (1=info, 2=debug) opts in
to a console handler, while applications may instead configure that logger
themselves.
Termination is configured by options: the single-iteration
:class:~ipax.options.OptimalityConditionOptions (reports
Status.OPTIMAL), the multi-iteration
:class:~ipax.options.AcceptableStoppingOptions (reports
Status.ACCEPTABLE), and the top-level max_iter / max_time limits.
Problem definition
Define a model by subclassing Problem, or use one
of the ready-made adapters for callable-based, quadratic, and linear programs.
Bases: ABC
User-facing NLP definition.
Subclass and implement :attr:n_vars and :meth:objective; everything else
is optional. When an optional derivative method is left unimplemented (it
raises :class:NotImplementedError), the solver fills it by the precedence
analytic → autodiff → finite-difference for gradients/Jacobians and
analytic → autodiff-HVP → L-BFGS for the Lagrangian Hessian. Constraint
values (:meth:eq_constraints, :meth:ineq_constraints) have no fallback:
the constraint exists only if its value method is implemented.
Nonlinear and linear constraints are declared through separate methods so
the constant-data linear blocks (:meth:linear_eq, :meth:linear_ineq) can
be assembled once with no Hessian contribution — a real performance lever at
RT scale. Jacobians and the Hessian may be returned either as dense
Array-API arrays or as :class:~ipax.backend.operators.LinearOperator
instances, so a structured or matrix-free model never needs to materialize a
matrix.
All arrays are read in whatever Array-API backend x carries; the class
never imports a concrete array library. See
:class:~ipax.problem.function.FunctionProblem,
:class:~ipax.problem.function.QuadraticProblem, and
:class:~ipax.problem.function.LinearProblem for ready-made implementations.
n_vars
abstractmethod
property
Number of optimization variables.
bounds()
(x_L, x_U); None entries → ∓∞. Default: unbounded.
objective(x)
abstractmethod
Scalar objective f(x).
gradient(x)
∇f(x). Raising signals 'derive it for me'.
eq_constraints(x)
c(x) = 0.
eq_jacobian(x)
∇c(x). Optional → autodiff → finite-diff.
ineq_constraints(x)
g(x) ≤ 0.
ineq_jacobian(x)
∇g(x). Optional → autodiff → finite-diff.
linear_eq()
(A_eq, b_eq) for A_eq x = b_eq.
linear_ineq()
(A_ineq, l, u) for l ≤ A_ineq x ≤ u (l/u may be ∓inf).
lagrangian_hessian(x, y_eq, y_ineq, sigma=1.0)
W = σ∇²f + Σ y_eq·∇²c + Σ y_ineq·∇²g, as matrix or matvec operator.
Follows the IPOPT eval_h convention so a structured/analytic Hessian
can be supplied as a cheap matvec without ever materializing W.
Bases: Problem
Assemble a :class:Problem from callables without subclassing.
A thin adapter for the common case where the model is already expressed as
functions of x. Pass at least n_vars and objective; every other
derivative is optional and, when omitted, supplied by the resolver's
autodiff → finite-difference fallback (the Hessian falls back to L-BFGS).
Whether a callable was supplied is recorded explicitly, so the resolver
treats a missing callable exactly as a hand-written subclass that leaves the
corresponding method unimplemented.
Examples:
>>> import numpy as np
>>> from ipax import FunctionProblem, solve
>>> problem = FunctionProblem(
... n_vars=2,
... objective=lambda x: np.sum(x**2),
... gradient=lambda x: 2 * x,
... )
>>> result = solve(problem, np.ones(2))
__init__(n_vars, objective, *, gradient=None, bounds=(None, None), eq_constraints=None, eq_jacobian=None, ineq_constraints=None, ineq_jacobian=None, linear_eq=None, linear_ineq=None, lagrangian_hessian=None)
Build a problem from objective and optional derivative callables.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_vars
|
int
|
Number of optimization variables (must be non-negative). |
required |
objective
|
Callable[[Array], Scalar]
|
Scalar objective |
required |
gradient
|
Callable[[Array], Array] | None
|
|
None
|
bounds
|
tuple[Array | None, Array | None]
|
|
(None, None)
|
eq_constraints
|
Callable[[Array], Array] | None
|
Nonlinear equalities |
None
|
eq_jacobian
|
Callable[[Array], Array] | None
|
Nonlinear equalities |
None
|
ineq_constraints
|
Callable[[Array], Array] | None
|
Nonlinear inequalities |
None
|
ineq_jacobian
|
Callable[[Array], Array] | None
|
Nonlinear inequalities |
None
|
linear_eq
|
tuple[Array | LinearOperator, Array] | None
|
|
None
|
linear_ineq
|
tuple[Array | LinearOperator, Array, Array] | None
|
|
None
|
lagrangian_hessian
|
Callable[[Array, Array, Array, Scalar], Array | LinearOperator] | None
|
|
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Bases: Problem
min 0.5 xᵀ Q x + cᵀ x with a constant Hessian Q.
The objective gradient (Q x + c) and Lagrangian Hessian (Q) are
exact, so no derivative fallback is engaged. The problem is unconstrained on
its own; compose it with bounds or constraints through a subclass, or use
:class:FunctionProblem when constraints are also needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Q
|
Array | LinearOperator
|
Symmetric |
required |
c
|
Array
|
Rank-1 linear term of length |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Bases: Problem
min cᵀ x (a linear program) with a zero Hessian.
Bounds and linear constraints are the only way to make the problem
well-posed, so they are accepted directly in the constructor. The gradient
is the constant c and the Hessian is identically zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
c
|
Array
|
Rank-1 cost vector of length |
required |
bounds
|
tuple[Array | None, Array | None]
|
|
(None, None)
|
linear_eq
|
tuple[Array | LinearOperator, Array] | None
|
|
None
|
linear_ineq
|
tuple[Array | LinearOperator, Array, Array] | None
|
|
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Options
Options is the top-level configuration object; the remaining dataclasses
configure individual subsystems and are attached to it. All are frozen and
validated on construction.
Top-level solver options.
scaling accepts either a full :class:ScalingOptions object or the
shorthand strings "none" and "gradient-based"; corrections
likewise accepts a :class:CorrectionsOptions or one of "none",
"mehrotra", "gondzio".
Termination has four sources, checked in priority order:
optimality(:class:OptimalityConditionOptions) — single-iteration test reporting :attr:Status.OPTIMAL.acceptable(:class:AcceptableStoppingOptions) — multi-iteration test reporting :attr:Status.ACCEPTABLE(disabled by default).max_iter— iteration cap, reports :attr:Status.MAX_ITER.max_time— wall-clock cap in seconds, reports :attr:Status.MAX_TIME(Nonedisables it).
__post_init__()
Validate limits and normalize shorthand option values.
Single-iteration optimality test → :attr:Status.OPTIMAL.
The solve is declared optimal as soon as every enabled condition below
holds in a single iteration. None disables a condition; at least one must
remain enabled. The defaults reproduce the classic scaled-KKT test
max(dual_inf, constr_viol, compl) ≤ 1e-8.
f_tol— absolute objective magnitude|f_k| ≤ f_tol(a level test, so it can hold on iteration 0). Off by default.f_rel_change_tol— relative objective change|f_k-f_{k-1}| / max(1, |f_k|, |f_{k-1}|)(needs a previous iterate, so it never holds on iteration 0). Off by default.dual_inf_tol— scaled dual-infeasibility component.constr_viol_tol— scaled primal-infeasibility (constraint violation).compl_inf_tol— scaled complementarity component.
The minimum of the enabled KKT-residual tolerances (dual_inf_tol,
constr_viol_tol, compl_inf_tol) also sets the barrier μ floor via
:attr:kkt_tol.
kkt_tol
property
Representative KKT tolerance feeding the barrier μ floor.
The smallest enabled KKT-residual tolerance, or 1e-8 when only
f_tol is set.
Multi-iteration acceptable-level termination → :attr:Status.ACCEPTABLE.
Mirrors :class:OptimalityConditionOptions but the conditions must hold for
n_iter consecutive iterations before the solve stops. "Acceptable"
means what the caller is willing to accept — e.g. stopping once the
objective and primal feasibility have settled even though a
dual-infeasibility-dominated residual will not reduce further. Every
tolerance is None (disabled) by default, so the whole mechanism is off
unless explicitly configured.
The fields match :class:OptimalityConditionOptions (f_tol,
f_rel_change_tol, dual_inf_tol, constr_viol_tol,
compl_inf_tol) plus n_iter.
NLP auto-scaling (IPOPT nlp_scaling_method; Wächter & Biegler 2006 §3.8).
"gradient-based" rescales the objective and each constraint once at the
starting point so their gradients have an ∞-norm of at most max_gradient
(factor min(1, max_gradient / ‖∇·‖∞)); variables/bounds are left
unscaled. "none" (default) disables scaling and the solver sees the
problem verbatim.
Higher-order predictor–corrector step corrections.
The default "none" uses the single Newton/centering direction. The two
optional higher-order schemes reuse the iteration's KKT operator for extra
complementarity-target solves:
"mehrotra"— Mehrotra (1992) predictor–corrector (adaptive centering + second-order complementarity correction, one extra solve)."gondzio"— Gondzio (1996) multiple centrality corrections (up togondzio_max_correctionsextra solves pulling the complementarity products into the box[γ μ, μ/γ]withγ = gondzio_gamma).
gondzio_gamma sets the centrality neighborhood and
gondzio_max_corrections caps its extra solves per outer iteration.
Matrix-free solver tolerances (§5.2).
__post_init__()
Validate runtime values as well as static Literal hints.
Results and diagnostics
What a solve returns, plus the per-iteration snapshot passed to a callback.
Solution and diagnostics returned by :func:ipax.solve.
success is true for strict :attr:Status.OPTIMAL and the explicitly
enabled :attr:Status.ACCEPTABLE exit. The status itself records the stopping
condition; component KKT residuals provide the corresponding diagnostics.
Bases: Enum
Termination status.
is_success
property
Whether this status represents a usable converged point.
Scaled components whose maximum is the solver's KKT error.
error
property
Return the aggregate scaled KKT infinity norm.
One row of the iteration log.
objective uses the original problem's units. mu, theta, and
kkt_error and its three component residuals remain scaled-space
algorithm diagnostics when gradient-based problem scaling is enabled.
Read-only snapshot handed to a user iteration callback each iteration.
record is the same :class:IterationRecord appended to
Result.history; the remaining fields expose the current primal/dual
iterate so callbacks can plot progress or compute a custom stopping rule.
Optional fields are None when the corresponding block is absent (no
slacks/inequalities, no equalities, no bounds). Values use the original
problem's units and array namespace. Treat arrays as read-only and copy
before mutating.
A callback returning a truthy value requests early termination; the solve
then finishes with :attr:Status.STOPPED.
Warm starting
Initial slacks/multipliers seeding a solve.
Pair with the starting x0 passed to :func:ipax.solve: the primal point
comes from x0 while these dual quantities (and optionally the inequality
slacks) seed the interior-point iterate instead of the default
μ-complementarity start. Any field left None falls back to the standard
initialization, so a partial warm start is fine. The slacks and the bound /
inequality multipliers are floored to stay strictly interior; equality
multipliers (free sign) pass through unchanged.
:meth:from_result reuses a previous :class:Result directly — the typical
case when re-solving a perturbed problem (e.g. an RT re-plan). Slacks are not
stored on :class:Result, so they are recomputed from feasibility unless
supplied explicitly. Values are in the original problem's units; when
gradient-based scaling is enabled the solver rescales them internally.
from_result(result, *, s=None)
classmethod
Seed a solve from a prior :class:Result's multipliers.
Linear-algebra extension points
Advanced surface for supplying structured/matrix-free operators or custom solve strategies. The core only ever sees these protocol types — see invariants #1–#4.
Bases: ABC
Backend-agnostic linear map exposing matvec and optional adjoints.
shape
abstractmethod
property
matvec(v)
abstractmethod
Compute A @ v.
rmatvec(v)
Compute A.T @ v. Defaults to unsupported.
matmat(V)
Compute A @ V by applying matvec column-wise.
diagonal(like=None)
Return the main diagonal as a rank-1 array.
Optional capability: only operators that can produce it cheaply (i.e.
without n matvecs) override this. The Jacobi preconditioner of the
matrix-free Krylov solver consumes it when available and silently falls
back to no preconditioning otherwise, so a missing diagonal is never an
error. Operators that do not carry an array namespace internally may use
like as a dtype/backend template.
to_coo(like=None)
Emit COO structure (rows, cols, values, shape) as Array-API vectors.
Optional capability and the mechanism behind invariant #4: operators with
explicit structure override this so a sparse-direct adapter can assemble
and factor the actual matrix while the core stays backend-agnostic (the
core emits index/value vectors; the adapter builds the sparse object).
Duplicate (row, col) entries are summed by the adapter, so block
assemblies may emit overlapping diagonals freely.
Matrix-free / product operators (LBFGSOperator, MatrixFreeJacobian,
:class:Composite) cannot express their structure as triplets and leave
the default, which signals that the sparse-direct route is unavailable for
the system they appear in. Operators with no stored array (e.g.
:class:Identity) may use like as a dtype/backend template.
gram_diagonal(weights)
Return diag(Aᵀ diag(weights) A) — the weighted column energies.
Entry k is Σ_i weights_i · A_ik². This is the inequality term
diag(∇gᵀ Σ_s ∇g) of the condensed Newton matrix (Breedveld 2017,
eq. 18), needed to build the Jacobi preconditioner matrix-free. Optional,
like :meth:diagonal: only operators that can produce it without n
matvecs override it.
Bases: Protocol
Solve K x = rhs for a KKT or condensed operator K.
factor(K)
Prepare/factor the operator.
solve(rhs)
Return x such that K x = rhs to the configured tolerance.
Bases: RuntimeError
Backend-neutral numerical failure from a linear solve.
The IPM regularization loop catches this type and retries with a larger primal regularization. Configuration errors, unsupported features, shape errors, and user/operator callback bugs should propagate as their original exceptions instead of being reclassified as numerical trouble.
Backend introspection
Return the common Array-API namespace for arrays.
Thin wrapper over array_api_compat.array_namespace so the rest of the
core has a single import site. Raises if the arrays disagree on backend.
Probe xp for optional Array-API features and adapter availability.
Records presence of xp.linalg and which functions exist, whether a
sparse adapter is registered for this namespace, device info, and autodiff
support. Missing standard pieces (triangular solve, lstsq) are filled by
labeled helpers elsewhere in backend/linalg.