1   /* Copyright 2002-2022 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.estimation.measurements;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.Map;
23  
24  import org.hipparchus.stat.descriptive.moment.Mean;
25  import org.hipparchus.stat.descriptive.rank.Max;
26  import org.hipparchus.stat.descriptive.rank.Median;
27  import org.hipparchus.stat.descriptive.rank.Min;
28  import org.hipparchus.util.FastMath;
29  import org.junit.Assert;
30  import org.junit.Test;
31  import org.orekit.estimation.Context;
32  import org.orekit.estimation.EstimationTestUtils;
33  import org.orekit.estimation.measurements.modifiers.TurnAroundRangeTroposphericDelayModifier;
34  import org.orekit.models.earth.troposphere.SaastamoinenModel;
35  import org.orekit.orbits.OrbitType;
36  import org.orekit.orbits.PositionAngle;
37  import org.orekit.propagation.Propagator;
38  import org.orekit.propagation.SpacecraftState;
39  import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
40  import org.orekit.time.AbsoluteDate;
41  import org.orekit.utils.Constants;
42  import org.orekit.utils.Differentiation;
43  import org.orekit.utils.ParameterDriver;
44  import org.orekit.utils.ParameterFunction;
45  import org.orekit.utils.StateFunction;
46  import org.orekit.utils.TimeStampedPVCoordinates;
47  
48  public class TurnAroundRangeTest {
49  
50  
51      /**
52       * Test the values of the TAR (Turn-Around Range) comparing the observed values and the estimated values
53       * Both are calculated with a different algorithm
54       */
55      @Test
56      public void testValues() {
57          boolean printResults = false;
58          if (printResults) {
59              System.out.println("\nTest TAR Values\n");
60          }
61          // Run test
62          this.genericTestValues(printResults);
63      }
64  
65      /**
66       * Test the values of the state derivatives
67       * using a numerical finite differences calculation as a reference
68       */
69      @Test
70      public void testStateDerivatives() {
71  
72          boolean printResults = false;
73          if (printResults) {
74              System.out.println("\nTest TAR State Derivatives - Finite Differences comparison\n");
75          }
76          // Run test
77          boolean isModifier = false;
78          double refErrorsPMedian = 1.4e-6;
79          double refErrorsPMean   = 1.4e-06;
80          double refErrorsPMax    = 2.6e-06;
81          double refErrorsVMedian = 8.2e-05;
82          double refErrorsVMean   = 3.6e-04;
83          double refErrorsVMax    = 1.4e-02;
84  
85          this.genericTestStateDerivatives(isModifier, printResults,
86                                           refErrorsPMedian, refErrorsPMean, refErrorsPMax,
87                                           refErrorsVMedian, refErrorsVMean, refErrorsVMax);
88      }
89  
90      /**
91       * Test the values of the state derivatives with modifier
92       * using a numerical finite differences calculation as a reference
93       */
94      @Test
95      public void testStateDerivativesWithModifier() {
96  
97          boolean printResults = false;
98          if (printResults) {
99              System.out.println("\nTest TAR State Derivatives with Modifier - Finite Differences comparison\n");
100         }
101         // Run test
102         boolean isModifier = true;
103         double refErrorsPMedian = 1.4e-6;
104         double refErrorsPMean   = 1.4e-06;
105         double refErrorsPMax    = 2.6e-06;
106         double refErrorsVMedian = 8.2e-05;
107         double refErrorsVMean   = 3.6e-04;
108         double refErrorsVMax    = 1.4e-2;
109 
110         this.genericTestStateDerivatives(isModifier, printResults,
111                                          refErrorsPMedian, refErrorsPMean, refErrorsPMax,
112                                          refErrorsVMedian, refErrorsVMean, refErrorsVMax);
113     }
114 
115     /**
116      * Test the values of the parameter derivatives
117      * using a numerical finite differences calculation as a reference
118      */
119     @Test
120     public void testParameterDerivatives() {
121 
122         // Print the results ?
123         boolean printResults = false;
124 
125         if (printResults) {
126             System.out.println("\nTest TAR Parameter Derivatives - Finite Differences comparison\n");
127         }
128         // Run test
129         boolean isModifier = false;
130         double refErrorQMMedian = 2.6e-6;
131         double refErrorQMMean   = 2.5e-6;
132         double refErrorQMMax    = 5.6e-6;
133         double refErrorQSMedian = 3.7e-7;
134         double refErrorQSMean   = 3.6e-7;
135         double refErrorQSMax    = 7.7e-7;
136         this.genericTestParameterDerivatives(isModifier, printResults,
137                                              refErrorQMMedian, refErrorQMMean, refErrorQMMax,
138                                              refErrorQSMedian, refErrorQSMean, refErrorQSMax);
139 
140     }
141 
142     /**
143      * Test the values of the parameter derivatives with modifier
144      * using a numerical finite differences calculation as a reference
145      */
146     @Test
147     public void testParameterDerivativesWithModifier() {
148 
149         // Print the results ?
150         boolean printResults = false;
151 
152         if (printResults) {
153             System.out.println("\nTest TAR Parameter Derivatives with Modifier - Finite Differences comparison\n");
154         }
155         // Run test
156         boolean isModifier = true;
157         double refErrorQMMedian = 2.6e-6;
158         double refErrorQMMean   = 2.5e-6;
159         double refErrorQMMax    = 5.6e-6;
160         double refErrorQSMedian = 3.7e-7;
161         double refErrorQSMean   = 3.6e-7;
162         double refErrorQSMax    = 7.7e-7;
163         this.genericTestParameterDerivatives(isModifier, printResults,
164                                              refErrorQMMedian, refErrorQMMean, refErrorQMMax,
165                                              refErrorQSMedian, refErrorQSMean, refErrorQSMax);
166     }
167 
168     /**
169      * Generic test function for values of the TAR
170      * @param printResults Print the results ?
171      */
172     void genericTestValues(final boolean printResults) {
173 
174         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
175         //Context context = EstimationTestUtils.geoStationnaryContext();
176 
177         final NumericalPropagatorBuilder propagatorBuilder =
178                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngle.TRUE, true,
179                                               1.0e-6, 60.0, 0.001);
180 
181         // create perfect range measurements
182         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
183                                                                            propagatorBuilder);
184         final List<ObservedMeasurement<?>> measurements =
185                         EstimationTestUtils.createMeasurements(propagator,
186                                                                new TurnAroundRangeMeasurementCreator(context),
187                                                                1.0, 3.0, 300.0);
188         propagator.clearStepHandlers();
189 
190         double[] absoluteErrors = new double[measurements.size()];
191         double[] relativeErrors = new double[measurements.size()];
192         int index = 0;
193 
194         // Print the results ? Header
195         if (printResults) {
196            System.out.format(Locale.US, "%-15s  %-15s  %-23s  %-23s  %17s  %17s  %13s %13s%n",
197                               "Primary Station", "secondary Station",
198                               "Measurement Date", "State Date",
199                               "TAR observed [m]", "TAR estimated [m]",
200                               "|ΔTAR| [m]", "rel |ΔTAR|");
201         }
202 
203         // Loop on the measurements
204         for (final ObservedMeasurement<?> measurement : measurements) {
205 
206             final double          meanDelay = measurement.getObservedValue()[0] / Constants.SPEED_OF_LIGHT;
207             final AbsoluteDate    date      = measurement.getDate().shiftedBy(meanDelay);
208             final SpacecraftState state     = propagator.propagate(date);
209 
210             // Values of the TAR & errors
211             final double TARobserved  = measurement.getObservedValue()[0];
212             final EstimatedMeasurement<?> estimated = measurement.estimate(0, 0, new SpacecraftState[] { state });
213             final double TARestimated = estimated.getEstimatedValue()[0];
214 
215             final TimeStampedPVCoordinates[] participants = estimated.getParticipants();
216             Assert.assertEquals(5, participants.length);
217             Assert.assertEquals(0.5 * Constants.SPEED_OF_LIGHT * participants[4].getDate().durationFrom(participants[0].getDate()),
218                                 estimated.getEstimatedValue()[0],
219                                 2.0e-8);
220 
221             absoluteErrors[index] = TARestimated-TARobserved;
222             relativeErrors[index] = FastMath.abs(absoluteErrors[index])/FastMath.abs(TARobserved);
223             index++;
224 
225             // Print results ? Values
226             if (printResults) {
227                 final AbsoluteDate measurementDate = measurement.getDate();
228 
229                 String primaryStationName = ((TurnAroundRange) measurement).getPrimaryStation().getBaseFrame().getName();
230                 String secondaryStationName = ((TurnAroundRange) measurement).getSecondaryStation().getBaseFrame().getName();
231                 System.out.format(Locale.US, "%-15s  %-15s  %-23s  %-23s  %17.6f  %17.6f  %13.6e %13.6e%n",
232                                   primaryStationName, secondaryStationName, measurementDate, date,
233                                  TARobserved, TARestimated,
234                                  FastMath.abs(TARestimated-TARobserved),
235                                  FastMath.abs((TARestimated-TARobserved)/TARobserved));
236             }
237 
238         }
239 
240         // Compute some statistics
241         final double absErrorsMedian = new Median().evaluate(absoluteErrors);
242         final double absErrorsMin    = new Min().evaluate(absoluteErrors);
243         final double absErrorsMax    = new Max().evaluate(absoluteErrors);
244         final double relErrorsMedian = new Median().evaluate(relativeErrors);
245         final double relErrorsMax    = new Max().evaluate(relativeErrors);
246 
247         // Print the results on console ? Final results
248         if (printResults) {
249             System.out.println();
250             System.out.println("Absolute errors median: " +  absErrorsMedian);
251             System.out.println("Absolute errors min   : " +  absErrorsMin);
252             System.out.println("Absolute errors max   : " +  absErrorsMax);
253             System.out.println("Relative errors median: " +  relErrorsMedian);
254             System.out.println("Relative errors max   : " +  relErrorsMax);
255         }
256 
257         // Assert statistical errors
258         Assert.assertEquals(0.0, absErrorsMedian, 1.4e-7);
259         Assert.assertEquals(0.0, absErrorsMin, 5.0e-7);
260         Assert.assertEquals(0.0, absErrorsMax, 4.9e-7);
261         Assert.assertEquals(0.0, relErrorsMedian, 8.9e-15);
262         Assert.assertEquals(0.0, relErrorsMax , 2.9e-14);
263     }
264 
265     void genericTestStateDerivatives(final boolean isModifier, final boolean printResults,
266                                      final double refErrorsPMedian, final double refErrorsPMean, final double refErrorsPMax,
267                                      final double refErrorsVMedian, final double refErrorsVMean, final double refErrorsVMax) {
268 
269         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
270         //Context context = EstimationTestUtils.geoStationnaryContext();
271 
272         final NumericalPropagatorBuilder propagatorBuilder =
273                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngle.TRUE, true,
274                                               1.0e-6, 60.0, 0.001);
275 
276         // create perfect range2 measurements
277         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
278                                                                            propagatorBuilder);
279         final List<ObservedMeasurement<?>> measurements =
280                         EstimationTestUtils.createMeasurements(propagator,
281                                                                new TurnAroundRangeMeasurementCreator(context),
282                                                                1.0, 3.0, 300.0);
283         propagator.clearStepHandlers();
284 
285         double[] errorsP = new double[3 * measurements.size()];
286         double[] errorsV = new double[3 * measurements.size()];
287         int indexP = 0;
288         int indexV = 0;
289 
290         // Print the results ? Header
291         if (printResults) {
292             System.out.format(Locale.US, "%-15s  %-15s  %-23s  %-23s  " +
293                               "%10s  %10s  %10s  " +
294                               "%10s  %10s  %10s  " +
295                               "%10s  %10s  %10s  " +
296                               "%10s  %10s  %10s%n",
297                               "Primary Station", "secondary Station",
298                               "Measurement Date", "State Date",
299                               "ΔdPx", "ΔdPy", "ΔdPz", "ΔdVx", "ΔdVy", "ΔdVz",
300                               "rel ΔdPx", "rel ΔdPy", "rel ΔdPz",
301                               "rel ΔdVx", "rel ΔdVy", "rel ΔdVz");
302         }
303 
304         // Loop on the measurements
305         for (final ObservedMeasurement<?> measurement : measurements) {
306 
307             // Add modifiers if test implies it
308             final TurnAroundRangeTroposphericDelayModifier modifier =
309                             new TurnAroundRangeTroposphericDelayModifier(SaastamoinenModel.getStandardModel());
310             if (isModifier) {
311                 ((TurnAroundRange) measurement).addModifier(modifier);
312             }
313 
314             // We intentionally propagate to a date which is close to the
315             // real spacecraft state but is *not* the accurate date, by
316             // compensating only part of the downlink delay. This is done
317             // in order to validate the partial derivatives with respect
318             // to velocity. If we had chosen the proper state date, the
319             // range would have depended only on the current position but
320             // not on the current velocity.
321             final double          meanDelay = measurement.getObservedValue()[0] / Constants.SPEED_OF_LIGHT;
322             final AbsoluteDate    date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
323             final SpacecraftState state     = propagator.propagate(date);
324             final double[][]      jacobian  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getStateDerivatives(0);
325 
326             // Jacobian reference value
327             final double[][] jacobianRef;
328 
329             // Compute a reference value using finite differences
330             jacobianRef = Differentiation.differentiate(new StateFunction() {
331                 public double[] value(final SpacecraftState state) {
332                     return measurement.estimate(0, 0, new SpacecraftState[] { state }).getEstimatedValue();
333                 }
334             }, measurement.getDimension(), propagator.getAttitudeProvider(),
335                OrbitType.CARTESIAN, PositionAngle.TRUE, 2.0, 3).value(state);
336 
337             Assert.assertEquals(jacobianRef.length, jacobian.length);
338             Assert.assertEquals(jacobianRef[0].length, jacobian[0].length);
339 
340             double [][] dJacobian         = new double[jacobian.length][jacobian[0].length];
341             double [][] dJacobianRelative = new double[jacobian.length][jacobian[0].length];
342             for (int i = 0; i < jacobian.length; ++i) {
343                 for (int j = 0; j < jacobian[i].length; ++j) {
344                     dJacobian[i][j] = jacobian[i][j] - jacobianRef[i][j];
345                     dJacobianRelative[i][j] = FastMath.abs(dJacobian[i][j]/jacobianRef[i][j]);
346 
347                     if (j < 3) {
348                         errorsP[indexP++] = dJacobianRelative[i][j];
349                     } else {
350                         errorsV[indexV++] = dJacobianRelative[i][j];
351                     }
352                 }
353             }
354             // Print results on the console ? Print the Jacobian
355             if (printResults) {
356                 String primaryStationName = ((TurnAroundRange) measurement).getPrimaryStation().getBaseFrame().getName();
357                 String secondaryStationName  = ((TurnAroundRange) measurement).getSecondaryStation().getBaseFrame().getName();
358                 System.out.format(Locale.US, "%-15s  %-15s  %-23s  %-23s  " +
359                                   "%10.3e  %10.3e  %10.3e  " +
360                                   "%10.3e  %10.3e  %10.3e  " +
361                                   "%10.3e  %10.3e  %10.3e  " +
362                                   "%10.3e  %10.3e  %10.3e%n",
363                                   primaryStationName, secondaryStationName, measurement.getDate(), date,
364                                   dJacobian[0][0], dJacobian[0][1], dJacobian[0][2],
365                                   dJacobian[0][3], dJacobian[0][4], dJacobian[0][5],
366                                   dJacobianRelative[0][0], dJacobianRelative[0][1], dJacobianRelative[0][2],
367                                   dJacobianRelative[0][3], dJacobianRelative[0][4], dJacobianRelative[0][5]);
368             }
369         } // End loop on the measurements
370 
371         // Compute some statistics
372         final double errorsPMedian = new Median().evaluate(errorsP);
373         final double errorsPMean   = new Mean().evaluate(errorsP);
374         final double errorsPMax    = new Max().evaluate(errorsP);
375         final double errorsVMedian = new Median().evaluate(errorsV);
376         final double errorsVMean   = new Mean().evaluate(errorsV);
377         final double errorsVMax    = new Max().evaluate(errorsV);
378 
379 
380         // Print the results on console ? Final results
381         if (printResults) {
382             System.out.println();
383             System.out.format(Locale.US, "Relative errors dR/dP -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
384                               errorsPMedian, errorsPMean, errorsPMax);
385             System.out.format(Locale.US, "Relative errors dR/dV -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
386                               errorsVMedian, errorsVMean, errorsVMax);
387         }
388 
389         Assert.assertEquals(0.0, errorsPMedian, refErrorsPMedian);
390         Assert.assertEquals(0.0, errorsPMean, refErrorsPMean);
391         Assert.assertEquals(0.0, errorsPMax, refErrorsPMax);
392         Assert.assertEquals(0.0, errorsVMedian, refErrorsVMedian);
393         Assert.assertEquals(0.0, errorsVMean, refErrorsVMean);
394         Assert.assertEquals(0.0, errorsVMax, refErrorsVMax);
395     }
396 
397 
398     void genericTestParameterDerivatives(final boolean isModifier, final boolean printResults,
399                                          final double refErrorQMMedian, final double refErrorQMMean, final double refErrorQMMax,
400                                          final double refErrorQSMedian, final double refErrorQSMean, final double refErrorQSMax) {
401 
402         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
403 
404         final NumericalPropagatorBuilder propagatorBuilder =
405                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngle.TRUE, true,
406                                               1.0e-6, 60.0, 0.001);
407 
408         // Create perfect TAR measurements
409         for (Map.Entry<GroundStation, GroundStation> entry : context.TARstations.entrySet()) {
410             final GroundStation    primaryStation = entry.getKey();
411             final GroundStation    secondaryStation  = entry.getValue();
412             primaryStation.getClockOffsetDriver().setSelected(true);
413             primaryStation.getEastOffsetDriver().setSelected(true);
414             primaryStation.getNorthOffsetDriver().setSelected(true);
415             primaryStation.getZenithOffsetDriver().setSelected(true);
416             secondaryStation.getClockOffsetDriver().setSelected(false);
417             secondaryStation.getEastOffsetDriver().setSelected(true);
418             secondaryStation.getNorthOffsetDriver().setSelected(true);
419             secondaryStation.getZenithOffsetDriver().setSelected(true);
420         }
421         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
422                                                                            propagatorBuilder);
423         final List<ObservedMeasurement<?>> measurements =
424                         EstimationTestUtils.createMeasurements(propagator,
425                                                                new TurnAroundRangeMeasurementCreator(context),
426                                                                1.0, 3.0, 300.0);
427         propagator.clearStepHandlers();
428 
429         // Print results on console ? Header
430         if (printResults) {
431             System.out.format(Locale.US, "%-15s %-15s %-23s  %-23s  " +
432                               "%10s  %10s  %10s  " +
433                               "%10s  %10s  %10s  " +
434                               "%10s  %10s  %10s  " +
435                               "%10s  %10s  %10s%n",
436                               "Primary Station", "secondary Station",
437                               "Measurement Date", "State Date",
438                               "ΔdQMx", "rel ΔdQMx",
439                               "ΔdQMy", "rel ΔdQMy",
440                               "ΔdQMz", "rel ΔdQMz",
441                               "ΔdQSx", "rel ΔdQSx",
442                               "ΔdQSy", "rel ΔdQSy",
443                               "ΔdQSz", "rel ΔdQSz");
444          }
445 
446         // List to store the results for primary and secondary station
447         final List<Double> relErrorQMList = new ArrayList<Double>();
448         final List<Double> relErrorQSList = new ArrayList<Double>();
449 
450         // Loop on the measurements
451         for (final ObservedMeasurement<?> measurement : measurements) {
452 
453           // Add modifiers if test implies it
454           final TurnAroundRangeTroposphericDelayModifier modifier = new TurnAroundRangeTroposphericDelayModifier(SaastamoinenModel.getStandardModel());
455           if (isModifier) {
456               ((TurnAroundRange) measurement).addModifier(modifier);
457           }
458 
459             // parameter corresponding to station position offset
460             final GroundStation primaryStationParameter = ((TurnAroundRange) measurement).getPrimaryStation();
461             final GroundStation secondaryStationParameter = ((TurnAroundRange) measurement).getSecondaryStation();
462 
463             // We intentionally propagate to a date which is close to the
464             // real spacecraft state but is *not* the accurate date, by
465             // compensating only part of the downlink delay. This is done
466             // in order to validate the partial derivatives with respect
467             // to velocity. If we had chosen the proper state date, the
468             // range would have depended only on the current position but
469             // not on the current velocity.
470             final double          meanDelay = measurement.getObservedValue()[0] / Constants.SPEED_OF_LIGHT;
471             final AbsoluteDate    date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
472             final SpacecraftState state     = propagator.propagate(date);
473             final ParameterDriver[] drivers = new ParameterDriver[] {
474                                                                      primaryStationParameter.getEastOffsetDriver(),
475                                                                      primaryStationParameter.getNorthOffsetDriver(),
476                                                                      primaryStationParameter.getZenithOffsetDriver(),
477                                                                      secondaryStationParameter.getEastOffsetDriver(),
478                                                                      secondaryStationParameter.getNorthOffsetDriver(),
479                                                                      secondaryStationParameter.getZenithOffsetDriver(),
480             };
481 
482             // Print results on console ? Stations' names
483             if (printResults) {
484                 String primaryStationName  = primaryStationParameter.getBaseFrame().getName();
485                 String secondaryStationName  = secondaryStationParameter.getBaseFrame().getName();
486                 System.out.format(Locale.US, "%-15s %-15s %-23s  %-23s  ",
487                                   primaryStationName, secondaryStationName, measurement.getDate(), date);
488             }
489 
490             // Loop on the parameters
491             for (int i = 0; i < 6; ++i) {
492                 final double[] gradient  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getParameterDerivatives(drivers[i]);
493                 Assert.assertEquals(1, measurement.getDimension());
494                 Assert.assertEquals(1, gradient.length);
495 
496                 // Compute a reference value using finite differences
497                 final ParameterFunction dMkdP =
498                                 Differentiation.differentiate(new ParameterFunction() {
499                                     /** {@inheritDoc} */
500                                     @Override
501                                     public double value(final ParameterDriver parameterDriver) {
502                                         return measurement.estimate(0, 0, new SpacecraftState[] { state }).getEstimatedValue()[0];
503                                     }
504                                 }, 3, 20.0 * drivers[i].getScale());
505                 final double ref = dMkdP.value(drivers[i]);
506 
507                 // Deltas
508                 double dGradient         = gradient[0] - ref;
509                 double dGradientRelative = FastMath.abs(dGradient/ref);
510 
511                 // Print results on console ? Gradient difference
512                 if (printResults) {
513                     System.out.format(Locale.US, "%10.3e  %10.3e  ", dGradient, dGradientRelative);
514                 }
515 
516                 // Add relative error to the list
517                 if (i<3) { relErrorQMList.add(dGradientRelative);}
518                 else     { relErrorQSList.add(dGradientRelative);}
519 
520             } // End for loop on the parameters
521             if (printResults) {
522                 System.out.format(Locale.US, "%n");
523             }
524         } // End for loop on the measurements
525 
526         // Convert error list to double[]
527         final double relErrorQM[] = relErrorQMList.stream().mapToDouble(Double::doubleValue).toArray();
528         final double relErrorQS[] = relErrorQSList.stream().mapToDouble(Double::doubleValue).toArray();
529 
530         // Compute statistics
531         final double relErrorsQMMedian = new Median().evaluate(relErrorQM);
532         final double relErrorsQMMean   = new Mean().evaluate(relErrorQM);
533         final double relErrorsQMMax    = new Max().evaluate(relErrorQM);
534 
535         final double relErrorsQSMedian = new Median().evaluate(relErrorQS);
536         final double relErrorsQSMean   = new Mean().evaluate(relErrorQS);
537         final double relErrorsQSMax    = new Max().evaluate(relErrorQS);
538 
539 
540         // Print the results on console ?
541         if (printResults) {
542             System.out.println();
543             System.out.format(Locale.US, "Relative errors dR/dQ primary station -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
544                               relErrorsQMMedian, relErrorsQMMean, relErrorsQMMax);
545             System.out.format(Locale.US, "Relative errors dR/dQ secondary station  -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
546                               relErrorsQSMedian, relErrorsQSMean, relErrorsQSMax);
547         }
548 
549         // Check values
550         Assert.assertEquals(0.0, relErrorsQMMedian, refErrorQMMedian);
551         Assert.assertEquals(0.0, relErrorsQMMean, refErrorQMMean);
552         Assert.assertEquals(0.0, relErrorsQMMax, refErrorQMMax);
553         Assert.assertEquals(0.0, relErrorsQSMedian, refErrorQSMedian);
554         Assert.assertEquals(0.0, relErrorsQSMean, refErrorQSMean);
555         Assert.assertEquals(0.0, relErrorsQSMax, refErrorQSMax);
556 
557     }
558 }