Skip to content

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 f(x).

required
gradient Callable[[Array], Array] | None

∇f(x). Derived by autodiff/finite-difference if omitted.

None
bounds tuple[Array | None, Array | None]

(x_L, x_U) variable bounds; None on either side means that side is unbounded. Defaults to fully unbounded.

(None, None)
eq_constraints Callable[[Array], Array] | None

Nonlinear equalities c(x) = 0 and their Jacobian ∇c(x). The Jacobian may be a dense array or a :class:~ipax.backend.operators.LinearOperator; it is derived if omitted.

None
eq_jacobian Callable[[Array], Array] | None

Nonlinear equalities c(x) = 0 and their Jacobian ∇c(x). The Jacobian may be a dense array or a :class:~ipax.backend.operators.LinearOperator; it is derived if omitted.

None
ineq_constraints Callable[[Array], Array] | None

Nonlinear inequalities g(x) ≤ 0 and their Jacobian ∇g(x), same conventions as the equality pair.

None
ineq_jacobian Callable[[Array], Array] | None

Nonlinear inequalities g(x) ≤ 0 and their Jacobian ∇g(x), same conventions as the equality pair.

None
linear_eq tuple[Array | LinearOperator, Array] | None

(A_eq, b_eq) declaring the constant-data equalities A_eq x = b_eq. Assembled once; carries no Hessian term.

None
linear_ineq tuple[Array | LinearOperator, Array, Array] | None

(A_ineq, l, u) declaring l ≤ A_ineq x ≤ u (l/u may be ∓inf).

None
lagrangian_hessian Callable[[Array, Array, Array, Scalar], Array | LinearOperator] | None

W(x, y_eq, y_ineq, σ) following the IPOPT eval_h convention. Returns a dense array or matvec operator; falls back to autodiff-HVP then L-BFGS when omitted.

None

Raises:

Type Description
ValueError

If n_vars is negative.

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 (n_vars, n_vars) Hessian, as a dense array or a :class:~ipax.backend.operators.LinearOperator (for a matrix-free quadratic). A non-unit σ is only supported for the dense form.

required
c Array

Rank-1 linear term of length n_vars.

required

Raises:

Type Description
ValueError

If c is not rank-1 or Q is not square of shape (n_vars, n_vars).

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 n_vars.

required
bounds tuple[Array | None, Array | None]

(x_L, x_U) variable bounds; None on either side means unbounded.

(None, None)
linear_eq tuple[Array | LinearOperator, Array] | None

(A_eq, b_eq) for A_eq x = b_eq.

None
linear_ineq tuple[Array | LinearOperator, Array, Array] | None

(A_ineq, l, u) for l ≤ A_ineq x ≤ u (l/u may be ∓inf).

None

Raises:

Type Description
ValueError

If c is not rank-1.

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 (None disables 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 to gondzio_max_corrections extra 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.

μ schedule and complementarity targets (Wächter & Biegler §3.1).

Filter line-search parameters (Wächter & Biegler §2–3).

Friedlander–Orban primal–dual regularization + Breedveld δ_w bumping (§4.4).

Limited-memory Hessian (compact, Powell-damped) — invariant of PD (§4.3).

Matrix-free solver tolerances (§5.2).

__post_init__()

Validate runtime values as well as static Literal hints.

Alternative step controller (Breedveld 2017 §2, eqs. 34–36).

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.

How each derivative was obtained (§3.3) — surfaced for transparency.

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.

Normalize a rank-2 dense array or existing operator.

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.

Pick a concrete linear solver from user preference and backend features.

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.

What the current namespace/device supports (§5.4).