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.gnss;
18  
19  import java.util.ArrayList;
20  import java.util.Comparator;
21  import java.util.List;
22  import java.util.Locale;
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.Utils;
32  import org.orekit.bodies.GeodeticPoint;
33  import org.orekit.bodies.OneAxisEllipsoid;
34  import org.orekit.estimation.Context;
35  import org.orekit.estimation.EstimationTestUtils;
36  import org.orekit.estimation.measurements.EstimatedMeasurement;
37  import org.orekit.estimation.measurements.GroundStation;
38  import org.orekit.estimation.measurements.ObservableSatellite;
39  import org.orekit.estimation.measurements.ObservedMeasurement;
40  import org.orekit.estimation.measurements.modifiers.PhaseIonosphericDelayModifier;
41  import org.orekit.estimation.measurements.modifiers.PhaseTroposphericDelayModifier;
42  import org.orekit.frames.FramesFactory;
43  import org.orekit.frames.TopocentricFrame;
44  import org.orekit.gnss.Frequency;
45  import org.orekit.models.earth.ionosphere.IonosphericModel;
46  import org.orekit.models.earth.ionosphere.KlobucharIonoModel;
47  import org.orekit.models.earth.troposphere.EstimatedTroposphericModel;
48  import org.orekit.models.earth.troposphere.NiellMappingFunctionModel;
49  import org.orekit.orbits.OrbitType;
50  import org.orekit.orbits.PositionAngle;
51  import org.orekit.propagation.Propagator;
52  import org.orekit.propagation.SpacecraftState;
53  import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
54  import org.orekit.time.AbsoluteDate;
55  import org.orekit.utils.Constants;
56  import org.orekit.utils.Differentiation;
57  import org.orekit.utils.IERSConventions;
58  import org.orekit.utils.ParameterDriver;
59  import org.orekit.utils.ParameterFunction;
60  import org.orekit.utils.StateFunction;
61  import org.orekit.utils.TimeStampedPVCoordinates;
62  
63  public class PhaseTest {
64  
65      /**
66       * Test the values of the phase comparing the observed values and the estimated values
67       * Both are calculated with a different algorithm
68       */
69      @Test
70      public void testValues() {
71          boolean printResults = false;
72          if (printResults) {
73              System.out.println("\nTest Phase Values\n");
74          }
75          // Run test
76          this.genericTestValues(printResults);
77      }
78  
79      /**
80       * Test the values of the state derivatives using a numerical
81       * finite differences calculation as a reference
82       */
83      @Test
84      public void testStateDerivatives() {
85  
86          boolean printResults = false;
87          if (printResults) {
88              System.out.println("\nTest Range Phase Derivatives - Finite Differences Comparison\n");
89          }
90          // Run test
91          double refErrorsPMedian = 5.2e-10;
92          double refErrorsPMean   = 3.5e-09;
93          double refErrorsPMax    = 1.5e-07;
94          double refErrorsVMedian = 2.0e-05;
95          double refErrorsVMean   = 5.8e-05;
96          double refErrorsVMax    = 2.1e-03;
97          this.genericTestStateDerivatives(printResults,
98                                           refErrorsPMedian, refErrorsPMean, refErrorsPMax,
99                                           refErrorsVMedian, refErrorsVMean, refErrorsVMax);
100     }
101 
102     /**
103      * Test the values of the state derivatives with modifier using a numerical
104      * finite differences calculation as a reference
105      */
106     @Test
107     public void testStateDerivativesWithModifier() {
108 
109         boolean printResults = false;
110         if (printResults) {
111             System.out.println("\nTest Phase State Derivatives with Modifier - Finite Differences Comparison\n");
112         }
113         // Run test
114         double refErrorsPMedian = 5.2e-10;
115         double refErrorsPMean   = 3.5e-09;
116         double refErrorsPMax    = 1.5e-07;
117         double refErrorsVMedian = 2.0e-05;
118         double refErrorsVMean   = 5.8e-05;
119         double refErrorsVMax    = 2.1e-03;
120         this.genericTestStateDerivatives(printResults,
121                                          refErrorsPMedian, refErrorsPMean, refErrorsPMax,
122                                          refErrorsVMedian, refErrorsVMean, refErrorsVMax);
123     }
124 
125     /**
126      * Test the values of the parameters' derivatives using a numerical
127      * finite differences calculation as a reference
128      */
129     @Test
130     public void testParameterDerivatives() {
131 
132         // Print the results ?
133         boolean printResults = false;
134 
135         if (printResults) {
136             System.out.println("\nTest Phase Parameter Derivatives - Finite Differences Comparison\n");
137         }
138         // Run test
139         double refErrorsMedian = 1.4e-10;
140         double refErrorsMean   = 5.0e-8;
141         double refErrorsMax    = 5.1e-6;
142         this.genericTestParameterDerivatives(printResults,
143                                              refErrorsMedian, refErrorsMean, refErrorsMax);
144 
145     }
146 
147     /**
148      * Generic test function for values of the phase
149      * @param printResults Print the results ?
150      */
151     void genericTestValues(final boolean printResults) {
152 
153         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
154 
155         final NumericalPropagatorBuilder propagatorBuilder =
156                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngle.TRUE, true,
157                                               1.0e-6, 60.0, 0.001);
158 
159         // Create perfect phase measurements
160         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
161                                                                            propagatorBuilder);
162         final double groundClockOffset =  12.0e-6;
163         for (final GroundStation station : context.stations) {
164             station.getClockOffsetDriver().setValue(groundClockOffset);
165         }
166         final int    ambiguity         = 1234;
167         final double satClockOffset    = 345.0e-6;
168         final List<ObservedMeasurement<?>> measurements =
169                         EstimationTestUtils.createMeasurements(propagator,
170                                                                new PhaseMeasurementCreator(context,
171                                                                                            Frequency.E01,
172                                                                                            ambiguity,
173                                                                                            satClockOffset),
174                                                                1.0, 3.0, 300.0);
175 
176         // Lists for results' storage - Used only for derivatives with respect to state
177         // "final" value to be seen by "handleStep" function of the propagator
178         final List<Double> absoluteErrors = new ArrayList<>();
179         final List<Double> relativeErrors = new ArrayList<>();
180 
181         // Use a lambda function to implement "handleStep" function
182         propagator.setStepHandler(interpolator -> {
183 
184             for (final ObservedMeasurement<?> measurement : measurements) {
185 
186                 //  Play test if the measurement date is between interpolator previous and current date
187                 if ((measurement.getDate().durationFrom(interpolator.getPreviousState().getDate()) > 0.) &&
188                     (measurement.getDate().durationFrom(interpolator.getCurrentState().getDate())  <=  0.)) {
189                     // We intentionally propagate to a date which is close to the
190                     // real spacecraft state but is *not* the accurate date, by
191                     // compensating only part of the downlink delay. This is done
192                     // in order to validate the partial derivatives with respect
193                     // to velocity. If we had chosen the proper state date, the
194                     // range would have depended only on the current position but
195                     // not on the current velocity.
196                     final double          meanDelay = measurement.getObservedValue()[0] / Constants.SPEED_OF_LIGHT;
197                     final AbsoluteDate    date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
198                     final SpacecraftState state     = interpolator.getInterpolatedState(date);
199 
200                     // Values of the Phase & errors
201                     final double phaseObserved  = measurement.getObservedValue()[0];
202                     final EstimatedMeasurement<?> estimated = measurement.estimate(0, 0, new SpacecraftState[] { state });
203 
204                     final TimeStampedPVCoordinates[] participants = estimated.getParticipants();
205                     Assert.assertEquals(2, participants.length);
206                     final double dt = participants[1].getDate().durationFrom(participants[0].getDate());
207                     Assert.assertEquals(1.0e6 * Frequency.E01.getMHzFrequency() * (dt + groundClockOffset - satClockOffset) + ambiguity,
208                                         estimated.getEstimatedValue()[0],
209                                         1.0e-7);
210 
211                     final double phaseEstimated = estimated.getEstimatedValue()[0];
212                     final double absoluteError = phaseEstimated - phaseObserved;
213                     absoluteErrors.add(absoluteError);
214                     relativeErrors.add(FastMath.abs(absoluteError) / FastMath.abs(phaseObserved));
215 
216                     // Print results on console ?
217                     if (printResults) {
218                         final AbsoluteDate measurementDate = measurement.getDate();
219                         String stationName = ((Phase) measurement).getStation().getBaseFrame().getName();
220 
221                         System.out.format(Locale.US, "%-15s  %-23s  %-23s     %19.6f      %19.6f    %13.6e   %13.6e%n",
222                                          stationName, measurementDate, date,
223                                          phaseObserved, phaseEstimated,
224                                          FastMath.abs(phaseEstimated - phaseObserved),
225                                          FastMath.abs((phaseEstimated - phaseObserved) / phaseObserved));
226                     }
227 
228                 } // End if measurement date between previous and current interpolator step
229             } // End for loop on the measurements
230         }); // End lambda function handlestep
231 
232         // Print results on console ? Header
233         if (printResults) {
234             System.out.format(Locale.US, "%-15s  %-23s  %-23s  %19s  %19s   %13s   %13s%n",
235                               "Station", "Measurement Date", "State Date",
236                               "Phase observed [cycle]", "Phase estimated [cycle]",
237                               "ΔPhase [cycle]", "rel ΔPhase");
238         }
239 
240         // Rewind the propagator to initial date
241         propagator.propagate(context.initialOrbit.getDate());
242 
243         // Sort measurements chronologically
244         measurements.sort(Comparator.naturalOrder());
245 
246         // Propagate to final measurement's date
247         propagator.propagate(measurements.get(measurements.size()-1).getDate());
248 
249         // Convert lists to double array
250         final double[] absErrors = absoluteErrors.stream().mapToDouble(Double::doubleValue).toArray();
251         final double[] relErrors = relativeErrors.stream().mapToDouble(Double::doubleValue).toArray();
252 
253         // Statistics' assertion
254         final double absErrorsMedian = new Median().evaluate(absErrors);
255         final double absErrorsMin    = new Min().evaluate(absErrors);
256         final double absErrorsMax    = new Max().evaluate(absErrors);
257         final double relErrorsMedian = new Median().evaluate(relErrors);
258         final double relErrorsMax    = new Max().evaluate(relErrors);
259 
260         // Print the results on console ? Final results
261         if (printResults) {
262             System.out.println();
263             System.out.println("Absolute errors median: " +  absErrorsMedian);
264             System.out.println("Absolute errors min   : " +  absErrorsMin);
265             System.out.println("Absolute errors max   : " +  absErrorsMax);
266             System.out.println("Relative errors median: " +  relErrorsMedian);
267             System.out.println("Relative errors max   : " +  relErrorsMax);
268         }
269 
270         Assert.assertEquals(0.0, absErrorsMedian, 1.4e-7);
271         Assert.assertEquals(0.0, absErrorsMin,    1.3e-6);
272         Assert.assertEquals(0.0, absErrorsMax,    1.5e-6);
273         Assert.assertEquals(0.0, relErrorsMedian, 9.1e-15);
274         Assert.assertEquals(0.0, relErrorsMax,    2.8e-14);
275 
276 
277     }
278 
279     void genericTestStateDerivatives(final boolean printResults,
280                                      final double refErrorsPMedian, final double refErrorsPMean, final double refErrorsPMax,
281                                      final double refErrorsVMedian, final double refErrorsVMean, final double refErrorsVMax) {
282 
283         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
284 
285         final NumericalPropagatorBuilder propagatorBuilder =
286                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngle.TRUE, true,
287                                               1.0e-6, 60.0, 0.001);
288 
289         // Create perfect range measurements
290         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
291                                                                            propagatorBuilder);
292         final double groundClockOffset =  12.0e-6;
293         for (final GroundStation station : context.stations) {
294             station.getClockOffsetDriver().setValue(groundClockOffset);
295         }
296         final int    ambiguity         = 1234;
297         final double satClockOffset    = 345.0e-6;
298         final List<ObservedMeasurement<?>> measurements =
299                         EstimationTestUtils.createMeasurements(propagator,
300                                                                new PhaseMeasurementCreator(context,
301                                                                                            Frequency.E01,
302                                                                                            ambiguity,
303                                                                                            satClockOffset),
304                                                                1.0, 3.0, 300.0);
305 
306         // Lists for results' storage - Used only for derivatives with respect to state
307         // "final" value to be seen by "handleStep" function of the propagator
308         final List<Double> errorsP = new ArrayList<Double>();
309         final List<Double> errorsV = new ArrayList<Double>();
310 
311         // Use a lambda function to implement "handleStep" function
312         propagator.setStepHandler(interpolator -> {
313 
314             for (final ObservedMeasurement<?> measurement : measurements) {
315 
316                 //  Play test if the measurement date is between interpolator previous and current date
317                 if ((measurement.getDate().durationFrom(interpolator.getPreviousState().getDate()) > 0.) &&
318                     (measurement.getDate().durationFrom(interpolator.getCurrentState().getDate())  <=  0.)) {
319 
320                     // We intentionally propagate to a date which is close to the
321                     // real spacecraft state but is *not* the accurate date, by
322                     // compensating only part of the downlink delay. This is done
323                     // in order to validate the partial derivatives with respect
324                     // to velocity. If we had chosen the proper state date, the
325                     // range would have depended only on the current position but
326                     // not on the current velocity.
327                     final double          meanDelay = measurement.getObservedValue()[0] / Constants.SPEED_OF_LIGHT;
328                     final AbsoluteDate    date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
329                     final SpacecraftState state     = interpolator.getInterpolatedState(date);
330                     final double[][]      jacobian  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getStateDerivatives(0);
331 
332                     // Jacobian reference value
333                     final double[][] jacobianRef;
334 
335                     // Compute a reference value using finite differences
336                     jacobianRef = Differentiation.differentiate(new StateFunction() {
337                         public double[] value(final SpacecraftState state) {
338                             return measurement.estimate(0, 0, new SpacecraftState[] { state }).getEstimatedValue();
339                         }
340                     }, measurement.getDimension(), propagator.getAttitudeProvider(),
341                        OrbitType.CARTESIAN, PositionAngle.TRUE, 2.0, 3).value(state);
342 
343                     Assert.assertEquals(jacobianRef.length, jacobian.length);
344                     Assert.assertEquals(jacobianRef[0].length, jacobian[0].length);
345 
346                     // Errors & relative errors on the Jacobian
347                     double [][] dJacobian         = new double[jacobian.length][jacobian[0].length];
348                     double [][] dJacobianRelative = new double[jacobian.length][jacobian[0].length];
349                     for (int i = 0; i < jacobian.length; ++i) {
350                         for (int j = 0; j < jacobian[i].length; ++j) {
351                             dJacobian[i][j] = jacobian[i][j] - jacobianRef[i][j];
352                             dJacobianRelative[i][j] = FastMath.abs(dJacobian[i][j]/jacobianRef[i][j]);
353 
354                             if (j < 3) { errorsP.add(dJacobianRelative[i][j]);
355                             } else { errorsV.add(dJacobianRelative[i][j]); }
356                         }
357                     }
358                     // Print values in console ?
359                     if (printResults) {
360                         String stationName  = ((Phase) measurement).getStation().getBaseFrame().getName();
361                         System.out.format(Locale.US, "%-15s  %-23s  %-23s  " +
362                                           "%10.3e  %10.3e  %10.3e  " +
363                                           "%10.3e  %10.3e  %10.3e  " +
364                                           "%10.3e  %10.3e  %10.3e  " +
365                                           "%10.3e  %10.3e  %10.3e%n",
366                                           stationName, measurement.getDate(), date,
367                                           dJacobian[0][0], dJacobian[0][1], dJacobian[0][2],
368                                           dJacobian[0][3], dJacobian[0][4], dJacobian[0][5],
369                                           dJacobianRelative[0][0], dJacobianRelative[0][1], dJacobianRelative[0][2],
370                                           dJacobianRelative[0][3], dJacobianRelative[0][4], dJacobianRelative[0][5]);
371                     }
372                 } // End if measurement date between previous and current interpolator step
373             } // End for loop on the measurements
374         });
375 
376         // Print results on console ?
377         if (printResults) {
378             System.out.format(Locale.US, "%-15s  %-23s  %-23s  " +
379                             "%10s  %10s  %10s  " +
380                             "%10s  %10s  %10s  " +
381                             "%10s  %10s  %10s  " +
382                             "%10s  %10s  %10s%n",
383                             "Station", "Measurement Date", "State Date",
384                             "ΔdPx", "ΔdPy", "ΔdPz", "ΔdVx", "ΔdVy", "ΔdVz",
385                             "rel ΔdPx", "rel ΔdPy", "rel ΔdPz",
386                             "rel ΔdVx", "rel ΔdVy", "rel ΔdVz");
387         }
388 
389         // Rewind the propagator to initial date
390         propagator.propagate(context.initialOrbit.getDate());
391 
392         // Sort measurements chronologically
393         measurements.sort(Comparator.naturalOrder());
394 
395         // Propagate to final measurement's date
396         propagator.propagate(measurements.get(measurements.size()-1).getDate());
397 
398         // Convert lists to double[] and evaluate some statistics
399         final double relErrorsP[] = errorsP.stream().mapToDouble(Double::doubleValue).toArray();
400         final double relErrorsV[] = errorsV.stream().mapToDouble(Double::doubleValue).toArray();
401 
402         final double errorsPMedian = new Median().evaluate(relErrorsP);
403         final double errorsPMean   = new Mean().evaluate(relErrorsP);
404         final double errorsPMax    = new Max().evaluate(relErrorsP);
405         final double errorsVMedian = new Median().evaluate(relErrorsV);
406         final double errorsVMean   = new Mean().evaluate(relErrorsV);
407         final double errorsVMax    = new Max().evaluate(relErrorsV);
408 
409         // Print the results on console ?
410         if (printResults) {
411             System.out.println();
412             System.out.format(Locale.US, "Relative errors dΦ/dP -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
413                               errorsPMedian, errorsPMean, errorsPMax);
414             System.out.format(Locale.US, "Relative errors dΦ/dV -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
415                               errorsVMedian, errorsVMean, errorsVMax);
416         }
417 
418         Assert.assertEquals(0.0, errorsPMedian, refErrorsPMedian);
419         Assert.assertEquals(0.0, errorsPMean, refErrorsPMean);
420         Assert.assertEquals(0.0, errorsPMax, refErrorsPMax);
421         Assert.assertEquals(0.0, errorsVMedian, refErrorsVMedian);
422         Assert.assertEquals(0.0, errorsVMean, refErrorsVMean);
423         Assert.assertEquals(0.0, errorsVMax, refErrorsVMax);
424     }
425 
426     void genericTestParameterDerivatives(final boolean printResults,
427                                          final double refErrorsMedian, final double refErrorsMean, final double refErrorsMax) {
428 
429         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
430 
431         final NumericalPropagatorBuilder propagatorBuilder =
432                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngle.TRUE, true,
433                                               1.0e-6, 60.0, 0.001);
434 
435         // Create perfect range measurements
436         final double groundClockOffset =  12.0e-6;
437         for (final GroundStation station : context.stations) {
438             station.getClockOffsetDriver().setValue(groundClockOffset);
439         }
440         final int    ambiguity         = 1234;
441         final double satClockOffset    = 345.0e-6;
442         final PhaseMeasurementCreator creator = new PhaseMeasurementCreator(context, Frequency.E01,
443                                                                             ambiguity,
444                                                                             satClockOffset);
445         creator.getSatellite().getClockOffsetDriver().setSelected(true);
446         for (final GroundStation station : context.stations) {
447             station.getClockOffsetDriver().setSelected(true);
448             station.getEastOffsetDriver().setSelected(true);
449             station.getNorthOffsetDriver().setSelected(true);
450             station.getZenithOffsetDriver().setSelected(true);
451         }
452         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
453                                                                            propagatorBuilder);
454         final List<ObservedMeasurement<?>> measurements =
455                         EstimationTestUtils.createMeasurements(propagator, creator, 1.0, 3.0, 300.0);
456 
457         // List to store the results
458         final List<Double> relErrorList = new ArrayList<Double>();
459 
460         // Use a lambda function to implement "handleStep" function
461         propagator.setStepHandler(interpolator -> {
462 
463             for (final ObservedMeasurement<?> measurement : measurements) {
464 
465                 //  Play test if the measurement date is between interpolator previous and current date
466                 if ((measurement.getDate().durationFrom(interpolator.getPreviousState().getDate()) > 0.) &&
467                     (measurement.getDate().durationFrom(interpolator.getCurrentState().getDate())  <=  0.)) {
468 
469                     // Parameter corresponding to station position offset
470                     final GroundStation stationParameter = ((Phase) measurement).getStation();
471 
472                     // We intentionally propagate to a date which is close to the
473                     // real spacecraft state but is *not* the accurate date, by
474                     // compensating only part of the downlink delay. This is done
475                     // in order to validate the partial derivatives with respect
476                     // to velocity. If we had chosen the proper state date, the
477                     // range would have depended only on the current position but
478                     // not on the current velocity.
479                     final double          meanDelay = measurement.getObservedValue()[0] / Constants.SPEED_OF_LIGHT;
480                     final AbsoluteDate    date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
481                     final SpacecraftState state     = interpolator.getInterpolatedState(date);
482                     final ParameterDriver[] drivers = new ParameterDriver[] {
483                         stationParameter.getClockOffsetDriver(),
484                         stationParameter.getEastOffsetDriver(),
485                         stationParameter.getNorthOffsetDriver(),
486                         stationParameter.getZenithOffsetDriver(),
487                         measurement.getSatellites().get(0).getClockOffsetDriver()
488                     };
489 
490                     if (printResults) {
491                         String stationName  = ((Phase) measurement).getStation().getBaseFrame().getName();
492                         System.out.format(Locale.US, "%-15s  %-23s  %-23s  ",
493                                           stationName, measurement.getDate(), date);
494                     }
495 
496                     for (int i = 0; i < drivers.length; ++i) {
497                         final double[] gradient  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getParameterDerivatives(drivers[i]);
498                         Assert.assertEquals(1, measurement.getDimension());
499                         Assert.assertEquals(1, gradient.length);
500 
501                         // Compute a reference value using finite differences
502                         final ParameterFunction dMkdP =
503                                         Differentiation.differentiate(new ParameterFunction() {
504                                             /** {@inheritDoc} */
505                                             @Override
506                                             public double value(final ParameterDriver parameterDriver) {
507                                                 return measurement.estimate(0, 0, new SpacecraftState[] { state }).getEstimatedValue()[0];
508                                             }
509                                         }, 3, 20.0 * drivers[i].getScale());
510                         final double ref = dMkdP.value(drivers[i]);
511 
512                         if (printResults) {
513                             System.out.format(Locale.US, "%10.3e  %10.3e  ", gradient[0]-ref, FastMath.abs((gradient[0]-ref)/ref));
514                         }
515 
516                         final double relError = FastMath.abs((ref-gradient[0])/ref);
517                         relErrorList.add(relError);
518 //                        Assert.assertEquals(ref, gradient[0], 6.1e-5 * FastMath.abs(ref));
519                     }
520                     if (printResults) {
521                         System.out.format(Locale.US, "%n");
522                     }
523 
524                 } // End if measurement date between previous and current interpolator step
525             } // End for loop on the measurements
526         });
527 
528         // Rewind the propagator to initial date
529         propagator.propagate(context.initialOrbit.getDate());
530 
531         // Sort measurements chronologically
532         measurements.sort(Comparator.naturalOrder());
533 
534         // Print results ? Header
535         if (printResults) {
536             System.out.format(Locale.US, "%-15s  %-23s  %-23s  " +
537                               "%10s  %10s  %10s  %10s  %10s  %10s  %10s  %10s  %10s  %10s%n",
538                               "Station", "Measurement Date", "State Date",
539                               "Δt",    "rel Δt",
540                               "ΔdQx",  "rel ΔdQx",
541                               "ΔdQy",  "rel ΔdQy",
542                               "ΔdQz",  "rel ΔdQz",
543                               "Δtsat", "rel Δtsat");
544          }
545 
546         // Propagate to final measurement's date
547         propagator.propagate(measurements.get(measurements.size()-1).getDate());
548 
549         // Convert error list to double[]
550         final double relErrors[] = relErrorList.stream().mapToDouble(Double::doubleValue).toArray();
551 
552         // Compute statistics
553         final double relErrorsMedian = new Median().evaluate(relErrors);
554         final double relErrorsMean   = new Mean().evaluate(relErrors);
555         final double relErrorsMax    = new Max().evaluate(relErrors);
556 
557         // Print the results on console ?
558         if (printResults) {
559             System.out.println();
560             System.out.format(Locale.US, "Relative errors dR/dQ -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
561                               relErrorsMedian, relErrorsMean, relErrorsMax);
562         }
563 
564         Assert.assertEquals(0.0, relErrorsMedian, refErrorsMedian);
565         Assert.assertEquals(0.0, relErrorsMean, refErrorsMean);
566         Assert.assertEquals(0.0, relErrorsMax, refErrorsMax);
567 
568     }
569 
570     @Test
571     public void testStateDerivativesWithTroposphericModifier() {
572 
573         final boolean printResults     = false;
574         final double  refErrorsPMedian = 5.9e-10;
575         final double  refErrorsPMean   = 3.5e-9;
576         final double  refErrorsPMax    = 1.1e-7;
577         final double  refErrorsVMedian = 2.0e-5;
578         final double  refErrorsVMean   = 5.8e-5;
579         final double  refErrorsVMax    = 2.1e-3;
580 
581         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
582 
583         final NumericalPropagatorBuilder propagatorBuilder =
584                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngle.TRUE, true,
585                                               1.0e-6, 60.0, 0.001);
586 
587         // Create perfect range measurements
588         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
589                                                                            propagatorBuilder);
590         final double groundClockOffset =  12.0e-6;
591         for (final GroundStation station : context.stations) {
592             station.getClockOffsetDriver().setValue(groundClockOffset);
593         }
594         final int    ambiguity         = 1234;
595         final double satClockOffset    = 345.0e-6;
596         final List<ObservedMeasurement<?>> measurements =
597                         EstimationTestUtils.createMeasurements(propagator,
598                                                                new PhaseMeasurementCreator(context,
599                                                                                            Frequency.E01,
600                                                                                            ambiguity,
601                                                                                            satClockOffset),
602                                                                1.0, 3.0, 300.0);
603 
604         // Lists for results' storage - Used only for derivatives with respect to state
605         // "final" value to be seen by "handleStep" function of the propagator
606         final List<Double> errorsP = new ArrayList<Double>();
607         final List<Double> errorsV = new ArrayList<Double>();
608 
609         // Use a lambda function to implement "handleStep" function
610         propagator.setStepHandler(interpolator -> {
611 
612             for (final ObservedMeasurement<?> measurement : measurements) {
613 
614                 //  Play test if the measurement date is between interpolator previous and current date
615                 if ((measurement.getDate().durationFrom(interpolator.getPreviousState().getDate()) > 0.) &&
616                     (measurement.getDate().durationFrom(interpolator.getCurrentState().getDate())  <=  0.)) {
617 
618                     String stationName  = ((Phase) measurement).getStation().getBaseFrame().getName();
619 
620                     // Add modifier
621                     final NiellMappingFunctionModel mappingFunction = new NiellMappingFunctionModel();
622                     final EstimatedTroposphericModel tropoModel     = new EstimatedTroposphericModel(mappingFunction, 5.0);
623                     final PhaseTroposphericDelayModifier modifier = new PhaseTroposphericDelayModifier(tropoModel);
624                     final List<ParameterDriver> parameters = modifier.getParametersDrivers();
625                     parameters.get(0).setName(stationName + "/" + EstimatedTroposphericModel.TOTAL_ZENITH_DELAY);
626                     parameters.get(0).setSelected(true);
627                     ((Phase) measurement).addModifier(modifier);
628 
629                     // We intentionally propagate to a date which is close to the
630                     // real spacecraft state but is *not* the accurate date, by
631                     // compensating only part of the downlink delay. This is done
632                     // in order to validate the partial derivatives with respect
633                     // to velocity. If we had chosen the proper state date, the
634                     // range would have depended only on the current position but
635                     // not on the current velocity.
636                     final double          meanDelay = measurement.getObservedValue()[0] / Constants.SPEED_OF_LIGHT;
637                     final AbsoluteDate    date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
638                     final SpacecraftState state     = interpolator.getInterpolatedState(date);
639                     final double[][]      jacobian  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getStateDerivatives(0);
640 
641                     // Jacobian reference value
642                     final double[][] jacobianRef;
643 
644                     // Compute a reference value using finite differences
645                     jacobianRef = Differentiation.differentiate(new StateFunction() {
646                         public double[] value(final SpacecraftState state) {
647                             return measurement.estimate(0, 0, new SpacecraftState[] { state }).getEstimatedValue();
648                         }
649                     }, measurement.getDimension(), propagator.getAttitudeProvider(),
650                        OrbitType.CARTESIAN, PositionAngle.TRUE, 2.0, 3).value(state);
651 
652                     Assert.assertEquals(jacobianRef.length, jacobian.length);
653                     Assert.assertEquals(jacobianRef[0].length, jacobian[0].length);
654 
655                     // Errors & relative errors on the Jacobian
656                     double [][] dJacobian         = new double[jacobian.length][jacobian[0].length];
657                     double [][] dJacobianRelative = new double[jacobian.length][jacobian[0].length];
658                     for (int i = 0; i < jacobian.length; ++i) {
659                         for (int j = 0; j < jacobian[i].length; ++j) {
660                             dJacobian[i][j] = jacobian[i][j] - jacobianRef[i][j];
661                             dJacobianRelative[i][j] = FastMath.abs(dJacobian[i][j]/jacobianRef[i][j]);
662 
663                             if (j < 3) { errorsP.add(dJacobianRelative[i][j]);
664                             } else { errorsV.add(dJacobianRelative[i][j]); }
665                         }
666                     }
667                     // Print values in console ?
668                     if (printResults) {
669                         System.out.format(Locale.US, "%-15s  %-23s  %-23s  " +
670                                           "%10.3e  %10.3e  %10.3e  " +
671                                           "%10.3e  %10.3e  %10.3e  " +
672                                           "%10.3e  %10.3e  %10.3e  " +
673                                           "%10.3e  %10.3e  %10.3e%n",
674                                           stationName, measurement.getDate(), date,
675                                           dJacobian[0][0], dJacobian[0][1], dJacobian[0][2],
676                                           dJacobian[0][3], dJacobian[0][4], dJacobian[0][5],
677                                           dJacobianRelative[0][0], dJacobianRelative[0][1], dJacobianRelative[0][2],
678                                           dJacobianRelative[0][3], dJacobianRelative[0][4], dJacobianRelative[0][5]);
679                     }
680                 } // End if measurement date between previous and current interpolator step
681             } // End for loop on the measurements
682         });
683 
684         // Print results on console ?
685         if (printResults) {
686             System.out.format(Locale.US, "%-15s  %-23s  %-23s  " +
687                             "%10s  %10s  %10s  " +
688                             "%10s  %10s  %10s  " +
689                             "%10s  %10s  %10s  " +
690                             "%10s  %10s  %10s%n",
691                             "Station", "Measurement Date", "State Date",
692                             "ΔdPx", "ΔdPy", "ΔdPz", "ΔdVx", "ΔdVy", "ΔdVz",
693                             "rel ΔdPx", "rel ΔdPy", "rel ΔdPz",
694                             "rel ΔdVx", "rel ΔdVy", "rel ΔdVz");
695         }
696 
697         // Rewind the propagator to initial date
698         propagator.propagate(context.initialOrbit.getDate());
699 
700         // Sort measurements chronologically
701         measurements.sort(Comparator.naturalOrder());
702 
703         // Propagate to final measurement's date
704         propagator.propagate(measurements.get(measurements.size()-1).getDate());
705 
706         // Convert lists to double[] and evaluate some statistics
707         final double relErrorsP[] = errorsP.stream().mapToDouble(Double::doubleValue).toArray();
708         final double relErrorsV[] = errorsV.stream().mapToDouble(Double::doubleValue).toArray();
709 
710         final double errorsPMedian = new Median().evaluate(relErrorsP);
711         final double errorsPMean   = new Mean().evaluate(relErrorsP);
712         final double errorsPMax    = new Max().evaluate(relErrorsP);
713         final double errorsVMedian = new Median().evaluate(relErrorsV);
714         final double errorsVMean   = new Mean().evaluate(relErrorsV);
715         final double errorsVMax    = new Max().evaluate(relErrorsV);
716 
717         // Print the results on console ?
718         if (printResults) {
719             System.out.println();
720             System.out.format(Locale.US, "Relative errors dΦ/dP -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
721                               errorsPMedian, errorsPMean, errorsPMax);
722             System.out.format(Locale.US, "Relative errors dΦ/dV -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
723                               errorsVMedian, errorsVMean, errorsVMax);
724         }
725 
726         Assert.assertEquals(0.0, errorsPMedian, refErrorsPMedian);
727         Assert.assertEquals(0.0, errorsPMean, refErrorsPMean);
728         Assert.assertEquals(0.0, errorsPMax, refErrorsPMax);
729         Assert.assertEquals(0.0, errorsVMedian, refErrorsVMedian);
730         Assert.assertEquals(0.0, errorsVMean, refErrorsVMean);
731         Assert.assertEquals(0.0, errorsVMax, refErrorsVMax);
732     }
733 
734     @Test
735     public void testStateDerivativesWithIonosphericModifier() {
736 
737         final boolean printResults = false;
738         final double refErrorsPMedian = 5.1e-10;
739         final double refErrorsPMean = 2.6e-9;
740         final double refErrorsPMax = 7.8e-8;
741         final double refErrorsVMedian = 2.0e-5;
742         final double refErrorsVMean = 6.0e-5;
743         final double refErrorsVMax = 2.1e-3;
744 
745         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
746 
747         final NumericalPropagatorBuilder propagatorBuilder =
748                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngle.TRUE, true,
749                                               1.0e-6, 60.0, 0.001);
750 
751         // Create perfect range measurements
752         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
753                                                                            propagatorBuilder);
754         final double groundClockOffset =  12.0e-6;
755         for (final GroundStation station : context.stations) {
756             station.getClockOffsetDriver().setValue(groundClockOffset);
757         }
758         final int    ambiguity         = 1234;
759         final double satClockOffset    = 345.0e-6;
760         final List<ObservedMeasurement<?>> measurements =
761                         EstimationTestUtils.createMeasurements(propagator,
762                                                                new PhaseMeasurementCreator(context,
763                                                                                            Frequency.E01,
764                                                                                            ambiguity,
765                                                                                            satClockOffset),
766                                                                1.0, 3.0, 300.0);
767 
768         // Lists for results' storage - Used only for derivatives with respect to state
769         // "final" value to be seen by "handleStep" function of the propagator
770         final List<Double> errorsP = new ArrayList<Double>();
771         final List<Double> errorsV = new ArrayList<Double>();
772 
773         final IonosphericModel model = new KlobucharIonoModel(new double[]{.3820e-07, .1490e-07, -.1790e-06, 0},
774                                                               new double[]{.1430e+06, 0, -.3280e+06, .1130e+06});
775         final double frequency = Frequency.G01.getMHzFrequency() * 1.0e6;
776         final PhaseIonosphericDelayModifier modifier = new PhaseIonosphericDelayModifier(model, frequency);
777 
778         // Use a lambda function to implement "handleStep" function
779         propagator.setStepHandler(interpolator -> {
780 
781             for (final ObservedMeasurement<?> measurement : measurements) {
782 
783                 final Phase phase = (Phase) measurement;
784                 //  Play test if the measurement date is between interpolator previous and current date
785                 if ((measurement.getDate().durationFrom(interpolator.getPreviousState().getDate()) > 0.) &&
786                     (measurement.getDate().durationFrom(interpolator.getCurrentState().getDate())  <=  0.)) {
787 
788                     // Add modifier
789                     phase.addModifier(modifier);
790 
791                     // We intentionally propagate to a date which is close to the
792                     // real spacecraft state but is *not* the accurate date, by
793                     // compensating only part of the downlink delay. This is done
794                     // in order to validate the partial derivatives with respect
795                     // to velocity. If we had chosen the proper state date, the
796                     // range would have depended only on the current position but
797                     // not on the current velocity.
798                     final double          meanDelay = measurement.getObservedValue()[0] / Constants.SPEED_OF_LIGHT;
799                     final AbsoluteDate    date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
800                     final SpacecraftState state     = interpolator.getInterpolatedState(date);
801                     final double[][]      jacobian  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getStateDerivatives(0);
802 
803                     // Jacobian reference value
804                     final double[][] jacobianRef;
805 
806                     // Compute a reference value using finite differences
807                     jacobianRef = Differentiation.differentiate(new StateFunction() {
808                         public double[] value(final SpacecraftState state) {
809                             return measurement.estimate(0, 0, new SpacecraftState[] { state }).getEstimatedValue();
810                         }
811                     }, measurement.getDimension(), propagator.getAttitudeProvider(),
812                        OrbitType.CARTESIAN, PositionAngle.TRUE, 2.0, 3).value(state);
813 
814                     Assert.assertEquals(jacobianRef.length, jacobian.length);
815                     Assert.assertEquals(jacobianRef[0].length, jacobian[0].length);
816 
817                     // Errors & relative errors on the Jacobian
818                     double [][] dJacobian         = new double[jacobian.length][jacobian[0].length];
819                     double [][] dJacobianRelative = new double[jacobian.length][jacobian[0].length];
820                     for (int i = 0; i < jacobian.length; ++i) {
821                         for (int j = 0; j < jacobian[i].length; ++j) {
822                             dJacobian[i][j] = jacobian[i][j] - jacobianRef[i][j];
823                             dJacobianRelative[i][j] = FastMath.abs(dJacobian[i][j]/jacobianRef[i][j]);
824 
825                             if (j < 3) { errorsP.add(dJacobianRelative[i][j]);
826                             } else { errorsV.add(dJacobianRelative[i][j]); }
827                         }
828                     }
829                     // Print values in console ?
830                     if (printResults) {
831                         System.out.format(Locale.US, "%-15s  %-23s  %-23s  " +
832                                           "%10.3e  %10.3e  %10.3e  " +
833                                           "%10.3e  %10.3e  %10.3e  " +
834                                           "%10.3e  %10.3e  %10.3e  " +
835                                           "%10.3e  %10.3e  %10.3e%n",
836                                           phase.getStation().getBaseFrame().getName(), measurement.getDate(), date,
837                                           dJacobian[0][0], dJacobian[0][1], dJacobian[0][2],
838                                           dJacobian[0][3], dJacobian[0][4], dJacobian[0][5],
839                                           dJacobianRelative[0][0], dJacobianRelative[0][1], dJacobianRelative[0][2],
840                                           dJacobianRelative[0][3], dJacobianRelative[0][4], dJacobianRelative[0][5]);
841                     }
842                 } // End if measurement date between previous and current interpolator step
843             } // End for loop on the measurements
844         });
845 
846         // Print results on console ?
847         if (printResults) {
848             System.out.format(Locale.US, "%-15s  %-23s  %-23s  " +
849                             "%10s  %10s  %10s  " +
850                             "%10s  %10s  %10s  " +
851                             "%10s  %10s  %10s  " +
852                             "%10s  %10s  %10s%n",
853                             "Station", "Measurement Date", "State Date",
854                             "ΔdPx", "ΔdPy", "ΔdPz", "ΔdVx", "ΔdVy", "ΔdVz",
855                             "rel ΔdPx", "rel ΔdPy", "rel ΔdPz",
856                             "rel ΔdVx", "rel ΔdVy", "rel ΔdVz");
857         }
858 
859         // Rewind the propagator to initial date
860         propagator.propagate(context.initialOrbit.getDate());
861 
862         // Sort measurements chronologically
863         measurements.sort(Comparator.naturalOrder());
864 
865         // Propagate to final measurement's date
866         propagator.propagate(measurements.get(measurements.size()-1).getDate());
867 
868         // Convert lists to double[] and evaluate some statistics
869         final double relErrorsP[] = errorsP.stream().mapToDouble(Double::doubleValue).toArray();
870         final double relErrorsV[] = errorsV.stream().mapToDouble(Double::doubleValue).toArray();
871 
872         final double errorsPMedian = new Median().evaluate(relErrorsP);
873         final double errorsPMean   = new Mean().evaluate(relErrorsP);
874         final double errorsPMax    = new Max().evaluate(relErrorsP);
875         final double errorsVMedian = new Median().evaluate(relErrorsV);
876         final double errorsVMean   = new Mean().evaluate(relErrorsV);
877         final double errorsVMax    = new Max().evaluate(relErrorsV);
878 
879         // Print the results on console ?
880         if (printResults) {
881             System.out.println();
882             System.out.format(Locale.US, "Relative errors dΦ/dP -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
883                               errorsPMedian, errorsPMean, errorsPMax);
884             System.out.format(Locale.US, "Relative errors dΦ/dV -> Median: %6.3e / Mean: %6.3e / Max: %6.3e%n",
885                               errorsVMedian, errorsVMean, errorsVMax);
886         }
887 
888         Assert.assertEquals(0.0, errorsPMedian, refErrorsPMedian);
889         Assert.assertEquals(0.0, errorsPMean, refErrorsPMean);
890         Assert.assertEquals(0.0, errorsPMax, refErrorsPMax);
891         Assert.assertEquals(0.0, errorsVMedian, refErrorsVMedian);
892         Assert.assertEquals(0.0, errorsVMean, refErrorsVMean);
893         Assert.assertEquals(0.0, errorsVMax, refErrorsVMax);
894     }
895 
896     @Test
897     public void testIssue734() {
898 
899         Utils.setDataRoot("regular-data");
900 
901         // Create a ground station
902         final OneAxisEllipsoid body = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING,
903                                                            FramesFactory.getITRF(IERSConventions.IERS_2010, true));
904         final TopocentricFrame topo = new TopocentricFrame(body,
905                                                            new GeodeticPoint(FastMath.toRadians(51.8), FastMath.toRadians(102.2), 811.2),
906                                                            "BADG");
907         final GroundStation station = new GroundStation(topo);
908 
909         // Create a phase measurement
910         final Phase phase = new Phase(station, AbsoluteDate.J2000_EPOCH, 119866527.060, Frequency.G01.getWavelength(), 0.02, 1.0, new ObservableSatellite(0));
911 
912         // First check
913         Assert.assertEquals(0.0, phase.getAmbiguityDriver().getValue(), Double.MIN_VALUE);
914         Assert.assertFalse(phase.getAmbiguityDriver().isSelected());
915 
916         // Perform some changes in ambiguity driver
917         phase.getAmbiguityDriver().setValue(1234.0);
918         phase.getAmbiguityDriver().setSelected(true);
919 
920         // Second check
921         Assert.assertEquals(1234.0, phase.getAmbiguityDriver().getValue(), Double.MIN_VALUE);
922         Assert.assertTrue(phase.getAmbiguityDriver().isSelected());
923         for (ParameterDriver driver : phase.getParametersDrivers()) {
924             // Verify if the current driver corresponds to the phase ambiguity
925             if (driver.getName() == Phase.AMBIGUITY_NAME) {
926                 Assert.assertEquals(1234.0, phase.getAmbiguityDriver().getValue(), Double.MIN_VALUE);
927                 Assert.assertTrue(phase.getAmbiguityDriver().isSelected());
928             }
929         }
930 
931     }
932 
933 }