Basin

Search documentation

Search the guides, solvers, and migration docs.

  • Guide
  • Visualizer
  • Benchmarks

Documentation

  • Overview
  • Getting started
  • Migrating from Argmin
  • Migrating to Basin 2.0
  • Migrating to Basin 3.0

Solvers

  • Choosing a solver
  • First-order
    • Gradient descent
    • Nonlinear CG
    • Stochastic gradient descent
    • Projected gradient descent
  • Quasi-Newton
    • BFGS
    • L-BFGS
    • L-BFGS-B
  • Derivative-free
    • Nelder–Mead
    • MADS
    • NEWUOA
    • BOBYQA
    • LINCOA
    • COBYLA
    • Solis–Wets
  • Trust-region
    • Trust-region Newton
  • Nonlinear least squares
    • Gauss–Newton
    • Levenberg–Marquardt
    • Levenberg–Marquardt QR
    • Bounded Levenberg–Marquardt
    • Trust-region reflective
  • Global and stochastic
    • DIRECT
    • Globalized bounded Nelder–Mead
    • Simulated annealing
    • CMA-ES
    • Bounded CMA-ES
    • Differential evolution
    • Particle swarm
    • Random search
    • Steady-state genetic algorithm
    • Basin-hopping
  • Memetic
    • CMA-ES with local search
    • Bounded CMA-ES with local search
    • DE with local search
    • Local-search chains
    • CMA-ES chains
    • Solis–Wets chains
  • General constrained
    • SLSQP
    • Barrier method
    • Augmented Lagrangian
  • Scalar minimization
    • Brent
    • Brent with derivatives
    • Golden-section search
  • Root finding
    • Bisection
    • Brent
    • Secant
    • Newton
    • Halley
    • TOMS 748
Navigation

Documentation

  • Overview
  • Getting started
  • Migrating from Argmin
  • Migrating to Basin 2.0
  • Migrating to Basin 3.0

Solvers

  • Choosing a solver
  • First-order
    • Gradient descent
    • Nonlinear CG
    • Stochastic gradient descent
    • Projected gradient descent
  • Quasi-Newton
    • BFGS
    • L-BFGS
    • L-BFGS-B
  • Derivative-free
    • Nelder–Mead
    • MADS
    • NEWUOA
    • BOBYQA
    • LINCOA
    • COBYLA
    • Solis–Wets
  • Trust-region
    • Trust-region Newton
  • Nonlinear least squares
    • Gauss–Newton
    • Levenberg–Marquardt
    • Levenberg–Marquardt QR
    • Bounded Levenberg–Marquardt
    • Trust-region reflective
  • Global and stochastic
    • DIRECT
    • Globalized bounded Nelder–Mead
    • Simulated annealing
    • CMA-ES
    • Bounded CMA-ES
    • Differential evolution
    • Particle swarm
    • Random search
    • Steady-state genetic algorithm
    • Basin-hopping
  • Memetic
    • CMA-ES with local search
    • Bounded CMA-ES with local search
    • DE with local search
    • Local-search chains
    • CMA-ES chains
    • Solis–Wets chains
  • General constrained
    • SLSQP
    • Barrier method
    • Augmented Lagrangian
  • Scalar minimization
    • Brent
    • Brent with derivatives
    • Golden-section search
  • Root finding
    • Bisection
    • Brent
    • Secant
    • Newton
    • Halley
    • TOMS 748

Augmented Lagrangian

AugmentedLagrangianMethod minimizes a scalar objective subject to linear equalities A x = b. It combines multiplier estimates and a quadratic penalty with a configurable unconstrained inner solver. The starting point may be infeasible, and inner iterates can move freely off the constraint surface.

API reference

PropertyRequirement
ObjectiveA differentiable scalar function, including off the constraint surface
EvaluationsCostFunction and Gradient, plus a compatible WarmStart inner
SearchLocal continuation through augmented-Lagrangian subproblems
ConstraintsA linear equality block
ParametersAll four dense backends with a compatible inner

When to use it

Use this wrapper when your constraints are linear equalities and you want to reuse an unconstrained solver. Multiplier estimates can reduce the need for an extremely large quadratic penalty. Basin follows a simplified LANCELOT-style outer loop from Nocedal and Wright (2006), §17.3, Algorithm 17.4.

The complete declaration must contain only a linear equality block. Bounds, inequalities, and nonlinear blocks are unsupported. For mixed constraint blocks, compare SLSQP. For linear inequalities, compare the barrier method. This is a local method, so a nonconvex objective can lead to different solutions from different starts.

How it works

For residual c(x) = A x - b, the surrogate is L_rho(x, lambda) = f(x) + lambda^T c(x) + (rho/2) * ||c(x)||^2. The wrapper initializes lambda to zero and rho to the initial penalty. Each outer iteration starts a fresh inner solve at the current point with those surrogate parameters held fixed.

If the returned equality residual norm decreases enough, the wrapper updates the multipliers with lambda = lambda + rho * c(x). Otherwise, it increases rho and keeps the multipliers. The surrogate gradient is grad f(x) + A^T (lambda + rho * c(x)); the outer method independently checks it at the returned point before accepting convergence.

The penalty is finite away from the constraint surface. Inner solves can therefore use ordinary backtracking or Wolfe line searches, and no Phase I feasibility search is needed. The objective and gradient must be defined at the infeasible points those solves explore.

Example

