AugLag.jl
OptimizationAuglag.jl provides an augmented Lagrangian wrapper for constrained Optimization.jl problems. It repeatedly solves augmented subproblems with a user selected inner optimizer.
Installation: OptimizationAuglag.jl
import Pkg
Pkg.add("OptimizationAuglag")Methods
OptimizationAuglag.AugLag — Type
AugLag(; inner, kwargs...)Augmented Lagrangian optimizer that solves constrained Optimization.jl problems by repeatedly calling an inner optimizer on the augmented objective.
Keyword Arguments
inner: Inner optimizer used for the augmented subproblems.τ: Constraint violation contraction threshold.γ: Penalty growth factor.ϵ,ϵ_primal,ϵ_dual: Convergence tolerances.ρ_init: Initial penalty parameter. Ifnothing, a problem-scaled value is chosen.inner_kwargs: Keyword arguments forwarded to the inner optimizer.
OptimizationAuglag.classify_constraints — Function
classify_constraints(lcons, ucons)Partition constraint indices given the SciMLBase contract lcons[i] ≤ c_i(θ) ≤ ucons[i]:
eq_inds: rows wherelcons[i] == ucons[i](equality).ineq_upper_inds: rows withlcons[i] != ucons[i]and finiteucons[i], contributing the penalty forc_i ≤ ucons[i].ineq_lower_inds: rows withlcons[i] != ucons[i]and finitelcons[i], contributing the penalty forlcons[i] ≤ c_i.
A two-sided inequality (both bounds finite, lcons[i] < ucons[i]) appears in bothineq_upper_inds and ineq_lower_inds and gets a separate Lagrange multiplier per side.
OptimizationAuglag.generate_auglag — Function
generate_auglag(cache, eq_inds, ineq_upper_inds, ineq_lower_inds,
λ, μ_upper, μ_lower, ρ_ref)Build the augmented-Lagrangian subproblem function as an OptimizationFunction with analytical value, gradient, and fg! derived from the user's loss and constraints in cache.f.
The augmented Lagrangian is
L(θ; λ, μ_u, μ_l, ρ) = f(θ)
+ Σᵢ∈eq [ λᵢ (cᵢ - lᵢ) + (ρ/2)(cᵢ - lᵢ)² ]
+ (1/(2ρ)) Σᵢ∈up max(0, μ_uᵢ + ρ (cᵢ - uᵢ))²
+ (1/(2ρ)) Σᵢ∈lo max(0, μ_lᵢ + ρ (lᵢ - cᵢ))²so its gradient (used closed-form, not by AD'ing through L) is
∇L = ∇f
+ Σᵢ∈eq (λᵢ + ρ (cᵢ - lᵢ)) ∇cᵢ
+ Σᵢ∈up max(0, μ_uᵢ + ρ (cᵢ - uᵢ)) ∇cᵢ
- Σᵢ∈lo max(0, μ_lᵢ + ρ (lᵢ - cᵢ)) ∇cᵢ.λ, μ_upper, μ_lower are mutable vectors and ρ_ref is a Ref{<:Real} (or any zero-arg-getindex container). The closures dereference them at call time, so the outer AugLag loop can update multipliers and the penalty in place between inner solves without rebuilding the function.
cons_tmp and J are preallocated once with element type eltype(cache.u0). This is safe because the analytical gradient does not AD through this function — cache.f.grad and cache.f.cons_j (which the inner solver ultimately calls) handle their own AD internally.
Constraints and the data-iterator p
Constraints are treated as non-stochastic / batch-independent: the user's cons!(res, θ, p) is always invoked with the fullcache.p, regardless of which inner-solve batch is currently being processed. For the typical non-DataLoader case cache.p is just the user's p. For a DataLoaderp, cons! receives the iterator itself, and the user's constraint body may pull the underlying full data from it (e.g. via p.data for an MLUtils.DataLoader) — but the constraint is still a deterministic function of θ and the full data, never of a single batch.
Consistent with this, the constraint Jacobian cache.f.cons_j is invoked without p and uses the p that was closed over at AD-preparation time (the first batch for a data iterator). Since the constraint is by contract batch-independent, that closed-over p is irrelevant to the Jacobian's value.
Example
using OptimizationBase, OptimizationAuglag, OptimizationOptimJL, ADTypes
rosenbrock(x, p) = (p[1] - x[1])^2 + p[2] * (x[2] - x[1]^2)^2
cons(res, x, p) = (res .= [x[1]^2 + x[2]^2])
optf = OptimizationFunction(rosenbrock, ADTypes.AutoForwardDiff(); cons)
prob = OptimizationProblem(
optf, zeros(2), [1.0, 100.0];
lcons = [1.0], ucons = [1.0],
)
sol = solve(prob, AugLag(inner = BFGS()); maxiters = 100)