Help us shape the future of Go! Take the 2026 Go Developer Survey

optimize

package
v1.0.0-alpha.27 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package optimize is Pulse's public numerical-optimization core: solvers an embedder (and Pulse's own estimators) call with plain Go callbacks.

LeastSquares is bounded Levenberg–Marquardt nonlinear least squares: residual and Jacobian callbacks, per-parameter bounds held by projection (a step that crosses a bound lands exactly on it, so a coefficient bounded at 0 can be exactly 0), damped normal equations solved through linalg.SolveSPD, and JᵀJ at the solution for the Gauss–Newton covariance.

Minimize is SLSQP — Kraft's Sequential Least Squares Programming, a statement-by-statement port of SciPy 1.18.1's C source (BSD-3-Clause; SCIPY_LICENSE.txt travels with the code) — for a smooth objective under simple bounds plus any number of equality (Fn(x) = 0) and inequality (Fn(x) ≥ 0) constraints. It reports the Lagrange multiplier of every constraint and which bounds bind. Gradients are the caller's.

Determinism

The arithmetic is FMA-free: every product that feeds an addition or subtraction is an explicit float64(a*b), which the Go specification forbids the compiler from fusing into a fused multiply-add (TestOptimize_NoFMAContraction holds the barriers). A problem whose callbacks are themselves deterministic therefore gives the same bits on every architecture, serial or not.

Errors

Bad input is a PULSE_OPTIMIZE_INVALID *errors.CodedError and no result. A run that stops short returns its final iterate together with PULSE_OPTIMIZE_NOT_CONVERGED or (Minimize) PULSE_OPTIMIZE_INFEASIBLE; the solvers themselves never panic.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActiveBound

type ActiveBound int8

ActiveBound reports which of a variable's bounds holds at the solution.

const (
	// NotActive: the variable sits strictly inside its bounds (or has none).
	NotActive ActiveBound = iota
	// AtLower: the variable sits on its lower bound (also reported for a
	// variable fixed by Lower == Upper).
	AtLower
	// AtUpper: the variable sits on its upper bound.
	AtUpper
)

type Constraint

type Constraint struct {
	Fn   func(x []float64) float64
	Grad func(x []float64) []float64
	Type ConstraintType
}

Constraint is one general constraint on the decision vector: Fn(x) = 0 (Eq) or Fn(x) ≥ 0 (Ineq), with Grad returning ∂Fn/∂x (length len(x)).

type ConstraintType

type ConstraintType int

ConstraintType says whether a Constraint holds with equality or as a lower bound of zero. The zero value is invalid, so a Constraint that forgets its Type is refused rather than silently read as one kind.

const (
	// Eq is an equality constraint: Fn(x) = 0.
	Eq ConstraintType = iota + 1
	// Ineq is an inequality constraint: Fn(x) ≥ 0.
	Ineq
)

func (ConstraintType) String

func (t ConstraintType) String() string

String returns "eq" or "ineq".

type LeastSquaresOptions

type LeastSquaresOptions struct {
	// MaxIter caps the outer iterations (one per accepted step, the
	// damping retries inside an iteration not counted). 0 means 2000;
	// negative is refused.
	MaxIter int
	// CostTol stops the run when an accepted step lowers the cost by at
	// most CostTol·cost. 0 means 1e-13; a negative or non-finite value is
	// refused.
	CostTol float64
	// StepTol stops the run when an accepted step moves every parameter
	// by at most StepTol·(1+|x_i|). 0 means 1e-10; a negative or
	// non-finite value is refused.
	StepTol float64
}

LeastSquaresOptions configures LeastSquares. The zero value is the default schedule.

type LeastSquaresResult

type LeastSquaresResult struct {
	// X is the final parameter vector; Cost is ½·Σ r_i² there.
	X    []float64
	Cost float64
	// JTJ is JᵀJ at X over every parameter (bound-held ones included),
	// so a caller forms the Gauss–Newton covariance σ²·(JᵀJ)⁻¹ from it
	// (linalg.InverseSPD), with σ² = 2·Cost / (m − n), after dropping the
	// rows and columns of parameters held on a bound.
	JTJ *linalg.Sym
	// Active marks, per parameter, the bound it sits EXACTLY on: a step
	// that crosses a bound is projected onto it, so a parameter bounded
	// at 0 reaches 0.
	Active     []ActiveBound
	Status     LeastSquaresStatus
	Iterations int
	FuncEvals  int
	JacEvals   int
}

LeastSquaresResult is LeastSquares' final iterate.

func LeastSquares

func LeastSquares(residual func(x []float64) []float64, jacobian func(x []float64) [][]float64,
	x0, lower, upper []float64, opts LeastSquaresOptions) (res *LeastSquaresResult, err error)

LeastSquares minimises ½·Σ r_i(x)² by projected Levenberg–Marquardt from x0 within per-parameter bounds lower ≤ x ≤ upper (each nil, or one entry per parameter; an infinite or NaN entry leaves that side open). residual returns the m residuals at x (m fixed by the first call); jacobian returns the m×n Jacobian ∂r_i/∂x_j as m rows. Both see a scratch copy of x and may reuse their returned slices.

The schedule is fixed: Marquardt damping λ·max(diag(JᵀJ), 1e-12) with λ starting at 1e-3, shrunk ×⅓ (floor 1e-12) on an accepted step and grown ×4 on a rejected one. Each iteration holds a parameter that sits on a bound its gradient pushes past, solves the damped normal equations on the rest (linalg.SolveSPD), and projects the trial point onto the bounds — so a bound is reached exactly, never approached through a reparametrisation. A trial is accepted when its cost does not rise. The run stops on an accepted step that changes the cost by ≤ CostTol relative or every parameter by ≤ StepTol·(1+|x|), or when λ exceeds 1e16. The arithmetic is serial, fixed-order and FMA-free.

