1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.hipparchus.optim.nonlinear.vector.leastsquares;
18
19 import org.hipparchus.exception.LocalizedCoreFormats;
20 import org.hipparchus.exception.MathIllegalArgumentException;
21 import org.hipparchus.exception.MathIllegalStateException;
22 import org.hipparchus.exception.NullArgumentException;
23 import org.hipparchus.linear.ArrayRealVector;
24 import org.hipparchus.linear.CholeskyDecomposition;
25 import org.hipparchus.linear.MatrixDecomposer;
26 import org.hipparchus.linear.MatrixUtils;
27 import org.hipparchus.linear.QRDecomposer;
28 import org.hipparchus.linear.RealMatrix;
29 import org.hipparchus.linear.RealVector;
30 import org.hipparchus.optim.ConvergenceChecker;
31 import org.hipparchus.optim.LocalizedOptimFormats;
32 import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem.Evaluation;
33 import org.hipparchus.util.Incrementor;
34 import org.hipparchus.util.Pair;
35
36
37
38
39
40
41
42
43
44 public class SequentialGaussNewtonOptimizer implements LeastSquaresOptimizer {
45
46
47
48
49
50
51 private static final double SINGULARITY_THRESHOLD = 1e-11;
52
53
54 private final MatrixDecomposer decomposer;
55
56
57 private final boolean formNormalEquations;
58
59
60 private final Evaluation oldEvaluation;
61
62
63 private final RealMatrix oldLhs;
64
65
66 private final RealVector oldRhs;
67
68
69
70
71
72
73
74
75
76 public SequentialGaussNewtonOptimizer() {
77 this(new QRDecomposer(SINGULARITY_THRESHOLD), false, null);
78 }
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 public SequentialGaussNewtonOptimizer(final MatrixDecomposer decomposer,
97 final boolean formNormalEquations,
98 final Evaluation evaluation) {
99 this.decomposer = decomposer;
100 this.formNormalEquations = formNormalEquations;
101 this.oldEvaluation = evaluation;
102 if (evaluation == null) {
103 this.oldLhs = null;
104 this.oldRhs = null;
105 } else {
106 if (formNormalEquations) {
107 final Pair<RealMatrix, RealVector> normalEquation =
108 computeNormalMatrix(evaluation.getJacobian(), evaluation.getResiduals());
109
110 this.oldLhs = normalEquation.getFirst();
111 this.oldRhs = normalEquation.getSecond();
112 } else {
113 this.oldLhs = evaluation.getJacobian();
114 this.oldRhs = evaluation.getResiduals();
115 }
116 }
117 }
118
119
120
121
122
123
124 public MatrixDecomposer getDecomposer() {
125 return decomposer;
126 }
127
128
129
130
131
132
133
134 public SequentialGaussNewtonOptimizer withDecomposer(final MatrixDecomposer newDecomposer) {
135 return new SequentialGaussNewtonOptimizer(newDecomposer,
136 this.isFormNormalEquations(),
137 this.getOldEvaluation());
138 }
139
140
141
142
143
144
145
146
147 public boolean isFormNormalEquations() {
148 return formNormalEquations;
149 }
150
151
152
153
154
155
156
157
158
159
160
161
162 public SequentialGaussNewtonOptimizer withFormNormalEquations(final boolean newFormNormalEquations) {
163 return new SequentialGaussNewtonOptimizer(this.getDecomposer(),
164 newFormNormalEquations,
165 this.getOldEvaluation());
166 }
167
168
169
170
171
172
173 public Evaluation getOldEvaluation() {
174 return oldEvaluation;
175 }
176
177
178
179
180
181
182
183
184
185
186
187
188
189 public SequentialGaussNewtonOptimizer withEvaluation(final Evaluation previousEvaluation) {
190 return new SequentialGaussNewtonOptimizer(this.getDecomposer(),
191 this.isFormNormalEquations(),
192 previousEvaluation);
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211 public SequentialGaussNewtonOptimizer withAPrioriData(final RealVector aPrioriState,
212 final RealMatrix aPrioriCovariance) {
213 return withAPrioriData(aPrioriState, aPrioriCovariance,
214 CholeskyDecomposition.DEFAULT_RELATIVE_SYMMETRY_THRESHOLD,
215 CholeskyDecomposition.DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD);
216 }
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238 public SequentialGaussNewtonOptimizer withAPrioriData(final RealVector aPrioriState,
239 final RealMatrix aPrioriCovariance,
240 final double relativeSymmetryThreshold,
241 final double absolutePositivityThreshold) {
242
243
244
245
246
247
248
249 final RealMatrix jTj = getDecomposer().decompose(aPrioriCovariance).getInverse();
250 final RealMatrix weightedJacobian = new CholeskyDecomposition(jTj,
251 relativeSymmetryThreshold,
252 absolutePositivityThreshold).getLT();
253
254
255 final RealVector residuals = MatrixUtils.createRealVector(aPrioriState.getDimension());
256
257
258 final Evaluation fakeEvaluation = new AbstractEvaluation(aPrioriState.getDimension()) {
259
260
261 @Override
262 public RealVector getResiduals() {
263 return residuals;
264 }
265
266
267 @Override
268 public RealVector getPoint() {
269 return aPrioriState;
270 }
271
272
273 @Override
274 public RealMatrix getJacobian() {
275 return weightedJacobian;
276 }
277 };
278
279 return withEvaluation(fakeEvaluation);
280
281 }
282
283
284 @Override
285 public Optimum optimize(final LeastSquaresProblem lsp) {
286
287 final Incrementor evaluationCounter = lsp.getEvaluationCounter();
288 final Incrementor iterationCounter = lsp.getIterationCounter();
289 final ConvergenceChecker<Evaluation> checker =
290 lsp.getConvergenceChecker();
291
292
293 if (checker == null) {
294 throw new NullArgumentException();
295 }
296
297 RealVector currentPoint = lsp.getStart();
298
299 if (oldEvaluation != null &&
300 currentPoint.getDimension() != oldEvaluation.getPoint().getDimension()) {
301 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
302 currentPoint.getDimension(), oldEvaluation.getPoint().getDimension());
303 }
304
305
306 Evaluation current = null;
307 while (true) {
308 iterationCounter.increment();
309
310
311 final Evaluation previous = current;
312
313
314 evaluationCounter.increment();
315 current = lsp.evaluate(currentPoint);
316 final RealVector currentResiduals = current.getResiduals();
317 final RealMatrix weightedJacobian = current.getJacobian();
318
319 currentPoint = current.getPoint();
320
321
322 if (previous != null &&
323 checker.converged(iterationCounter.getCount(), previous,
324 current)) {
325
326 final Evaluation combinedEvaluation = oldEvaluation == null ?
327 current :
328 new CombinedEvaluation(oldEvaluation, current);
329 return Optimum.of(combinedEvaluation, evaluationCounter.getCount(),
330 iterationCounter.getCount());
331 }
332
333
334 final RealMatrix lhs;
335 final RealVector rhs;
336 if (this.formNormalEquations) {
337 final Pair<RealMatrix, RealVector> normalEquation =
338 computeNormalMatrix(weightedJacobian, currentResiduals);
339
340 lhs = oldLhs == null ?
341 normalEquation.getFirst() :
342 normalEquation.getFirst().add(oldLhs);
343 rhs = oldRhs == null ?
344 normalEquation.getSecond() :
345 normalEquation.getSecond().add(oldRhs);
346 } else {
347 lhs = oldLhs == null ?
348 weightedJacobian :
349 combineJacobians(oldLhs, weightedJacobian);
350 rhs = oldRhs == null ?
351 currentResiduals :
352 combineResiduals(oldRhs, currentResiduals);
353 }
354
355 final RealVector dX;
356 try {
357 dX = this.decomposer.decompose(lhs).solve(rhs);
358 } catch (MathIllegalArgumentException e) {
359
360 throw new MathIllegalStateException(LocalizedOptimFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM,
361 e);
362 }
363
364 currentPoint = currentPoint.add(dX);
365
366 }
367 }
368
369
370 @Override
371 public String toString() {
372 return "SequentialGaussNewtonOptimizer{" +
373 "decomposer=" + decomposer + '}';
374 }
375
376
377
378
379
380
381
382
383 private static Pair<RealMatrix, RealVector>
384 computeNormalMatrix(final RealMatrix jacobian,
385 final RealVector residuals) {
386
387
388 final int nR = jacobian.getRowDimension();
389 final int nC = jacobian.getColumnDimension();
390
391 final RealMatrix normal = MatrixUtils.createRealMatrix(nC, nC);
392 final RealVector jTr = new ArrayRealVector(nC);
393
394 for (int i = 0; i < nR; ++i) {
395
396 for (int j = 0; j < nC; j++) {
397 jTr.setEntry(j,
398 jTr.getEntry(j) +
399 residuals.getEntry(i) *
400 jacobian.getEntry(i, j));
401 }
402
403
404 for (int k = 0; k < nC; ++k) {
405
406 for (int l = k; l < nC; ++l) {
407 normal
408 .setEntry(k, l,
409 normal.getEntry(k,
410 l) +
411 jacobian.getEntry(i, k) *
412 jacobian.getEntry(i, l));
413 }
414 }
415 }
416
417 for (int i = 0; i < nC; i++) {
418 for (int j = 0; j < i; j++) {
419 normal.setEntry(i, j, normal.getEntry(j, i));
420 }
421 }
422 return new Pair<RealMatrix, RealVector>(normal, jTr);
423 }
424
425
426
427
428
429
430 private static RealMatrix combineJacobians(final RealMatrix oldJacobian,
431 final RealMatrix newJacobian) {
432 final int oldRowDimension = oldJacobian.getRowDimension();
433 final int oldColumnDimension = oldJacobian.getColumnDimension();
434 final RealMatrix jacobian =
435 MatrixUtils.createRealMatrix(oldRowDimension + newJacobian.getRowDimension(),
436 oldColumnDimension);
437 jacobian.setSubMatrix(oldJacobian.getData(), 0, 0);
438 jacobian.setSubMatrix(newJacobian.getData(), oldRowDimension, 0);
439 return jacobian;
440 }
441
442
443
444
445
446
447 private static RealVector combineResiduals(final RealVector oldResiduals,
448 final RealVector newResiduals) {
449 return oldResiduals.append(newResiduals);
450 }
451
452
453
454
455 private static class CombinedEvaluation extends AbstractEvaluation {
456
457
458 private final RealVector point;
459
460
461 private final RealMatrix jacobian;
462
463
464 private final RealVector residuals;
465
466
467
468
469
470
471
472 private CombinedEvaluation(final Evaluation oldEvaluation,
473 final Evaluation newEvaluation) {
474
475 super(oldEvaluation.getResiduals().getDimension() +
476 newEvaluation.getResiduals().getDimension());
477
478 this.point = newEvaluation.getPoint();
479 this.jacobian = combineJacobians(oldEvaluation.getJacobian(),
480 newEvaluation.getJacobian());
481 this.residuals = combineResiduals(oldEvaluation.getResiduals(),
482 newEvaluation.getResiduals());
483 }
484
485
486 @Override
487 public RealMatrix getJacobian() {
488 return jacobian;
489 }
490
491
492 @Override
493 public RealVector getPoint() {
494 return point;
495 }
496
497
498 @Override
499 public RealVector getResiduals() {
500 return residuals;
501 }
502
503 }
504
505 }