1   /* Copyright 2002-2020 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.Arrays;
20  import java.util.HashMap;
21  import java.util.Map;
22  
23  import org.hipparchus.analysis.differentiation.Gradient;
24  import org.hipparchus.analysis.differentiation.GradientField;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Rotation;
27  import org.hipparchus.geometry.euclidean.threed.Vector3D;
28  import org.orekit.frames.FieldTransform;
29  import org.orekit.frames.Transform;
30  import org.orekit.propagation.SpacecraftState;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.FieldAbsoluteDate;
33  import org.orekit.utils.AngularCoordinates;
34  import org.orekit.utils.Constants;
35  import org.orekit.utils.PVCoordinates;
36  import org.orekit.utils.ParameterDriver;
37  import org.orekit.utils.TimeStampedFieldPVCoordinates;
38  import org.orekit.utils.TimeStampedPVCoordinates;
39  
40  /** Class modeling a range measurement from a ground station.
41   * <p>
42   * The measurement is considered to be a signal emitted from
43   * a ground station, reflected on spacecraft, and received
44   * on the same ground station. Its value is the elapsed time
45   * between emission and reception divided by 2c were c is the
46   * speed of light. The motion of both the station and the
47   * spacecraft during the signal flight time are taken into
48   * account. The date of the measurement corresponds to the
49   * reception on ground of the reflected signal.
50   * Difference with the Range class in src folder are:
51   *  - The computation of the evaluation is made with analytic formulas
52   *    instead of using auto-differentiation and derivative structures
53   *    It is the implementation used in Orekit 8.0
54   *  - A function evaluating the difference between analytical calculation
55   *    and numerical calculation was added for validation
56   * </p>
57   * @author Thierry Ceolin
58   * @author Luc Maisonobe
59   * @author Maxime Journot
60   * @since 9.0
61   */
62  public class RangeAnalytic extends Range {
63  
64      /** Constructor from parent Range class
65       * @param Range parent class
66       */
67      public RangeAnalytic(final Range range) {
68          super(range.getStation(), true, range.getDate(), range.getObservedValue()[0],
69                range.getTheoreticalStandardDeviation()[0],
70                range.getBaseWeight()[0],
71                new ObservableSatellite(0));
72      }
73  
74      /**
75       * Analytical version of the theoreticalEvaluation function in Range class
76       * The derivative structures are not used, an analytical computation is used instead.
77       * @param iteration current LS estimator iteration
78       * @param evaluation current LS estimator evaluation
79       * @param state spacecraft state. At measurement date on first iteration then close to emission date on further iterations
80       * @param interpolator Orekit step interpolator
81       * @return
82       */
83      protected EstimatedMeasurement<Range> theoreticalEvaluationAnalytic(final int iteration, final int evaluation,
84                                                                          final SpacecraftState state) {
85  
86          // Station attribute from parent Range class
87          final GroundStation groundStation = this.getStation();
88  
89          // Station position at signal arrival
90          final AbsoluteDate downlinkDate = getDate();
91          final Transform topoToInertDownlink =
92                          groundStation.getOffsetToInertial(state.getFrame(), downlinkDate);
93          final TimeStampedPVCoordinates stationDownlink =
94                          topoToInertDownlink.transformPVCoordinates(new TimeStampedPVCoordinates(downlinkDate,
95                                                                                                  PVCoordinates.ZERO));
96  
97          // Take propagation time into account
98          // (if state has already been set up to pre-compensate propagation delay,
99          //  we will have offset == downlinkDelay and transitState will be
100         //  the same as state)
101         // Downlink time of flight
102         final double          tauD         = signalTimeOfFlight(state.getPVCoordinates(),
103                                                                 stationDownlink.getPosition(),
104                                                                 downlinkDate);
105         final double          delta        = downlinkDate.durationFrom(state.getDate());
106         final double          dt           = delta - tauD;
107 
108         // Transit state position
109         final SpacecraftState transitState = state.shiftedBy(dt);
110         final AbsoluteDate    transitDate  = transitState.getDate();
111         final Vector3D        transitP     = transitState.getPVCoordinates().getPosition();
112 
113         // Station position at transit state date
114         final Transform topoToInertAtTransitDate =
115                       groundStation.getOffsetToInertial(state.getFrame(), transitDate);
116         final TimeStampedPVCoordinates stationAtTransitDate = topoToInertAtTransitDate.
117                       transformPVCoordinates(new TimeStampedPVCoordinates(transitDate, PVCoordinates.ZERO));
118 
119         // Uplink time of flight
120         final double          tauU         = signalTimeOfFlight(stationAtTransitDate, transitP, transitDate);
121         final double          tau          = tauD + tauU;
122 
123         // Real date and position of station at signal departure
124         final AbsoluteDate             uplinkDate    = downlinkDate.shiftedBy(-tau);
125         final TimeStampedPVCoordinates stationUplink = topoToInertDownlink.shiftedBy(-tau).
126                         transformPVCoordinates(new TimeStampedPVCoordinates(uplinkDate, PVCoordinates.ZERO));
127 
128         // Prepare the evaluation
129         final EstimatedMeasurement<Range> estimated =
130                         new EstimatedMeasurement<Range>(this, iteration, evaluation,
131                                                         new SpacecraftState[] {
132                                                             transitState
133                                                         }, new TimeStampedPVCoordinates[] {
134                                                             stationUplink,
135                                                             transitState.getPVCoordinates(),
136                                                             stationDownlink
137                                                         });
138 
139         // Set range value
140         final double cOver2 = 0.5 * Constants.SPEED_OF_LIGHT;
141         estimated.setEstimatedValue(tau * cOver2);
142 
143         // Partial derivatives with respect to state
144         // The formulas below take into account the fact the measurement is at fixed reception date.
145         // When spacecraft position is changed, the downlink delay is changed, and in order
146         // to still have the measurement arrive at exactly the same date on ground, we must
147         // take the spacecraft-station relative velocity into account.
148         final Vector3D v         = state.getPVCoordinates().getVelocity();
149         final Vector3D qv        = stationDownlink.getVelocity();
150         final Vector3D downInert = stationDownlink.getPosition().subtract(transitP);
151         final double   dDown     = Constants.SPEED_OF_LIGHT * Constants.SPEED_OF_LIGHT * tauD -
152                         Vector3D.dotProduct(downInert, v);
153         final Vector3D upInert   = transitP.subtract(stationUplink.getPosition());
154 
155         //test
156         //     final double   dUp       = Constants.SPEED_OF_LIGHT * Constants.SPEED_OF_LIGHT * tauU -
157         //                     Vector3D.dotProduct(upInert, qv);
158         //test
159         final double   dUp       = Constants.SPEED_OF_LIGHT * Constants.SPEED_OF_LIGHT * tauU -
160                         Vector3D.dotProduct(upInert, stationUplink.getVelocity());
161 
162 
163         // derivatives of the downlink time of flight
164         final double dTauDdPx   = -downInert.getX() / dDown;
165         final double dTauDdPy   = -downInert.getY() / dDown;
166         final double dTauDdPz   = -downInert.getZ() / dDown;
167 
168 
169         // Derivatives of the uplink time of flight
170         final double dTauUdPx = 1./dUp*upInert
171                         .dotProduct(Vector3D.PLUS_I
172                                     .add((qv.subtract(v))
173                                          .scalarMultiply(dTauDdPx)));
174         final double dTauUdPy = 1./dUp*upInert
175                         .dotProduct(Vector3D.PLUS_J
176                                     .add((qv.subtract(v))
177                                          .scalarMultiply(dTauDdPy)));
178         final double dTauUdPz = 1./dUp*upInert
179                         .dotProduct(Vector3D.PLUS_K
180                                     .add((qv.subtract(v))
181                                          .scalarMultiply(dTauDdPz)));
182 
183 
184         // derivatives of the range measurement
185         final double dRdPx = (dTauDdPx + dTauUdPx) * cOver2;
186         final double dRdPy = (dTauDdPy + dTauUdPy) * cOver2;
187         final double dRdPz = (dTauDdPz + dTauUdPz) * cOver2;
188         estimated.setStateDerivatives(0, new double[] {
189                                                     dRdPx,      dRdPy,      dRdPz,
190                                                     dRdPx * dt, dRdPy * dt, dRdPz * dt
191         });
192 
193         if (groundStation.getClockOffsetDriver().isSelected() ||
194             groundStation.getEastOffsetDriver().isSelected()  ||
195             groundStation.getNorthOffsetDriver().isSelected() ||
196             groundStation.getZenithOffsetDriver().isSelected()) {
197 
198             // Downlink tme of flight derivatives / station position in topocentric frame
199             final AngularCoordinates ac = topoToInertDownlink.getAngular().revert();
200             //final Rotation rotTopoToInert = ac.getRotation();
201             final Vector3D omega        = ac.getRotationRate();
202 
203             // Inertial frame
204             final double dTauDdQIx = downInert.getX()/dDown;
205             final double dTauDdQIy = downInert.getY()/dDown;
206             final double dTauDdQIz = downInert.getZ()/dDown;
207 
208             // Uplink tme of flight derivatives / station position in topocentric frame
209             // Inertial frame
210             final double dTauUdQIx = 1/dUp*upInert
211                             .dotProduct(Vector3D.MINUS_I
212                                         .add((qv.subtract(v)).scalarMultiply(dTauDdQIx))
213                                         .subtract(Vector3D.PLUS_I.crossProduct(omega).scalarMultiply(tau)));
214             final double dTauUdQIy = 1/dUp*upInert
215                             .dotProduct(Vector3D.MINUS_J
216                                         .add((qv.subtract(v)).scalarMultiply(dTauDdQIy))
217                                         .subtract(Vector3D.PLUS_J.crossProduct(omega).scalarMultiply(tau)));
218             final double dTauUdQIz = 1/dUp*upInert
219                             .dotProduct(Vector3D.MINUS_K
220                                         .add((qv.subtract(v)).scalarMultiply(dTauDdQIz))
221                                         .subtract(Vector3D.PLUS_K.crossProduct(omega).scalarMultiply(tau)));
222 
223 
224             // Range partial derivatives
225             // with respect to station position in inertial frame
226             final Vector3D dRdQI = new Vector3D((dTauDdQIx + dTauUdQIx) * cOver2,
227                                                 (dTauDdQIy + dTauUdQIy) * cOver2,
228                                                 (dTauDdQIz + dTauUdQIz) * cOver2);
229 
230             // convert to topocentric frame, as the station position
231             // offset parameter is expressed in this frame
232             final Vector3D dRdQT = ac.getRotation().applyTo(dRdQI);
233 
234             if (groundStation.getEastOffsetDriver().isSelected()) {
235                 estimated.setParameterDerivatives(groundStation.getEastOffsetDriver(), dRdQT.getX());
236             }
237             if (groundStation.getNorthOffsetDriver().isSelected()) {
238                 estimated.setParameterDerivatives(groundStation.getNorthOffsetDriver(), dRdQT.getY());
239             }
240             if (groundStation.getZenithOffsetDriver().isSelected()) {
241                 estimated.setParameterDerivatives(groundStation.getZenithOffsetDriver(), dRdQT.getZ());
242             }
243 
244         }
245 
246         return estimated;
247 
248     }
249 
250 
251     /**
252      * Added for validation
253      * Compares directly numeric and analytic computations
254      * @param iteration
255      * @param evaluation
256      * @param state
257      * @return
258      */
259     protected EstimatedMeasurement<Range> theoreticalEvaluationValidation(final int iteration, final int evaluation,
260                                                                           final SpacecraftState state) {
261 
262         // Station & DSFactory attributes from parent Range class
263         final GroundStation groundStation             =  getStation();
264 
265         // get the number of parameters used for derivation
266         int nbParams = 6;
267         final Map<String, Integer> indices = new HashMap<>();
268         for (ParameterDriver driver : getParametersDrivers()) {
269             if (driver.isSelected()) {
270                 indices.put(driver.getName(), nbParams++);
271             }
272         }
273         final FieldVector3D<Gradient> zero = FieldVector3D.getZero(GradientField.getField(nbParams));
274 
275         // Range derivatives are computed with respect to spacecraft state in inertial frame
276         // and station position in station's offset frame
277         // -------
278         //
279         // Parameters:
280         //  - 0..2 - Px, Py, Pz   : Position of the spacecraft in inertial frame
281         //  - 3..5 - Vx, Vy, Vz   : Velocity of the spacecraft in inertial frame
282         //  - 6..8 - QTx, QTy, QTz: Position of the station in station's offset frame
283 
284         // Coordinates of the spacecraft expressed as a gradient
285         final TimeStampedFieldPVCoordinates<Gradient> pvaDS = getCoordinates(state, 0, nbParams);
286 
287         // transform between station and inertial frame, expressed as a gradient
288         // The components of station's position in offset frame are the 3 last derivative parameters
289         final AbsoluteDate downlinkDate = getDate();
290         final FieldTransform<Gradient> offsetToInertialDownlink =
291                         groundStation.getOffsetToInertial(state.getFrame(), downlinkDate, nbParams, indices);
292         final FieldAbsoluteDate<Gradient> downlinkDateDS =
293                         offsetToInertialDownlink.getFieldDate();
294 
295         // Station position in inertial frame at end of the downlink leg
296         final TimeStampedFieldPVCoordinates<Gradient> stationDownlink =
297                         offsetToInertialDownlink.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(downlinkDateDS,
298                                                                                                             zero, zero, zero));
299 
300         // Compute propagation times
301         // (if state has already been set up to pre-compensate propagation delay,
302         //  we will have offset == downlinkDelay and transitState will be
303         //  the same as state)
304 
305         // Downlink delay
306         final Gradient tauD = signalTimeOfFlight(pvaDS, stationDownlink.getPosition(), downlinkDateDS);
307 
308         // Transit state
309         final double                delta        = downlinkDate.durationFrom(state.getDate());
310         final Gradient              tauDMDelta   = tauD.negate().add(delta);
311         final SpacecraftState       transitState = state.shiftedBy(tauDMDelta.getValue());
312 
313         // Transit state position (re)computed with gradients
314         final TimeStampedFieldPVCoordinates<Gradient> transitStateDS = pvaDS.shiftedBy(tauDMDelta);
315 
316         // Station at transit state date (derivatives of tauD taken into account)
317         final TimeStampedFieldPVCoordinates<Gradient> stationAtTransitDate =
318                         stationDownlink.shiftedBy(tauD.negate());
319         // Uplink delay
320         final Gradient tauU =
321                         signalTimeOfFlight(stationAtTransitDate, transitStateDS.getPosition(), transitStateDS.getDate());
322 
323         // Prepare the evaluation
324         final EstimatedMeasurement<Range> estimated =
325                         new EstimatedMeasurement<Range>(this, iteration, evaluation,
326                                                         new SpacecraftState[] {
327                                                             transitState
328                                                         }, null);
329 
330         // Range value
331         final Gradient tau    = tauD.add(tauU);
332         final double   cOver2 = 0.5 * Constants.SPEED_OF_LIGHT;
333         final Gradient range  = tau.multiply(cOver2);
334         estimated.setEstimatedValue(range.getValue());
335 
336         // Range partial derivatives with respect to state
337         final double[] derivatives = range.getGradient();
338         estimated.setStateDerivatives(0, Arrays.copyOfRange(derivatives, 0, 6));
339 
340         // set partial derivatives with respect to parameters
341         // (beware element at index 0 is the value, not a derivative)
342         for (final ParameterDriver driver : getParametersDrivers()) {
343             final Integer index = indices.get(driver.getName());
344             if (index != null) {
345                 estimated.setParameterDerivatives(driver, derivatives[index]);
346             }
347         }
348 
349         // ----------
350         // VALIDATION
351         //-----------
352 
353         // Computation of the value without Gradient
354         // ----------------------------------
355 
356         // Time difference between t (date of the measurement) and t' (date tagged in spacecraft state)
357 
358         // Station position at signal arrival
359         final Transform topoToInertDownlink =
360                         groundStation.getOffsetToInertial(state.getFrame(), downlinkDate);
361         final PVCoordinates QDownlink = topoToInertDownlink.
362                         transformPVCoordinates(PVCoordinates.ZERO);
363 
364         // Downlink time of flight from spacecraft to station
365         final double td = signalTimeOfFlight(state.getPVCoordinates(), QDownlink.getPosition(), downlinkDate);
366         final double dt = delta - td;
367 
368         // Transit state position
369         final AbsoluteDate    transitT = state.getDate().shiftedBy(dt);
370         final SpacecraftState transit  = state.shiftedBy(dt);
371         final Vector3D        transitP = transitState.getPVCoordinates().getPosition();
372 
373         // Station position at signal departure
374         // First guess
375 //        AbsoluteDate uplinkDate = downlinkDate.shiftedBy(-getObservedValue()[0] / cOver2);
376 //        final Transform topoToInertUplink =
377 //                        station.getOffsetFrame().getTransformTo(state.getFrame(), uplinkDate);
378 //        TimeStampedPVCoordinates QUplink = topoToInertUplink.
379 //                        transformPVCoordinates(new TimeStampedPVCoordinates(uplinkDate, PVCoordinates.ZERO));
380 
381         // Station position at transit state date
382         final Transform topoToInertAtTransitDate =
383                       groundStation.getOffsetToInertial(state.getFrame(), transitT);
384         TimeStampedPVCoordinates QAtTransitDate = topoToInertAtTransitDate.
385                       transformPVCoordinates(new TimeStampedPVCoordinates(transitT, PVCoordinates.ZERO));
386 
387         // Uplink time of flight
388         final double tu = signalTimeOfFlight(QAtTransitDate, transitP, transitT);
389 
390         // Total time of flight
391         final double t = td + tu;
392 
393         // Real date and position of station at signal departure
394         AbsoluteDate uplinkDate    = downlinkDate.shiftedBy(-t);
395 
396         TimeStampedPVCoordinates QUplink = topoToInertDownlink.shiftedBy(-t).
397                         transformPVCoordinates(new TimeStampedPVCoordinates(uplinkDate, PVCoordinates.ZERO));
398 
399 
400         // Range value
401         double r = t * cOver2;
402         double dR = r-range.getValue();
403 
404 
405 
406         // td derivatives / state
407         // -----------------------
408 
409         // Qt = Master station position at tmeas = t = signal arrival at master station
410         final Vector3D vel     = state.getPVCoordinates().getVelocity();
411         final Vector3D Qt_V    = QDownlink.getVelocity();
412         final Vector3D Ptr     = transit.getPVCoordinates().getPosition();
413         final Vector3D Ptr_Qt  = QDownlink.getPosition().subtract(Ptr);
414         final double   dDown   = Constants.SPEED_OF_LIGHT * Constants.SPEED_OF_LIGHT * td -
415                         Vector3D.dotProduct(Ptr_Qt, vel);
416 
417         // Derivatives of the downlink time of flight
418         final double dtddPx   = -Ptr_Qt.getX() / dDown;
419         final double dtddPy   = -Ptr_Qt.getY() / dDown;
420         final double dtddPz   = -Ptr_Qt.getZ() / dDown;
421 
422         final double dtddVx   = dtddPx*dt;
423         final double dtddVy   = dtddPy*dt;
424         final double dtddVz   = dtddPz*dt;
425 
426         // From the DS
427         final double dtddPxDS = tauD.getPartialDerivative(0);
428         final double dtddPyDS = tauD.getPartialDerivative(1);
429         final double dtddPzDS = tauD.getPartialDerivative(2);
430         final double dtddVxDS = tauD.getPartialDerivative(3);
431         final double dtddVyDS = tauD.getPartialDerivative(4);
432         final double dtddVzDS = tauD.getPartialDerivative(5);
433 
434         // Difference
435         final double d_dtddPx = dtddPxDS-dtddPx;
436         final double d_dtddPy = dtddPyDS-dtddPy;
437         final double d_dtddPz = dtddPzDS-dtddPz;
438         final double d_dtddVx = dtddVxDS-dtddVx;
439         final double d_dtddVy = dtddVyDS-dtddVy;
440         final double d_dtddVz = dtddVzDS-dtddVz;
441 
442 
443         // tu derivatives / state
444         // -----------------------
445 
446 
447 
448         final Vector3D Qt2_Ptr  = Ptr.subtract(QUplink.getPosition());
449         final double   dUp      = Constants.SPEED_OF_LIGHT * Constants.SPEED_OF_LIGHT * tu -
450                         Vector3D.dotProduct(Qt2_Ptr, Qt_V);
451 
452         //test
453 //        // Speed of the station at tmeas-t
454 //        // Note: Which one to use in the calculation of dUp ???
455 //        final Vector3D Qt2_V    = QUplink.getVelocity();
456 //        final double   dUp      = Constants.SPEED_OF_LIGHT * Constants.SPEED_OF_LIGHT * tu -
457 //                        Vector3D.dotProduct(Qt2_Ptr, Qt2_V);
458         //test
459 
460 
461         // tu derivatives
462         final double dtudPx = 1./dUp*Qt2_Ptr
463                         .dotProduct(Vector3D.PLUS_I
464                                     .add((Qt_V.subtract(vel))
465                                          .scalarMultiply(dtddPx)));
466         final double dtudPy = 1./dUp*Qt2_Ptr
467                         .dotProduct(Vector3D.PLUS_J
468                                     .add((Qt_V.subtract(vel))
469                                          .scalarMultiply(dtddPy)));
470         final double dtudPz = 1./dUp*Qt2_Ptr
471                         .dotProduct(Vector3D.PLUS_K
472                                     .add((Qt_V.subtract(vel))
473                                          .scalarMultiply(dtddPz)));
474 
475         final double dtudVx   = dtudPx*dt;
476         final double dtudVy   = dtudPy*dt;
477         final double dtudVz   = dtudPz*dt;
478 
479 
480         // From the DS
481         final double dtudPxDS = tauU.getPartialDerivative(0);
482         final double dtudPyDS = tauU.getPartialDerivative(1);
483         final double dtudPzDS = tauU.getPartialDerivative(2);
484         final double dtudVxDS = tauU.getPartialDerivative(3);
485         final double dtudVyDS = tauU.getPartialDerivative(4);
486         final double dtudVzDS = tauU.getPartialDerivative(5);
487 
488         // Difference
489         final double d_dtudPx = dtudPxDS-dtudPx;
490         final double d_dtudPy = dtudPyDS-dtudPy;
491         final double d_dtudPz = dtudPzDS-dtudPz;
492         final double d_dtudVx = dtudVxDS-dtudVx;
493         final double d_dtudVy = dtudVyDS-dtudVy;
494         final double d_dtudVz = dtudVzDS-dtudVz;
495 
496 
497         // Range derivatives / state
498         // -----------------------
499 
500         // R = Range
501         double dRdPx = (dtddPx + dtudPx)*cOver2;
502         double dRdPy = (dtddPy + dtudPy)*cOver2;
503         double dRdPz = (dtddPz + dtudPz)*cOver2;
504         double dRdVx = (dtddVx + dtudVx)*cOver2;
505         double dRdVy = (dtddVy + dtudVy)*cOver2;
506         double dRdVz = (dtddVz + dtudVz)*cOver2;
507 
508         // With DS
509         double dRdPxDS = range.getPartialDerivative(0);
510         double dRdPyDS = range.getPartialDerivative(1);
511         double dRdPzDS = range.getPartialDerivative(2);
512         double dRdVxDS = range.getPartialDerivative(3);
513         double dRdVyDS = range.getPartialDerivative(4);
514         double dRdVzDS = range.getPartialDerivative(5);
515 
516         // Diff
517         final double d_dRdPx = dRdPxDS-dRdPx;
518         final double d_dRdPy = dRdPyDS-dRdPy;
519         final double d_dRdPz = dRdPzDS-dRdPz;
520         final double d_dRdVx = dRdVxDS-dRdVx;
521         final double d_dRdVy = dRdVyDS-dRdVy;
522         final double d_dRdVz = dRdVzDS-dRdVz;
523 
524 
525         // td derivatives / station
526         // -----------------------
527 
528         final AngularCoordinates ac = topoToInertDownlink.getAngular().revert();
529         final Rotation rotTopoToInert = ac.getRotation();
530         final Vector3D omega        = ac.getRotationRate();
531 
532         final Vector3D dtddQI = Ptr_Qt.scalarMultiply(1./dDown);
533         final double dtddQIx = dtddQI.getX();
534         final double dtddQIy = dtddQI.getY();
535         final double dtddQIz = dtddQI.getZ();
536 
537         final Vector3D dtddQ = rotTopoToInert.applyTo(dtddQI);
538 
539         // With DS
540         double dtddQxDS = tauD.getPartialDerivative(6);
541         double dtddQyDS = tauD.getPartialDerivative(7);
542         double dtddQzDS = tauD.getPartialDerivative(8);
543 
544         // Diff
545         final double d_dtddQx = dtddQxDS-dtddQ.getX();
546         final double d_dtddQy = dtddQyDS-dtddQ.getY();
547         final double d_dtddQz = dtddQzDS-dtddQ.getZ();
548 
549 
550         // tu derivatives / station
551         // -----------------------
552 
553         // Inertial frame
554         final double dtudQIx = 1/dUp*Qt2_Ptr
555                         .dotProduct(Vector3D.MINUS_I
556                                     .add((Qt_V.subtract(vel)).scalarMultiply(dtddQIx))
557                                     .subtract(Vector3D.PLUS_I.crossProduct(omega).scalarMultiply(t)));
558         final double dtudQIy = 1/dUp*Qt2_Ptr
559                         .dotProduct(Vector3D.MINUS_J
560                                     .add((Qt_V.subtract(vel)).scalarMultiply(dtddQIy))
561                                     .subtract(Vector3D.PLUS_J.crossProduct(omega).scalarMultiply(t)));
562         final double dtudQIz = 1/dUp*Qt2_Ptr
563                         .dotProduct(Vector3D.MINUS_K
564                                     .add((Qt_V.subtract(vel)).scalarMultiply(dtddQIz))
565                                     .subtract(Vector3D.PLUS_K.crossProduct(omega).scalarMultiply(t)));
566 
567 //        // test
568 //        final double dtudQIx = 1/dUp*Qt2_Ptr
569 ////                        .dotProduct(Vector3D.MINUS_I);
570 ////                                    .dotProduct((Qt_V.subtract(vel)).scalarMultiply(dtddQIx));
571 //                                    .dotProduct(Vector3D.MINUS_I.crossProduct(omega).scalarMultiply(t));
572 //        final double dtudQIy = 1/dUp*Qt2_Ptr
573 ////                        .dotProduct(Vector3D.MINUS_J);
574 ////                                    .dotProduct((Qt_V.subtract(vel)).scalarMultiply(dtddQIy));
575 //                                    .dotProduct(Vector3D.MINUS_J.crossProduct(omega).scalarMultiply(t));
576 //        final double dtudQIz = 1/dUp*Qt2_Ptr
577 ////                        .dotProduct(Vector3D.MINUS_K);
578 ////                                    .dotProduct((Qt_V.subtract(vel)).scalarMultiply(dtddQIz));
579 //                                    .dotProduct(Vector3D.MINUS_K.crossProduct(omega).scalarMultiply(t));
580 //
581 //        double dtu_dQxDS = tauU.getPartialDerivative(0, 0, 0, 0, 0, 0, 1, 0, 0);
582 //        double dtu_dQyDS = tauU.getPartialDerivative(0, 0, 0, 0, 0, 0, 0, 1, 0);
583 //        double dtu_dQzDS = tauU.getPartialDerivative(0, 0, 0, 0, 0, 0, 0, 0, 1);
584 //        final Vector3D dtudQDS = new Vector3D(dtu_dQxDS, dtu_dQyDS, dtu_dQzDS);
585 //        final Vector3D dtudQIDS = rotTopoToInert.applyInverseTo(dtudQDS);
586 //        double dtudQIxDS = dtudQIDS.getX();
587 //        double dtudQIyDS = dtudQIDS.getY();
588 //        double dtudQIxzS = dtudQIDS.getZ();
589 //        // test
590 
591         // Topocentric frame
592         final Vector3D dtudQI = new Vector3D(dtudQIx, dtudQIy, dtudQIz);
593         final Vector3D dtudQ = rotTopoToInert.applyTo(dtudQI);
594 
595 
596         // With DS
597         double dtudQxDS = tauU.getPartialDerivative(6);
598         double dtudQyDS = tauU.getPartialDerivative(7);
599         double dtudQzDS = tauU.getPartialDerivative(8);
600 
601         // Diff
602         final double d_dtudQx = dtudQxDS-dtudQ.getX();
603         final double d_dtudQy = dtudQyDS-dtudQ.getY();
604         final double d_dtudQz = dtudQzDS-dtudQ.getZ();
605 
606 
607         // Range derivatives / station
608         // -----------------------
609 
610         double dRdQx = (dtddQ.getX() + dtudQ.getX())*cOver2;
611         double dRdQy = (dtddQ.getY() + dtudQ.getY())*cOver2;
612         double dRdQz = (dtddQ.getZ() + dtudQ.getZ())*cOver2;
613 
614         // With DS
615         double dRdQxDS = range.getPartialDerivative(6);
616         double dRdQyDS = range.getPartialDerivative(7);
617         double dRdQzDS = range.getPartialDerivative(8);
618 
619         // Diff
620         final double d_dRdQx = dRdQxDS-dRdQx;
621         final double d_dRdQy = dRdQyDS-dRdQy;
622         final double d_dRdQz = dRdQzDS-dRdQz;
623 
624 
625         // Print results to avoid warning
626         final boolean printResults = false;
627 
628         if (printResults) {
629             System.out.println("dR = " + dR);
630 
631             System.out.println("d_dtddPx = " + d_dtddPx);
632             System.out.println("d_dtddPy = " + d_dtddPy);
633             System.out.println("d_dtddPz = " + d_dtddPz);
634             System.out.println("d_dtddVx = " + d_dtddVx);
635             System.out.println("d_dtddVy = " + d_dtddVy);
636             System.out.println("d_dtddVz = " + d_dtddVz);
637 
638             System.out.println("d_dtudPx = " + d_dtudPx);
639             System.out.println("d_dtudPy = " + d_dtudPy);
640             System.out.println("d_dtudPz = " + d_dtudPz);
641             System.out.println("d_dtudVx = " + d_dtudVx);
642             System.out.println("d_dtudVy = " + d_dtudVy);
643             System.out.println("d_dtudVz = " + d_dtudVz);
644 
645             System.out.println("d_dRdPx = " + d_dRdPx);
646             System.out.println("d_dRdPy = " + d_dRdPy);
647             System.out.println("d_dRdPz = " + d_dRdPz);
648             System.out.println("d_dRdVx = " + d_dRdVx);
649             System.out.println("d_dRdVy = " + d_dRdVy);
650             System.out.println("d_dRdVz = " + d_dRdVz);
651 
652             System.out.println("d_dtddQx = " + d_dtddQx);
653             System.out.println("d_dtddQy = " + d_dtddQy);
654             System.out.println("d_dtddQz = " + d_dtddQz);
655 
656             System.out.println("d_dtudQx = " + d_dtudQx);
657             System.out.println("d_dtudQy = " + d_dtudQy);
658             System.out.println("d_dtudQz = " + d_dtudQz);
659 
660             System.out.println("d_dRdQx = " + d_dRdQx);
661             System.out.println("d_dRdQy = " + d_dRdQy);
662             System.out.println("d_dRdQz = " + d_dRdQz);
663 
664         }
665 
666         // Dummy return
667         return estimated;
668 
669     }
670 }