This quadratic has its unconstrained minimum at [2.0, 2.0]. The equality x[0] + x[1] = 2.0 moves the solution to [1.0, 1.0], with cost 2.0. The start [0.0, 0.0] violates the equality. The example checks both the returned point and the selected incumbent’s feasibility.

use basin::{
    AugmentedLagrangianMethod, Backtracking, Constrained, ConstraintSet,
    CostFunction, DenseMatrix, Executor, Gradient, GradientDescent,
    LinearBlock, TerminationCode, ViolationMeasure,
};
use std::convert::Infallible;

struct Quadratic;

impl CostFunction for Quadratic {
    type Param = Vec<f64>;
    type Output = f64;
    type Error = Infallible;

    fn cost(&self, x: &Self::Param) -> Result<f64, Self::Error> {
        Ok((x[0] - 2.0).powi(2) + (x[1] - 2.0).powi(2))
    }
}

impl Gradient for Quadratic {
    type Gradient = Vec<f64>;

    fn gradient(&self, x: &Self::Param) -> Result<Self::Gradient, Self::Error> {
        Ok(vec![2.0 * (x[0] - 2.0), 2.0 * (x[1] - 2.0)])
    }
}

fn main() {
    let constraints =
        ConstraintSet::new().with_linear_equalities(LinearBlock::new(
            DenseMatrix::from_row_slice(1, 2, &[1.0, 1.0]),
            vec![2.0],
        ));
    let problem = Constrained::new(Quadratic, constraints);
    let inner = GradientDescent::with_line_search(Backtracking::new())
        .with_absolute_gradient_tolerance(1e-8);
    let solver = AugmentedLagrangianMethod::with_inner_solver(inner);
    let result = Executor::from_start(problem, solver, vec![0.0, 0.0])
        .max_iter(50)
        .run()
        .unwrap();

    assert_eq!(result.report.code(), TerminationCode::SolverConverged);
    assert!((result.param()[0] - 1.0).abs() < 1e-4);
    assert!((result.param()[1] - 1.0).abs() < 1e-4);
    let (_, cost, violation) = result.state.best().unwrap();
    assert_eq!(violation.measure(), &ViolationMeasure::L2);
    assert!(violation.value <= 1e-8);
    assert!((cost - 2.0).abs() < 1e-4);
}

Practical considerations

Configure the inner solve and penalty together

Use with_inner_solver with gradient descent, BFGS, unbounded L-BFGS, or a compatible derivative-free method. Inners implement WarmStart and consume a CostFunction + Gradient surrogate. Least-squares solvers cannot consume this adapter because it provides no residual or Jacobian interface. The original problem needs Gradient even with a derivative-free inner because the outer method verifies stationarity independently.

Defaults are rho0 = 10.0, with_rho_increase(10.0), with_feasibility_decrease(0.25), and 50 inner iterations per outer step. The ratio compares consecutive equality residual norms. A larger penalty pushes toward feasibility but can make inner minimization harder, so scale the constraint rows and allow enough inner work before increasing it aggressively.

Check feasibility and stationarity

with_absolute_feasibility_tolerance tests ||A x - b||_2 and defaults to 1e-8. Outer convergence also requires the surrogate-gradient infinity norm to be at most 1e-8, or at most 1e-3 of its norm at the inner seed. Configure those alternatives with with_absolute_stationarity_tolerance and with_relative_stationarity_tolerance; they combine with OR and use the same pre-update multipliers and penalty.

None disables an optional test, and zero requests exact-zero comparisons. Disabling both stationarity guards permits feasibility-only stopping. Otherwise, a derivative-free or capped inner run still has to pass independent verification. Optional outer cost- and step-change tests require both feasibility and stationarity. These are numerical stopping tests, so inspect the returned objective and residual as well as the report.

Interpret the selected incumbent

SelectedState publishes the original objective and an unscaled L2 violation report. The incumbent prefers feasible points at the configured tolerance, then lower costs among feasible points. Before finding one, it prefers smaller residual norms, using the objective to break exact ties. A selected point can therefore have a higher cost than an earlier infeasible point.

Use state.best() for checked access and read its matching violation report. Objective-only executor targets and stalls are unavailable for this state; application stopping rules should inspect feasibility alongside cost.

Implementation

The solver owns the multipliers, penalty history, and inner executor. The outer state exposes no objective gradient. Every inner solve starts fresh, while a fresh outer run resets multipliers and penalty history. An ExactCheckpoint retains the solver, selected progress, and evaluation counts for continuation. With serde, checkpoints can be serialized when their constituent types can.

Inner work is charged to its corresponding evaluation categories. Independent stationarity checks add up to two gradient evaluations per consumed result. Enable with_inner_diagnostics(true) and retain the solver with run_with_solver to inspect the last native inner outcome and the multipliers and penalty that defined it. Outer executor limits are checked between segments, so one inner run can exceed a remaining evaluation allowance.

Supported dense pairs are Vec<F>/DenseMatrix<F>, nalgebra::DVector<F>/DMatrix<F>, ndarray::Array1<F>/Array2<F>, and faer::Col<F>/Mat<F>, with F = f32 or f64, when the chosen inner supports them. The outer method needs matrix-vector and transpose products, without a linear solve. Custom inners can add requirements; the Vec example needs no BLAS/LAPACK provider.

References

  1. Nocedal, J. and Wright, S. J. (2006). Numerical Optimization (2nd ed.). Springer. doi:10.1007/978-0-387-40065-5.

Basin v3.1.0 — numerical optimization for Rust.

GitHub docs.rs crates.io