LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
auglag.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_UTIL_AUGLAG_H
6#define LINE_UTIL_AUGLAG_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * Augmented Lagrangian method for equality- and inequality-constrained
12 * minimization, with line/util/neldermead.h or line/util/levmar.h as the inner
13 * unconstrained solver.
14 *
15 * Solves
16 * min f(x) s.t. h_i(x) = 0, g_j(x) <= 0, lo <= x <= hi
17 * by minimizing, for a sequence of penalty parameters rho and multiplier
18 * estimates (lambda, mu),
19 *
20 * L_A(x; lambda, mu, rho) = f(x)
21 * + sum_i [ lambda_i h_i + (rho/2) h_i^2 ]
22 * + (1/(2 rho)) sum_j [ max(0, mu_j + rho g_j)^2 - mu_j^2 ]
23 *
24 * which is the Hestenes-Powell-Rockafellar form: the inequality term is the
25 * exact penalty of Rockafellar (1973), differentiable once, and is inactive
26 * for a constraint that is strictly satisfied with a zero multiplier. After
27 * each inner solve
28 * lambda_i <- lambda_i + rho h_i, mu_j <- max(0, mu_j + rho g_j)
29 * and rho is multiplied by rho_factor whenever the constraint violation did
30 * not shrink by at least the factor `shrink`. This is a first-order multiplier
31 * method: it converges to a KKT point without driving rho to infinity, which
32 * is what keeps the inner problems well conditioned.
33 *
34 * WHY NOT A PENALTY-ONLY LOOP: with lambda held at zero, the minimizer of the
35 * penalized problem is offset from the true solution by O(1/rho) and the only
36 * way to tighten it is a large rho, whose Hessian is ill conditioned by
37 * exactly that factor. The multiplier update removes the offset, so a moderate
38 * rho suffices.
39 *
40 * ACCEPTANCE CONTRACT. This replaces MATLAB's fmincon (active-set /
41 * interior-point), quadprog, patternsearch and PSwarm in the m3a and
42 * kpctoolbox fitting routines. It is a different algorithm: it does not
43 * reproduce their iterates, their multipliers, or, on a nonconvex problem
44 * with several local minima, necessarily their local minimum. Every caller in
45 * line/api/mam therefore states its acceptance in terms of the specification
46 * (the fitted process reproduces the target characteristics to a stated
47 * tolerance, and is a valid MAP) and returns the objective value it achieved,
48 * so it can be compared against the reference's.
49 *
50 * Deterministic: no random multi-start, no global state, no exit(), no output.
51 * Non-convergence is reported through AugLagResult, never thrown.
52 *
53 * Gated on transcendental arithmetic through the inner solvers.
54 */
55
56#include <cstddef>
57#include <vector>
58
59#include "line/num/number.h"
60#include "line/util/error.h"
61#include "line/util/levmar.h"
63
64namespace line {
65
66/** Tuning of the outer multiplier iteration. */
67template <class T>
69 T rho0; ///< initial penalty parameter
70 T rho_factor; ///< growth factor applied to rho when needed
71 T rho_max; ///< cap on rho
72 T ctol; ///< constraint violation accepted as feasible
73 T shrink; ///< required violation reduction to leave rho alone
74 unsigned max_outer; ///< cap on outer iterations
75 NelderMeadOptions<T> inner_nm; ///< tuning of the simplex inner solver
76 LevmarOptions<T> inner_lm; ///< tuning of the least-squares inner solver
77};
78
79/** Defaults: rho0 = 10, growth 10, feasibility 1e-10, 50 outer iterations. */
80template <class T>
93
94/** Outcome of a constrained solve. */
95template <class T>
97 std::vector<T> x; ///< best point found
98 T fval; ///< the ORIGINAL objective f(x), not the augmented one
99 T violation; ///< max(|h_i|, max(0, g_j)) at x
100 std::vector<T> lambda; ///< final equality multipliers
101 std::vector<T> mu; ///< final inequality multipliers, all >= 0
103 bool converged; ///< feasible to ctol and the last inner solve converged
104};
105
106/** A constraint map that returns no constraints; the default for h or g. */
107template <class T>
109 std::vector<T> operator()(const std::vector<T>&) const { return std::vector<T>(); }
110};
111
112namespace agldetail {
113
114/** max(|h_i|, max(0, g_j)). */
115template <class T>
116T violation_of(const std::vector<T>& h, const std::vector<T>& g) {
117 const T zero = num_traits<T>::from_int(0);
118 T v = zero;
119 for (std::size_t i = 0; i < h.size(); ++i) {
120 const T a = num_abs(h[i]);
121 if (a > v) v = a;
122 }
123 for (std::size_t j = 0; j < g.size(); ++j)
124 if (g[j] > v) v = g[j];
125 return v;
126}
127
128} // namespace agldetail
129
130/**
131 * Augmented Lagrangian with a scalar objective and a simplex inner solver.
132 *
133 * @param f objective, x -> T
134 * @param h equality constraints, x -> vector (empty for none)
135 * @param g inequality constraints g(x) <= 0, x -> vector (empty for none)
136 * @param x0 starting point
137 * @param bounds one Bound per variable; pass all-free bounds for none
138 * @param opt tuning
139 */
140template <class T, class F, class H, class G>
141AugLagResult<T> auglag(F f, H h, G g, const std::vector<T>& x0,
142 const std::vector<Bound<T>>& bounds, const AugLagOptions<T>& opt) {
144 "auglag requires transcendental arithmetic");
145 const T zero = num_traits<T>::from_int(0);
146 const T two = num_traits<T>::from_int(2);
147 const std::size_t n = x0.size();
148 if (n == 0) throw InputError("auglag: no variables");
149 if (bounds.size() != n) throw InputError("auglag: one bound per variable is required");
150
151 const std::size_t ne = h(x0).size();
152 const std::size_t ni = g(x0).size();
153
154 AugLagResult<T> res;
155 res.x = x0;
156 res.lambda.assign(ne, zero);
157 res.mu.assign(ni, zero);
158 res.outer_iterations = 0;
159 res.converged = false;
160
161 T rho = opt.rho0;
162 T prev_viol = num_traits<T>::from_double(-1.0);
163 bool inner_ok = false;
164
165 for (unsigned outer = 0; outer < opt.max_outer; ++outer) {
166 res.outer_iterations = outer + 1;
167 const std::vector<T> lam = res.lambda;
168 const std::vector<T> mu = res.mu;
169 const T rho_c = rho;
170
171 auto L = [f, h, g, lam, mu, rho_c, zero, two](const std::vector<T>& x) {
172 T val = f(x);
173 const std::vector<T> hv = h(x);
174 for (std::size_t i = 0; i < hv.size(); ++i)
175 val += lam[i] * hv[i] + rho_c / two * hv[i] * hv[i];
176 const std::vector<T> gv = g(x);
177 for (std::size_t j = 0; j < gv.size(); ++j) {
178 const T s = mu[j] + rho_c * gv[j];
179 if (s > zero) val += (s * s - mu[j] * mu[j]) / (two * rho_c);
180 else val -= mu[j] * mu[j] / (two * rho_c);
181 }
182 return val;
183 };
184
185 const NelderMeadResult<T> in = nelder_mead_box(L, res.x, bounds, opt.inner_nm);
186 res.x = in.x;
187 inner_ok = in.converged;
188
189 const std::vector<T> hv = h(res.x);
190 const std::vector<T> gv = g(res.x);
191 const T viol = agldetail::violation_of(hv, gv);
192 res.violation = viol;
193
194 for (std::size_t i = 0; i < ne; ++i) res.lambda[i] = res.lambda[i] + rho * hv[i];
195 for (std::size_t j = 0; j < ni; ++j) {
196 const T s = res.mu[j] + rho * gv[j];
197 res.mu[j] = s > zero ? s : zero;
198 }
199
200 if (viol <= opt.ctol) {
201 res.converged = inner_ok;
202 break;
203 }
204 if (prev_viol >= zero && viol > opt.shrink * prev_viol && rho < opt.rho_max)
205 rho *= opt.rho_factor;
206 prev_viol = viol;
207 }
208
209 res.fval = f(res.x);
210 return res;
211}
212
213/** auglag with the default tuning. */
214template <class T, class F, class H, class G>
215AugLagResult<T> auglag(F f, H h, G g, const std::vector<T>& x0,
216 const std::vector<Bound<T>>& bounds) {
217 return auglag(f, h, g, x0, bounds, auglag_defaults<T>());
218}
219
220/**
221 * Augmented Lagrangian with a least-squares objective and levmar as the inner
222 * solver.
223 *
224 * The augmented Lagrangian of a sum of squares is itself a sum of squares up
225 * to an additive constant, because
226 * lambda h + (rho/2) h^2 = (rho/2)(h + lambda/rho)^2 - lambda^2/(2 rho)
227 * (1/(2 rho)) max(0, mu + rho g)^2 = (rho/2) max(0, g + mu/rho)^2
228 * so the inner problem is handed to levmar with the extended residual
229 * [ r(x) ; sqrt(rho/2) (h + lambda/rho) ; sqrt(rho/2) max(0, g + mu/rho) ].
230 * The dropped constants do not move the minimizer. The max(.) makes the
231 * extended residual only piecewise smooth, which the finite-difference
232 * Jacobian tolerates because an inequality is either active or inactive over
233 * a whole differencing step except on a measure-zero set of iterates.
234 *
235 * Bounds are NOT supported here (levmar is unconstrained): express them as
236 * inequality rows of g, which is what the callers in line/api/mam do.
237 *
238 * @param r residual map, x -> vector of length m; the objective is sum r_i^2
239 * @param m number of residuals
240 * @param h equality constraints
241 * @param g inequality constraints g(x) <= 0
242 * @param x0 starting point
243 * @param opt tuning
244 */
245template <class T, class R, class H, class G>
246AugLagResult<T> auglag_ls(R r, std::size_t m, H h, G g, const std::vector<T>& x0,
247 const AugLagOptions<T>& opt) {
249 "auglag_ls requires transcendental arithmetic");
250 using std::sqrt;
251 const T zero = num_traits<T>::from_int(0);
252 const T two = num_traits<T>::from_int(2);
253 const std::size_t n = x0.size();
254 if (n == 0) throw InputError("auglag_ls: no variables");
255
256 const std::size_t ne = h(x0).size();
257 const std::size_t ni = g(x0).size();
258
259 AugLagResult<T> res;
260 res.x = x0;
261 res.lambda.assign(ne, zero);
262 res.mu.assign(ni, zero);
263 res.outer_iterations = 0;
264 res.converged = false;
265
266 T rho = opt.rho0;
267 T prev_viol = num_traits<T>::from_double(-1.0);
268 bool inner_ok = false;
269
270 for (unsigned outer = 0; outer < opt.max_outer; ++outer) {
271 res.outer_iterations = outer + 1;
272 const std::vector<T> lam = res.lambda;
273 const std::vector<T> mu = res.mu;
274 const T rho_c = rho;
275 const T w = sqrt(T(rho_c / two));
276
277 auto Raug = [r, h, g, lam, mu, rho_c, w, zero](const std::vector<T>& x) {
278 std::vector<T> out = r(x);
279 const std::vector<T> hv = h(x);
280 for (std::size_t i = 0; i < hv.size(); ++i)
281 out.push_back(T(w * (hv[i] + lam[i] / rho_c)));
282 const std::vector<T> gv = g(x);
283 for (std::size_t j = 0; j < gv.size(); ++j) {
284 const T s = gv[j] + mu[j] / rho_c;
285 out.push_back(s > zero ? T(w * s) : zero);
286 }
287 return out;
288 };
289
290 const LevmarResult<T> in = levmar(Raug, res.x, m + ne + ni, opt.inner_lm);
291 res.x = in.x;
292 inner_ok = in.converged;
293
294 const std::vector<T> hv = h(res.x);
295 const std::vector<T> gv = g(res.x);
296 const T viol = agldetail::violation_of(hv, gv);
297 res.violation = viol;
298
299 for (std::size_t i = 0; i < ne; ++i) res.lambda[i] = res.lambda[i] + rho * hv[i];
300 for (std::size_t j = 0; j < ni; ++j) {
301 const T s = res.mu[j] + rho * gv[j];
302 res.mu[j] = s > zero ? s : zero;
303 }
304
305 if (viol <= opt.ctol) {
306 res.converged = inner_ok;
307 break;
308 }
309 if (prev_viol >= zero && viol > opt.shrink * prev_viol && rho < opt.rho_max)
310 rho *= opt.rho_factor;
311 prev_viol = viol;
312 }
313
314 res.fval = levmardetail::sum_squares(r(res.x));
315 return res;
316}
317
318/** auglag_ls with the default tuning. */
319template <class T, class R, class H, class G>
320AugLagResult<T> auglag_ls(R r, std::size_t m, H h, G g, const std::vector<T>& x0) {
321 return auglag_ls(r, m, h, g, x0, auglag_defaults<T>());
322}
323
324} // namespace line
325
326#endif // LINE_UTIL_AUGLAG_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Levenberg-Marquardt for nonlinear least squares.
T num_abs(const T &v)
Definition number.h:172
AugLagResult< T > auglag(F f, H h, G g, const std::vector< T > &x0, const std::vector< Bound< T > > &bounds, const AugLagOptions< T > &opt)
Augmented Lagrangian with a scalar objective and a simplex inner solver.
Definition auglag.h:141
LevmarResult< T > levmar(F f, const std::vector< T > &x0, std::size_t m, const LevmarOptions< T > &opt)
Levenberg-Marquardt with a central-difference Jacobian.
Definition levmar.h:301
NelderMeadResult< T > nelder_mead_box(F f, const std::vector< T > &x0, const std::vector< Bound< T > > &bounds, const NelderMeadOptions< T > &opt)
Box-constrained simplex minimization by the transformation described in the header comment.
Definition neldermead.h:370
AugLagOptions< T > auglag_defaults()
Defaults: rho0 = 10, growth 10, feasibility 1e-10, 50 outer iterations.
Definition auglag.h:81
AugLagResult< T > auglag_ls(R r, std::size_t m, H h, G g, const std::vector< T > &x0, const AugLagOptions< T > &opt)
Augmented Lagrangian with a least-squares objective and levmar as the inner solver.
Definition auglag.h:246
NelderMeadOptions< T > nelder_mead_defaults()
fminsearch's coefficients and initial simplex, with tighter tolerances.
Definition neldermead.h:79
LevmarOptions< T > levmar_defaults()
MINPACK-like defaults, with a central-difference step of eps^(1/3).
Definition levmar.h:77
Derivative-free simplex minimization (Nelder and Mead, 1965), with optional box bounds imposed by a c...
Number-type abstraction for the templated API port.
Tuning of the outer multiplier iteration.
Definition auglag.h:68
T ctol
constraint violation accepted as feasible
Definition auglag.h:72
T rho0
initial penalty parameter
Definition auglag.h:69
LevmarOptions< T > inner_lm
tuning of the least-squares inner solver
Definition auglag.h:76
NelderMeadOptions< T > inner_nm
tuning of the simplex inner solver
Definition auglag.h:75
T rho_factor
growth factor applied to rho when needed
Definition auglag.h:70
T shrink
required violation reduction to leave rho alone
Definition auglag.h:73
T rho_max
cap on rho
Definition auglag.h:71
unsigned max_outer
cap on outer iterations
Definition auglag.h:74
Outcome of a constrained solve.
Definition auglag.h:96
bool converged
feasible to ctol and the last inner solve converged
Definition auglag.h:103
std::vector< T > mu
final inequality multipliers, all >= 0
Definition auglag.h:101
std::vector< T > x
best point found
Definition auglag.h:97
T violation
max(|h_i|, max(0, g_j)) at x
Definition auglag.h:99
std::vector< T > lambda
final equality multipliers
Definition auglag.h:100
T fval
the ORIGINAL objective f(x), not the augmented one
Definition auglag.h:98
unsigned outer_iterations
Definition auglag.h:102
Box constraint on one variable.
Definition neldermead.h:110
Tuning of the Levenberg-Marquardt iteration.
Definition levmar.h:63
Outcome of a least-squares solve.
Definition levmar.h:93
std::vector< T > x
best point found
Definition levmar.h:94
bool converged
a tolerance was met before the caps
Definition levmar.h:99
Tuning of the simplex iteration.
Definition neldermead.h:64
Outcome of a simplex minimization.
Definition neldermead.h:96
std::vector< T > x
best point found
Definition neldermead.h:97
bool converged
both tolerances met before the caps
Definition neldermead.h:101
A constraint map that returns no constraints; the default for h or g.
Definition auglag.h:108
std::vector< T > operator()(const std::vector< T > &) const
Definition auglag.h:109