1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.hipparchus.fitting;
23
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.List;
27
28 import org.hipparchus.analysis.function.HarmonicOscillator;
29 import org.hipparchus.exception.LocalizedCoreFormats;
30 import org.hipparchus.exception.MathIllegalArgumentException;
31 import org.hipparchus.exception.MathIllegalStateException;
32 import org.hipparchus.linear.DiagonalMatrix;
33 import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresBuilder;
34 import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
35 import org.hipparchus.util.FastMath;
36 import org.hipparchus.util.SinCos;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public class HarmonicCurveFitter extends AbstractCurveFitter {
54
55 private static final HarmonicOscillator.Parametric FUNCTION = new HarmonicOscillator.Parametric();
56
57 private final double[] initialGuess;
58
59 private final int maxIter;
60
61
62
63
64
65
66
67
68 private HarmonicCurveFitter(double[] initialGuess, int maxIter) {
69 this.initialGuess = initialGuess == null ? null : initialGuess.clone();
70 this.maxIter = maxIter;
71 }
72
73
74
75
76
77
78
79
80
81
82
83
84 public static HarmonicCurveFitter create() {
85 return new HarmonicCurveFitter(null, Integer.MAX_VALUE);
86 }
87
88
89
90
91
92
93 public HarmonicCurveFitter withStartPoint(double[] newStart) {
94 return new HarmonicCurveFitter(newStart.clone(),
95 maxIter);
96 }
97
98
99
100
101
102
103 public HarmonicCurveFitter withMaxIterations(int newMaxIter) {
104 return new HarmonicCurveFitter(initialGuess,
105 newMaxIter);
106 }
107
108
109 @Override
110 protected LeastSquaresProblem getProblem(Collection<WeightedObservedPoint> observations) {
111
112 final int len = observations.size();
113 final double[] target = new double[len];
114 final double[] weights = new double[len];
115
116 int i = 0;
117 for (WeightedObservedPoint obs : observations) {
118 target[i] = obs.getY();
119 weights[i] = obs.getWeight();
120 ++i;
121 }
122
123 final AbstractCurveFitter.TheoreticalValuesFunction model
124 = new AbstractCurveFitter.TheoreticalValuesFunction(FUNCTION,
125 observations);
126
127 final double[] startPoint = initialGuess != null ?
128 initialGuess :
129
130 new ParameterGuesser(observations).guess();
131
132
133
134 return new LeastSquaresBuilder().
135 maxEvaluations(Integer.MAX_VALUE).
136 maxIterations(maxIter).
137 start(startPoint).
138 target(target).
139 weight(new DiagonalMatrix(weights)).
140 model(model.getModelFunction(), model.getModelFunctionJacobian()).
141 build();
142
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244 public static class ParameterGuesser {
245
246 private final double a;
247
248 private final double omega;
249
250 private final double phi;
251
252
253
254
255
256
257
258
259
260
261 public ParameterGuesser(Collection<WeightedObservedPoint> observations) {
262 if (observations.size() < 4) {
263 throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
264 observations.size(), 4, true);
265 }
266
267 final WeightedObservedPoint[] sorted
268 = sortObservations(observations).toArray(new WeightedObservedPoint[0]);
269
270 final double[] aOmega = guessAOmega(sorted);
271 a = aOmega[0];
272 omega = aOmega[1];
273
274 phi = guessPhi(sorted);
275 }
276
277
278
279
280
281
282
283
284
285
286
287 public double[] guess() {
288 return new double[] { a, omega, phi };
289 }
290
291
292
293
294
295
296
297 private List<WeightedObservedPoint> sortObservations(Collection<WeightedObservedPoint> unsorted) {
298 final List<WeightedObservedPoint> observations = new ArrayList<>(unsorted);
299
300
301
302
303 WeightedObservedPoint curr = observations.get(0);
304 final int len = observations.size();
305 for (int j = 1; j < len; j++) {
306 WeightedObservedPoint prec = curr;
307 curr = observations.get(j);
308 if (curr.getX() < prec.getX()) {
309
310 int i = j - 1;
311 WeightedObservedPoint mI = observations.get(i);
312 while ((i >= 0) && (curr.getX() < mI.getX())) {
313 observations.set(i + 1, mI);
314 if (i != 0) {
315 mI = observations.get(i - 1);
316 }
317 --i;
318 }
319 observations.set(i + 1, curr);
320 curr = observations.get(j);
321 }
322 }
323
324 return observations;
325 }
326
327
328
329
330
331
332
333
334
335
336
337 private double[] guessAOmega(WeightedObservedPoint[] observations) {
338 final double[] aOmega = new double[2];
339
340
341 double sx2 = 0;
342 double sy2 = 0;
343 double sxy = 0;
344 double sxz = 0;
345 double syz = 0;
346
347 double currentX = observations[0].getX();
348 double currentY = observations[0].getY();
349 double f2Integral = 0;
350 double fPrime2Integral = 0;
351 final double startX = currentX;
352 for (int i = 1; i < observations.length; ++i) {
353
354 final double previousX = currentX;
355 final double previousY = currentY;
356 currentX = observations[i].getX();
357 currentY = observations[i].getY();
358
359
360
361 final double dx = currentX - previousX;
362 final double dy = currentY - previousY;
363 final double f2StepIntegral =
364 dx * (previousY * previousY + previousY * currentY + currentY * currentY) / 3;
365 final double fPrime2StepIntegral = dy * dy / dx;
366
367 final double x = currentX - startX;
368 f2Integral += f2StepIntegral;
369 fPrime2Integral += fPrime2StepIntegral;
370
371 sx2 += x * x;
372 sy2 += f2Integral * f2Integral;
373 sxy += x * f2Integral;
374 sxz += x * fPrime2Integral;
375 syz += f2Integral * fPrime2Integral;
376 }
377
378
379 double c1 = sy2 * sxz - sxy * syz;
380 double c2 = sxy * sxz - sx2 * syz;
381 double c3 = sx2 * sy2 - sxy * sxy;
382 if ((c1 / c2 < 0) || (c2 / c3 < 0)) {
383 final int last = observations.length - 1;
384
385
386 final double xRange = observations[last].getX() - observations[0].getX();
387 if (xRange == 0) {
388 throw new MathIllegalArgumentException(LocalizedCoreFormats.ZERO_NOT_ALLOWED);
389 }
390 aOmega[1] = 2 * Math.PI / xRange;
391
392 double yMin = Double.POSITIVE_INFINITY;
393 double yMax = Double.NEGATIVE_INFINITY;
394 for (int i = 1; i < observations.length; ++i) {
395 final double y = observations[i].getY();
396 if (y < yMin) {
397 yMin = y;
398 }
399 if (y > yMax) {
400 yMax = y;
401 }
402 }
403 aOmega[0] = 0.5 * (yMax - yMin);
404 } else {
405 if (c2 == 0) {
406
407
408 throw new MathIllegalStateException(LocalizedCoreFormats.ZERO_DENOMINATOR);
409 }
410
411 aOmega[0] = FastMath.sqrt(c1 / c2);
412 aOmega[1] = FastMath.sqrt(c2 / c3);
413 }
414
415 return aOmega;
416 }
417
418
419
420
421
422
423
424 private double guessPhi(WeightedObservedPoint[] observations) {
425
426 double fcMean = 0;
427 double fsMean = 0;
428
429 double currentX = observations[0].getX();
430 double currentY = observations[0].getY();
431 for (int i = 1; i < observations.length; ++i) {
432
433 final double previousX = currentX;
434 final double previousY = currentY;
435 currentX = observations[i].getX();
436 currentY = observations[i].getY();
437 final double currentYPrime = (currentY - previousY) / (currentX - previousX);
438
439 double omegaX = omega * currentX;
440 SinCos sc = FastMath.sinCos(omegaX);
441 fcMean += omega * currentY * sc.cos() - currentYPrime * sc.sin();
442 fsMean += omega * currentY * sc.sin() + currentYPrime * sc.cos();
443 }
444
445 return FastMath.atan2(-fsMean, fcMean);
446 }
447 }
448 }