1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.hipparchus.filtering.kalman.linear;
19
20 import org.hipparchus.filtering.kalman.ProcessEstimate;
21 import org.hipparchus.filtering.kalman.Reference;
22 import org.hipparchus.filtering.kalman.SimpleMeasurement;
23 import org.hipparchus.linear.CholeskyDecomposer;
24 import org.hipparchus.linear.MatrixUtils;
25 import org.hipparchus.linear.RealMatrix;
26 import org.hipparchus.linear.RealVector;
27 import org.hipparchus.random.RandomGenerator;
28 import org.hipparchus.random.Well1024a;
29 import org.hipparchus.util.FastMath;
30 import org.junit.jupiter.api.Test;
31
32 import java.util.List;
33 import java.util.stream.IntStream;
34 import java.util.stream.Stream;
35
36 import static org.junit.jupiter.api.Assertions.assertEquals;
37 import static org.junit.jupiter.api.Assertions.assertNull;
38 import static org.junit.jupiter.api.Assertions.assertTrue;
39
40 class LinearKalmanFilterTest {
41
42 @Test
43 void testConstant() {
44 final RealMatrix a = MatrixUtils.createRealIdentityMatrix(1);
45 final RealMatrix b = null;
46 final RealVector u = null;
47 final RealMatrix q = MatrixUtils.createRealDiagonalMatrix(new double[] {
48 1.0e-5
49 });
50
51
52 final ProcessEstimate initial = new ProcessEstimate(0,
53 MatrixUtils.createRealVector(new double[] { 10.0 }),
54 q);
55 assertNull(initial.getInnovationCovariance());
56
57
58 final List<Reference> referenceData = Reference.loadReferenceData(1, 1, "constant-value.txt");
59 final Stream<SimpleMeasurement> measurements =
60 referenceData.stream().
61 map(r -> new SimpleMeasurement(r.getTime(),
62 r.getZ(),
63 MatrixUtils.createRealDiagonalMatrix(new double[] { 0.1 })));
64
65
66 final LinearKalmanFilter<SimpleMeasurement> filter =
67 new LinearKalmanFilter<>(new CholeskyDecomposer(1.0e-15, 1.0e-15),
68 measurement -> new LinearEvolution(a, b, u, q,
69 MatrixUtils.createRealMatrix(new double[][] { { 1.0 } })),
70 initial);
71
72
73 measurements.
74 map(measurement -> filter.estimationStep(measurement)).
75 forEach(estimate -> {
76 for (Reference r : referenceData) {
77 if (r.sameTime(estimate.getTime())) {
78 r.checkState(estimate.getState(), 1.0e-15);
79 r.checkCovariance(estimate.getCovariance(), 3.0e-19);
80 return;
81 }
82 }
83 });
84
85 }
86
87 @Test
88 void testConstantAcceleration() {
89 doTestConstantAcceleration("constant-acceleration.txt");
90 }
91
92 @Test
93 void testConstantAccelerationWithIntermediateData() {
94 doTestConstantAcceleration("constant-acceleration-with-intermediate-data.txt");
95 }
96
97 @Test
98 void testConstantAccelerationWithOutlier() {
99 doTestConstantAcceleration("constant-acceleration-with-outlier.txt");
100 }
101
102 private void doTestConstantAcceleration(String name) {
103
104
105
106
107
108
109
110 final double dt = 0.1;
111 final double dt2 = dt * dt;
112 final double dt3 = dt2 * dt;
113 final double dt4 = dt2 * dt2;
114 final double acc = 0.1;
115 final double aNoise = 0.2;
116 final double aNoise2 = aNoise * aNoise;
117 final double mNoise = 10.0;
118 final RealMatrix a = MatrixUtils.createRealMatrix(new double[][] {
119 { 1.0, dt },
120 { 0.0, 1.0 }
121 });
122 final RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
123 { 0.5 * dt2 },
124 { dt }
125 });
126 final RealVector u = MatrixUtils.createRealVector(new double[] { acc });
127 final RealMatrix q = MatrixUtils.createRealMatrix(new double[][] {
128 { 0.25 * dt4 * aNoise2, 0.5 * dt3 * aNoise2 },
129 { 0.5 * dt3 * aNoise2, dt2 * aNoise2 }
130 });
131
132
133 final ProcessEstimate initial = new ProcessEstimate(0,
134 MatrixUtils.createRealVector(new double[] { 0.0, 0.0 }),
135 MatrixUtils.createRealMatrix(new double[][] {
136 { 1.0, 1.0 },
137 { 1.0, 1.0 }
138 }));
139
140
141
142 final List<Reference> referenceData = Reference.loadReferenceData(2, 1, name);
143 final Stream<SimpleMeasurement> measurements =
144 referenceData.stream().
145 map(r -> new SimpleMeasurement(r.getTime(),
146 r.getZ(),
147 MatrixUtils.createRealDiagonalMatrix(new double[] { mNoise * mNoise })));
148
149
150 final LinearKalmanFilter<SimpleMeasurement> filter =
151 new LinearKalmanFilter<>(new CholeskyDecomposer(1.0e-15, 1.0e-15),
152 measurement -> {
153 RealMatrix h = (measurement.getValue().getEntry(0) > 1.0e6) ?
154 null :
155 MatrixUtils.createRealMatrix(new double[][] { { 1.0, 0.0 } });
156 return new LinearEvolution(a, b, u, q, h);
157 },
158 initial);
159
160
161 measurements.
162 map(measurement -> filter.estimationStep(measurement)).
163 forEach(estimate -> {
164 for (Reference r : referenceData) {
165 if (r.sameTime(estimate.getTime())) {
166 r.checkState(estimate.getState(), 4.0e-15);
167 r.checkCovariance(estimate.getCovariance(), 4.0e-15);
168 if (r.hasIntermediateData()) {
169 r.checkStateTransitionMatrix(estimate.getStateTransitionMatrix(), 1.0e-14);
170 r.checkMeasurementJacobian(estimate.getMeasurementJacobian(), 1.0e-15);
171 r.checkInnovationCovariance(estimate.getInnovationCovariance(), 1.0e-12);
172 r.checkKalmanGain(estimate.getKalmanGain(), 1.0e-12);
173 r.checkKalmanGain(estimate.getKalmanGain(), 1.0e-15);
174 }
175 return;
176 }
177 }
178 });
179
180 }
181
182 @Test
183 void testCannonballZeroProcessNoise() {
184 doTestCannonball(new double[][] {
185 { 0.00, 0.00, 0.00, 0.00 },
186 { 0.00, 0.00, 0.00, 0.00 },
187 { 0.00, 0.00, 0.00, 0.00 },
188 { 0.00, 0.00, 0.00, 0.00 },
189 }, "cannonball-zero-process-noise.txt",
190 9.0e-16, 6.0e-14);
191 }
192
193 @Test
194 void testCannonballNonZeroProcessNoise() {
195 doTestCannonball(new double[][] {
196 { 0.01, 0.00, 0.00, 0.00 },
197 { 0.00, 0.10, 0.00, 0.00 },
198 { 0.00, 0.00, 0.01, 0.00 },
199 { 0.00, 0.00, 0.00, 0.10 },
200 }, "cannonball-non-zero-process-noise.txt",
201 2.0e-13, 2.0e-13);
202 }
203
204 private void doTestCannonball(final double[][] qData, final String name,
205 final double tolState, final double tolCovariance) {
206
207 final double dt = 0.1;
208 final double g = 9.81;
209 final double mNoise = 30.0;
210 final double vIni = 100.0;
211 final double alphaIni = FastMath.PI / 4;
212 final RealMatrix a = MatrixUtils.createRealMatrix(new double[][] {
213 { 1.0, dt, 0.0, 0.0 },
214 { 0.0, 1.0, 0.0, 0.0 },
215 { 0.0, 0.0, 1.0, dt },
216 { 0.0, 0.0, 0.0, 1.0 },
217 });
218 final RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
219 { 0.0, 0.0 },
220 { 0.0, 0.0 },
221 { 1.0, 0.0 },
222 { 0.0, 1.0 }
223 });
224 final RealVector u = MatrixUtils.createRealVector(new double[] {
225 -0.5 * g * dt * dt, -g * dt
226 });
227 final RealMatrix q = MatrixUtils.createRealMatrix(qData);
228
229
230 final ProcessEstimate initial = new ProcessEstimate(0,
231 MatrixUtils.createRealVector(new double[] {
232 0.0, vIni * FastMath.cos(alphaIni),
233 0.0, vIni * FastMath.sin(alphaIni)
234 }),
235 MatrixUtils.createRealDiagonalMatrix(new double[] {
236 mNoise * mNoise, 1.0e-3, mNoise * mNoise, 1.0e-3
237 }));
238
239
240
241 final List<Reference> referenceData = Reference.loadReferenceData(4, 2, name);
242 final Stream<SimpleMeasurement> measurements =
243 referenceData.stream().
244 map(r -> new SimpleMeasurement(r.getTime(),
245 r.getZ(),
246 MatrixUtils.createRealDiagonalMatrix(new double[] {
247 mNoise * mNoise, mNoise * mNoise
248 })));
249
250
251 final LinearKalmanFilter<SimpleMeasurement> filter =
252 new LinearKalmanFilter<>(new CholeskyDecomposer(1.0e-15, 1.0e-15),
253 time -> new LinearEvolution(a, b, u, q,
254 MatrixUtils.createRealMatrix(new double[][] {
255 { 1.0, 0.0, 0.0, 0.0 },
256 { 0.0, 0.0, 1.0, 0.0 }
257 })),
258 initial);
259
260
261 measurements.
262 map(measurement -> filter.estimationStep(measurement)).
263 map(estimate -> {
264 final ProcessEstimate p = filter.getPredicted();
265 final ProcessEstimate c = filter.getCorrected();
266 assertEquals(p.getTime(), c.getTime(), 1.0e-15);
267 assertTrue(p.getState().getDistance(c.getState()) > 0.005);
268 return estimate;
269 }).
270 forEach(estimate -> {
271 for (Reference r : referenceData) {
272 if (r.sameTime(estimate.getTime())) {
273 r.checkState(estimate.getState(), tolState);
274 r.checkCovariance(estimate.getCovariance(), tolCovariance);
275 return;
276 }
277 }
278 });
279
280 }
281
282 @Test
283 void testWelshBishopExactR() {
284 doTestWelshBishop(0xd30a8f811e2f7c61l, -0.37727, 0.1,
285 0.0, 1.0, 1.0e-5, 0.1 * 0.1,
286 50, -0.389117, 1.0e-6);
287 }
288
289 @Test
290 void testWelshBishopBigR() {
291 doTestWelshBishop(0xd30a8f811e2f7c61l, -0.37727, 0.1,
292 0.0, 1.0, 1.0e-5, 1.0 * 1.0,
293 50, -0.385613, 1.0e-6);
294 }
295
296 @Test
297 void testWelshBishopSmallR() {
298 doTestWelshBishop(0xd30a8f811e2f7c61l, -0.37727, 0.1,
299 0.0, 1.0, 1.0e-5, 0.01 * 0.01,
300 50, -0.403015, 1.0e-6);
301 }
302
303 private void doTestWelshBishop(final long seed,
304 final double trueConstant, final double trueStdv,
305 final double initialEstimate, final double initialCovariance,
306 final double qValue, final double r,
307 final int nbMeasurements,
308 final double expected, final double tolerance) {
309
310
311
312
313 final RealMatrix a = MatrixUtils.createRealIdentityMatrix(1);
314 final RealMatrix b = null;
315 final RealVector u = null;
316 final RealMatrix q = MatrixUtils.createRealDiagonalMatrix(new double[] {
317 qValue
318 });
319 final ProcessEstimate initial = new ProcessEstimate(0,
320 MatrixUtils.createRealVector(new double[] { initialEstimate }),
321 MatrixUtils.createRealDiagonalMatrix(new double[] { initialCovariance }));
322 final RandomGenerator generator = new Well1024a(seed);
323 final Stream<SimpleMeasurement> measurements =
324 IntStream.
325 range(0, nbMeasurements).
326 mapToObj(i -> new SimpleMeasurement(i,
327 MatrixUtils.createRealVector(new double[] {
328 trueConstant + generator.nextGaussian() * trueStdv,
329 }),
330 MatrixUtils.createRealDiagonalMatrix(new double[] { r })));
331
332
333 final LinearKalmanFilter<SimpleMeasurement> filter =
334 new LinearKalmanFilter<>(new CholeskyDecomposer(1.0e-15, 1.0e-15),
335 measurement -> new LinearEvolution(a, b, u, q,
336 MatrixUtils.createRealMatrix(new double[][] { { 1.0 } })),
337 initial);
338
339
340 final ProcessEstimate finalEstimate = measurements.
341 map(measurement -> filter.estimationStep(measurement)).
342 reduce((first, second) -> second).get();
343
344 assertEquals(expected, finalEstimate.getState().getEntry(0), tolerance);
345
346 }
347
348 }