Skip to content

Commit 88c09c3

Browse files
committed
Apply MINPACK-style cubic damping and trust-region update to LM optimizer
1 parent ea74979 commit 88c09c3

File tree

1 file changed

+129
-38
lines changed

1 file changed

+129
-38
lines changed

src/Numerics/Optimization/LevenbergMarquardtMinimizer.cs

Lines changed: 129 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,18 @@ public NonlinearMinimizationResult Minimum(IObjectiveModel objective, Vector<dou
9393
if (objective == null)
9494
throw new ArgumentNullException(nameof(objective));
9595

96+
var objectiveModel = objective.CreateNew();
97+
9698
ValidateBounds(initialGuess, lowerBound, upperBound, scales);
9799

98-
objective.SetParameters(initialGuess, isFixed);
100+
objectiveModel.SetParameters(initialGuess, isFixed);
99101

100-
ExitCondition exitCondition = ExitCondition.None;
102+
var exitCondition = ExitCondition.None;
101103

102104
// First, calculate function values and setup variables
103105
var P = ProjectToInternalParameters(initialGuess); // current internal parameters
104106
Vector<double> Pstep; // the change of parameters
105-
var RSS = EvaluateFunction(objective, P); // Residual Sum of Squares = R'R
107+
var RSS = EvaluateFunction(objectiveModel, P); // Residual Sum of Squares = 1/2 R'R
106108

107109
if (maximumIterations < 0)
108110
{
@@ -113,7 +115,7 @@ public NonlinearMinimizationResult Minimum(IObjectiveModel objective, Vector<dou
113115
if (double.IsNaN(RSS))
114116
{
115117
exitCondition = ExitCondition.InvalidValues;
116-
return new NonlinearMinimizationResult(objective, -1, exitCondition);
118+
return new NonlinearMinimizationResult(objectiveModel, -1, exitCondition);
117119
}
118120

119121
// When only function evaluation is needed, set maximumIterations to zero,
@@ -129,8 +131,7 @@ public NonlinearMinimizationResult Minimum(IObjectiveModel objective, Vector<dou
129131
}
130132

131133
// Evaluate gradient and Hessian
132-
var (Gradient, Hessian) = EvaluateJacobian(objective, P);
133-
var diagonalOfHessian = Hessian.Diagonal(); // diag(H)
134+
var (Gradient, Hessian) = EvaluateJacobian(objectiveModel, P);
134135

135136
// if ||g||oo <= gtol, found and stop
136137
if (Gradient.InfinityNorm() <= gradientTolerance)
@@ -140,91 +141,181 @@ public NonlinearMinimizationResult Minimum(IObjectiveModel objective, Vector<dou
140141

141142
if (exitCondition != ExitCondition.None)
142143
{
143-
return new NonlinearMinimizationResult(objective, -1, exitCondition);
144+
return new NonlinearMinimizationResult(objectiveModel, -1, exitCondition);
144145
}
145146

146-
double mu = initialMu * diagonalOfHessian.Max(); // μ
147-
double nu = 2; // ν
148-
int iterations = 0;
147+
// Initialize trust region boundary delta and damping parameter mu
148+
var delta = initialMu * P.L2Norm(); // Trust region boundary
149+
if (delta == 0.0) delta = initialMu;
150+
var mu = initialMu * Hessian.Diagonal().Max(); // Damping parameter μ
151+
var nu = 2.0; // Multiplication factor ν for mu updates
152+
153+
// Counters for successful and failed iterations
154+
var ncsuc = 0; // number of consecutive successful iterations
155+
var ncfail = 0; // number of consecutive failed iterations
156+
157+
// Flag for first iteration special handling
158+
var firstIteration = true;
159+
160+
var iterations = 0;
149161
while (iterations < maximumIterations && exitCondition == ExitCondition.None)
150162
{
151163
iterations++;
152164

153165
while (true)
154166
{
167+
// Store current Hessian diagonal for restoration if step is rejected
168+
var savedDiagonal = Hessian.Diagonal().Clone();
169+
170+
// Add damping to Hessian: H + μI
155171
Hessian.SetDiagonal(Hessian.Diagonal() + mu); // hessian[i, i] = hessian[i, i] + mu;
156172

157-
// solve normal equations
173+
// Solve normal equations: (H + μI)Δp = -g
158174
Pstep = Hessian.Solve(-Gradient);
159175

160-
// if ||ΔP|| <= xTol * (||P|| + xTol), found and stop
161-
if (Pstep.L2Norm() <= stepTolerance * (P.L2Norm() + stepTolerance))
176+
// Calculate step size (norm)
177+
var pnorm = Pstep.L2Norm();
178+
179+
// On first iteration, adjust the initial step bound
180+
if (firstIteration)
181+
{
182+
delta = Math.Min(delta, pnorm);
183+
firstIteration = false;
184+
}
185+
186+
// Check convergence on step size
187+
if (pnorm <= stepTolerance * (P.L2Norm() + stepTolerance))
162188
{
163189
exitCondition = ExitCondition.RelativePoints;
164190
break;
165191
}
166192

167-
var Pnew = P + Pstep; // new parameters to test
168-
// evaluate function at Pnew
169-
var RSSnew = EvaluateFunction(objective, Pnew);
193+
// New parameter vector
194+
var Pnew = P + Pstep;
170195

196+
// Evaluate function at new point
197+
var RSSnew = EvaluateFunction(objectiveModel, Pnew);
198+
199+
// Check for invalid results
171200
if (double.IsNaN(RSSnew))
172201
{
173202
exitCondition = ExitCondition.InvalidValues;
174203
break;
175204
}
176205

177-
// calculate the ratio of the actual to the predicted reduction.
178-
// ρ = (RSS - RSSnew) / (Δp'(μΔp - g))
179-
var predictedReduction = Pstep.DotProduct(mu * Pstep - Gradient);
180-
var rho = (predictedReduction != 0)
181-
? (RSS - RSSnew) / predictedReduction
182-
: 0;
206+
// Compute the scaled actual reduction
207+
// actred = 1 - (fnorm1/fnorm)^2 if fnorm1 < fnorm, else -1
208+
var actred = (RSSnew < RSS) ? 1.0 - Math.Pow(RSSnew / RSS, 2.0) : -1.0;
209+
210+
// Compute predicted reduction metrics
211+
// In LMDER, wa3 = J'*(Q'*fvec), where J is the Jacobian at the current point
212+
// and the predicted reduction is ||J*p||^2 + ||sqrt(par)*D*p||^2
213+
var tempVec = Hessian.Multiply(Pstep);
214+
var temp1 = Pstep.DotProduct(tempVec) / RSS;
215+
var temp2 = (Math.Sqrt(mu) * pnorm) / Math.Sqrt(RSS);
216+
var prered = temp1 + Math.Pow(temp2, 2.0) / 0.5;
217+
var dirder = -(temp1 + Math.Pow(temp2, 2.0));
218+
219+
// Compute the ratio of actual to predicted reduction
220+
var ratio = (prered != 0.0) ? actred / prered : 0.0;
221+
222+
// Update trust region based on reduction ratio
223+
if (ratio < 0.0001)
224+
{
225+
// Failure: ratio too small
226+
ncsuc = 0;
227+
ncfail++;
228+
229+
mu = mu * nu;
230+
nu = 2.0 * nu;
231+
232+
delta = 0.25 * delta;
233+
}
234+
else if (ratio < 0.25)
235+
{
236+
// Accept but shrink
237+
ncfail = 0;
238+
ncsuc = 0;
239+
240+
delta = 0.5 * delta;
241+
242+
var temp = 1.0 - Math.Pow((2.0 * ratio - 1.0), 3);
243+
temp = Math.Max(temp, 1.0 / 3.0);
244+
mu = mu * temp;
245+
}
246+
else if (ratio < 0.75)
247+
{
248+
// Accept + mild delta increase
249+
ncsuc++;
250+
ncfail = 0;
251+
252+
delta = Math.Max(delta, pnorm);
253+
254+
var temp = 1.0 - Math.Pow((2.0 * ratio - 1.0), 3);
255+
temp = Math.Max(temp, 1.0 / 3.0);
256+
mu = mu * temp;
257+
}
258+
else
259+
{
260+
// ratio >= 0.75
261+
ncsuc++;
262+
ncfail = 0;
263+
264+
delta = Math.Max(delta, 2.0 * pnorm);
183265

184-
if (rho > 0.0)
266+
var temp = 1.0 - Math.Pow((2.0 * ratio - 1.0), 3);
267+
temp = Math.Max(temp, 1.0 / 3.0);
268+
mu = mu * temp;
269+
}
270+
271+
// Test for successful iteration
272+
if (ratio >= 0.0001)
185273
{
186-
// accepted
274+
// Update parameters
187275
Pnew.CopyTo(P);
188276
RSS = RSSnew;
189277

190-
// update gradient and Hessian
191-
(Gradient, Hessian) = EvaluateJacobian(objective, P);
192-
diagonalOfHessian = Hessian.Diagonal();
278+
// Recalculate gradient and Hessian at new point
279+
(Gradient, Hessian) = EvaluateJacobian(objectiveModel, P);
193280

194-
// if ||g||_oo <= gtol, found and stop
281+
// Check convergence criteria
195282
if (Gradient.InfinityNorm() <= gradientTolerance)
196283
{
197284
exitCondition = ExitCondition.RelativeGradient;
198285
}
199286

200-
// if ||R||^2 < fTol, found and stop
201287
if (RSS <= functionTolerance)
202288
{
203-
exitCondition = ExitCondition.Converged; // SmallRSS
289+
exitCondition = ExitCondition.Converged;
204290
}
205291

206-
mu = mu * Math.Max(1.0 / 3.0, 1.0 - Math.Pow(2.0 * rho - 1.0, 3));
207-
nu = 2;
208-
209-
break;
292+
break; // Exit inner loop, step accepted
210293
}
211294
else
212295
{
213-
// rejected, increased μ
296+
// Step was rejected, restore original Hessian
297+
Hessian.SetDiagonal(savedDiagonal);
298+
299+
// Update mu and nu
214300
mu = mu * nu;
215-
nu = 2 * nu;
301+
nu = 2.0 * nu;
216302

217-
Hessian.SetDiagonal(diagonalOfHessian);
303+
// If we're making no progress, exit the inner loop
304+
if (ncfail >= 2)
305+
{
306+
break; // Exit inner loop, try a new Jacobian
307+
}
218308
}
219309
}
220310
}
221311

312+
// Check if max iterations reached
222313
if (iterations >= maximumIterations)
223314
{
224315
exitCondition = ExitCondition.ExceedIterations;
225316
}
226317

227-
return new NonlinearMinimizationResult(objective, iterations, exitCondition);
318+
return new NonlinearMinimizationResult(objectiveModel, iterations, exitCondition);
228319
}
229320
}
230321
}

0 commit comments

Comments
 (0)