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