Bad input (nil callbacks, an empty or non-finite x0, a bound slice of the wrong length or crossed bounds, an option out of range, no residuals, a residual or Jacobian of the wrong shape, a non-finite residual at the start or a non-finite Jacobian entry) is a PULSE_OPTIMIZE_INVALID error with a nil result. An iteration-limit stop returns the final iterate WITH PULSE_OPTIMIZE_NOT_CONVERGED; a converged result has a nil error. x0 is clipped to the bounds.

type LeastSquaresStatus

type LeastSquaresStatus int

LeastSquaresStatus says why LeastSquares stopped. The zero value is never returned.

const (
	// LSConvergedCost: an accepted step lowered the cost by at most
	// CostTol relative.
	LSConvergedCost LeastSquaresStatus = iota + 1
	// LSConvergedStep: an accepted step moved every parameter by at most
	// StepTol relative, or every parameter is held on a bound.
	LSConvergedStep
	// LSConvergedDamping: no step lowered the cost at any damping up to
	// 1e16 — x is a minimum to working precision.
	LSConvergedDamping
	// LSIterLimit: MaxIter iterations ran without meeting a stop rule.
	LSIterLimit
)

func (LeastSquaresStatus) Converged

func (s LeastSquaresStatus) Converged() bool

Converged reports whether the run met a stop rule (every status but LSIterLimit).

func (LeastSquaresStatus) String

func (s LeastSquaresStatus) String() string

String describes the stop.

type MinimizeOptions

type MinimizeOptions struct {
	// Lower and Upper are per-variable bounds: nil, or exactly len(x0)
	// entries. An infinite or NaN entry marks that side unbounded; a
	// finite Lower[i] above Upper[i] is refused.
	Lower, Upper []float64
	// Constraints are the general constraints, in any mix of Eq and
	// Ineq; MinimizeResult.Multipliers follows this order.
	Constraints []Constraint
	// Acc is SLSQP's accuracy goal (SciPy's ftol): the stop on the
	// objective change, step length and constraint violation. 0 means
	// 1e-6; a negative or non-finite value is refused.
	Acc float64
	// MaxIter caps the major iterations. 0 means 100; negative is refused.
	MaxIter int
}

MinimizeOptions configures Minimize. The zero value is an unbounded, unconstrained problem with SciPy's defaults.

type MinimizeResult

type MinimizeResult struct {
	// X is the final decision vector; F is the objective there.
	X []float64
	F float64
	// Multipliers holds one Lagrange multiplier per Constraint, in
	// MinimizeOptions.Constraints order, from the last QP subproblem. At
	// a solution ∇f = Σ Multipliers[i]·∇Fn_i on the free variables, so
	// a multiplier is the rate the optimal objective moves as its
	// constraint's right-hand side is relaxed; an Ineq multiplier is ≥ 0
	// when its constraint binds and 0 when it is slack.
	Multipliers []float64
	// Active marks, per variable, the bound it sits on: within
	// sqrt(machine epsilon)·(1+|bound|) of it.
	Active []ActiveBound
	// MaxViolation is the largest constraint violation at X: |Fn| for an
	// Eq constraint, max(−Fn, 0) for an Ineq one (0 with no constraints).
	MaxViolation float64
	Status       Status
	Iterations   int
	FuncEvals    int
	GradEvals    int
}

MinimizeResult is the solver's final iterate.

func Minimize

func Minimize(f func(x []float64) float64, grad func(x []float64) []float64, x0 []float64, opts MinimizeOptions) (res *MinimizeResult, err error)

Minimize minimises f, with gradient grad, from x0 by Sequential Least Squares Programming (Kraft's SLSQP, as SciPy 1.18.1 ships it) subject to opts' bounds and general constraints. The arithmetic is FMA-free, so a problem whose callbacks are themselves deterministic gives the same bits on every architecture.

x0 is clipped to the bounds; an infeasible start for the general constraints is allowed. Bad input (empty or non-finite x0, nil callbacks, a bound or option out of range, a constraint with no Type, more equality constraints than variables, a gradient of the wrong length) is a PULSE_OPTIMIZE_INVALID error with a nil result. A run that stops short returns its final iterate WITH an error: PULSE_OPTIMIZE_INFEASIBLE when the constraints could not be met (StatusIncompatible, StatusSingularC, or any other early stop with a constraint violation above 10·Acc), else PULSE_OPTIMIZE_NOT_CONVERGED. A StatusSuccess result has a nil error.

type Status

type Status int

Status is SLSQP's exit mode, numbered as SciPy numbers it.

const (
	StatusSuccess           Status = 0 // optimization terminated successfully
	StatusTooManyEquality   Status = 2 // more equality constraints than variables
	StatusLSQIterLimit      Status = 3 // more than 3·n iterations in the LSQ subproblem
	StatusIncompatible      Status = 4 // inequality constraints incompatible
	StatusSingularE         Status = 5 // singular matrix E in the LSQ subproblem
	StatusSingularC         Status = 6 // singular matrix C in the LSQ subproblem
	StatusRankDeficient     Status = 7 // rank-deficient equality constraint subproblem
	StatusPositiveDirection Status = 8 // positive directional derivative in the line search
	StatusIterLimit         Status = 9 // iteration limit reached
)

func (Status) String

func (s Status) String() string

String returns SciPy's message for the exit mode.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL