1   /* Copyright 2002-2021 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.modifiers;
18  
19  import java.util.Arrays;
20  import java.util.List;
21  
22  import org.hipparchus.Field;
23  import org.hipparchus.CalculusFieldElement;
24  import org.hipparchus.analysis.differentiation.Gradient;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Vector3D;
27  import org.orekit.attitudes.InertialProvider;
28  import org.orekit.estimation.measurements.EstimatedMeasurement;
29  import org.orekit.estimation.measurements.EstimationModifier;
30  import org.orekit.estimation.measurements.GroundStation;
31  import org.orekit.estimation.measurements.TurnAroundRange;
32  import org.orekit.models.earth.troposphere.DiscreteTroposphericModel;
33  import org.orekit.propagation.FieldSpacecraftState;
34  import org.orekit.propagation.SpacecraftState;
35  import org.orekit.utils.Differentiation;
36  import org.orekit.utils.ParameterDriver;
37  import org.orekit.utils.ParameterFunction;
38  
39  /** Class modifying theoretical turn-around TurnAroundRange measurement with tropospheric delay.
40   * The effect of tropospheric correction on the TurnAroundRange is directly computed
41   * through the computation of the tropospheric delay.
42   *
43   * In general, for GNSS, VLBI, ... there is hardly any frequency dependence in the delay.
44   * For SLR techniques however, the frequency dependence is sensitive.
45   *
46   * @author Maxime Journot
47   * @since 9.0
48   */
49  public class TurnAroundRangeTroposphericDelayModifier implements EstimationModifier<TurnAroundRange> {
50  
51      /** Tropospheric delay model. */
52      private final DiscreteTroposphericModel tropoModel;
53  
54      /** Constructor.
55       *
56       * @param model  Tropospheric delay model appropriate for the current TurnAroundRange measurement method.
57       */
58      public TurnAroundRangeTroposphericDelayModifier(final DiscreteTroposphericModel model) {
59          tropoModel = model;
60      }
61  
62      /** Compute the measurement error due to Troposphere.
63       * @param station station
64       * @param state spacecraft state
65       * @return the measurement error due to Troposphere
66       */
67      private double rangeErrorTroposphericModel(final GroundStation station, final SpacecraftState state) {
68          //
69          final Vector3D position = state.getPVCoordinates().getPosition();
70  
71          // elevation
72          final double elevation = station.getBaseFrame().getElevation(position,
73                                                                       state.getFrame(),
74                                                                       state.getDate());
75  
76          // only consider measures above the horizon
77          if (elevation > 0) {
78              // Delay in meters
79              final double delay = tropoModel.pathDelay(elevation, station.getBaseFrame().getPoint(), tropoModel.getParameters(), state.getDate());
80  
81              return delay;
82          }
83  
84          return 0;
85      }
86  
87      /** Compute the measurement error due to Troposphere.
88       * @param <T> type of the element
89       * @param station station
90       * @param state spacecraft state
91       * @param parameters tropospheric model parameters
92       * @return the measurement error due to Troposphere
93       */
94      private <T extends CalculusFieldElement<T>> T rangeErrorTroposphericModel(final GroundStation station,
95                                                                            final FieldSpacecraftState<T> state,
96                                                                            final T[] parameters) {
97          // Field
98          final Field<T> field = state.getDate().getField();
99          final T zero         = field.getZero();
100 
101         //
102         final FieldVector3D<T> position = state.getPVCoordinates().getPosition();
103         final T dsElevation             = station.getBaseFrame().getElevation(position,
104                                                                               state.getFrame(),
105                                                                               state.getDate());
106 
107         // only consider measures above the horizon
108         if (dsElevation.getReal() > 0) {
109             // Delay in meters
110             final T delay = tropoModel.pathDelay(dsElevation, station.getBaseFrame().getPoint(field), parameters, state.getDate());
111 
112             return delay;
113         }
114 
115         return zero;
116     }
117 
118     /** Compute the Jacobian of the delay term wrt state using
119     * automatic differentiation.
120     *
121     * @param derivatives tropospheric delay derivatives
122     *
123     * @return Jacobian of the delay wrt state
124     */
125     private double[][] rangeErrorJacobianState(final double[] derivatives) {
126         final double[][] finiteDifferencesJacobian = new double[1][6];
127         System.arraycopy(derivatives, 0, finiteDifferencesJacobian[0], 0, 6);
128         return finiteDifferencesJacobian;
129     }
130 
131 
132     /** Compute the derivative of the delay term wrt parameters.
133      *
134      * @param station ground station
135      * @param driver driver for the station offset parameter
136      * @param state spacecraft state
137      * @return derivative of the delay wrt station offset parameter
138      */
139     private double rangeErrorParameterDerivative(final GroundStation station,
140                                                  final ParameterDriver driver,
141                                                  final SpacecraftState state) {
142 
143         final ParameterFunction rangeError = new ParameterFunction() {
144             /** {@inheritDoc} */
145             @Override
146             public double value(final ParameterDriver parameterDriver) {
147                 return rangeErrorTroposphericModel(station, state);
148             }
149         };
150 
151         final ParameterFunction rangeErrorDerivative = Differentiation.differentiate(rangeError, 3, 10.0 * driver.getScale());
152 
153         return rangeErrorDerivative.value(driver);
154 
155     }
156 
157     /** Compute the derivative of the delay term wrt parameters using
158     * automatic differentiation.
159     *
160     * @param derivatives tropospheric delay derivatives
161     * @param freeStateParameters dimension of the state.
162     * @return derivative of the delay wrt tropospheric model parameters
163     */
164     private double[] rangeErrorParameterDerivative(final double[] derivatives, final int freeStateParameters) {
165         // 0 ... freeStateParameters - 1 -> derivatives of the delay wrt state
166         // freeStateParameters ... n     -> derivatives of the delay wrt tropospheric parameters
167         final int dim = derivatives.length - freeStateParameters;
168         final double[] rangeError = new double[dim];
169 
170         for (int i = 0; i < dim; i++) {
171             rangeError[i] = derivatives[freeStateParameters + i];
172         }
173 
174         return rangeError;
175     }
176 
177     /** {@inheritDoc} */
178     @Override
179     public List<ParameterDriver> getParametersDrivers() {
180         return tropoModel.getParametersDrivers();
181     }
182 
183     /** {@inheritDoc} */
184     @Override
185     public void modify(final EstimatedMeasurement<TurnAroundRange> estimated) {
186         final TurnAroundRange measurement      = estimated.getObservedMeasurement();
187         final GroundStation   primaryStation   = measurement.getPrimaryStation();
188         final GroundStation   secondaryStation = measurement.getSecondaryStation();
189         final SpacecraftState state            = estimated.getStates()[0];
190 
191         final double[] oldValue = estimated.getEstimatedValue();
192 
193         // Update estimated derivatives with Jacobian of the measure wrt state
194         final TroposphericGradientConverter converter =
195                 new TroposphericGradientConverter(state, 6, new InertialProvider(state.getFrame()));
196         final FieldSpacecraftState<Gradient> gState = converter.getState(tropoModel);
197         final Gradient[] gParameters = converter.getParameters(gState, tropoModel);
198         final Gradient   primaryGDelay        = rangeErrorTroposphericModel(primaryStation, gState, gParameters);
199         final Gradient   secondaryGDelay      = rangeErrorTroposphericModel(secondaryStation, gState, gParameters);
200         final double[]   primaryDerivatives   = primaryGDelay.getGradient();
201         final double[]   secondaryDerivatives = secondaryGDelay.getGradient();
202 
203         final double[][] primaryDjac      = rangeErrorJacobianState(primaryDerivatives);
204         final double[][] secondaryDjac    = rangeErrorJacobianState(secondaryDerivatives);
205         final double[][] stateDerivatives = estimated.getStateDerivatives(0);
206         for (int irow = 0; irow < stateDerivatives.length; ++irow) {
207             for (int jcol = 0; jcol < stateDerivatives[0].length; ++jcol) {
208                 stateDerivatives[irow][jcol] += primaryDjac[irow][jcol] + secondaryDjac[irow][jcol];
209             }
210         }
211         estimated.setStateDerivatives(0, stateDerivatives);
212 
213         int indexPrimary = 0;
214         for (final ParameterDriver driver : getParametersDrivers()) {
215             if (driver.isSelected()) {
216                 // update estimated derivatives with derivative of the modification wrt tropospheric parameters
217                 double parameterDerivative = estimated.getParameterDerivatives(driver)[0];
218                 final double[] derivatives = rangeErrorParameterDerivative(primaryDerivatives, converter.getFreeStateParameters());
219                 parameterDerivative += derivatives[indexPrimary];
220                 estimated.setParameterDerivatives(driver, parameterDerivative);
221                 indexPrimary += 1;
222             }
223 
224         }
225 
226         int indexSecondary = 0;
227         for (final ParameterDriver driver : getParametersDrivers()) {
228             if (driver.isSelected()) {
229                 // update estimated derivatives with derivative of the modification wrt tropospheric parameters
230                 double parameterDerivative = estimated.getParameterDerivatives(driver)[0];
231                 final double[] derivatives = rangeErrorParameterDerivative(secondaryDerivatives, converter.getFreeStateParameters());
232                 parameterDerivative += derivatives[indexSecondary];
233                 estimated.setParameterDerivatives(driver, parameterDerivative);
234                 indexSecondary += 1;
235             }
236 
237         }
238 
239         // Update derivatives with respect to primary station position
240         for (final ParameterDriver driver : Arrays.asList(primaryStation.getClockOffsetDriver(),
241                                                           primaryStation.getEastOffsetDriver(),
242                                                           primaryStation.getNorthOffsetDriver(),
243                                                           primaryStation.getZenithOffsetDriver())) {
244             if (driver.isSelected()) {
245                 double parameterDerivative = estimated.getParameterDerivatives(driver)[0];
246                 parameterDerivative += rangeErrorParameterDerivative(primaryStation, driver, state);
247                 estimated.setParameterDerivatives(driver, parameterDerivative);
248             }
249         }
250 
251         // Update derivatives with respect to secondary station position
252         for (final ParameterDriver driver : Arrays.asList(secondaryStation.getEastOffsetDriver(),
253                                                           secondaryStation.getNorthOffsetDriver(),
254                                                           secondaryStation.getZenithOffsetDriver())) {
255             if (driver.isSelected()) {
256                 double parameterDerivative = estimated.getParameterDerivatives(driver)[0];
257                 parameterDerivative += rangeErrorParameterDerivative(secondaryStation, driver, state);
258                 estimated.setParameterDerivatives(driver, parameterDerivative);
259             }
260         }
261 
262         // Update estimated value taking into account the tropospheric delay.
263         // The tropospheric delay is directly added to the TurnAroundRange.
264         final double[] newValue = oldValue.clone();
265         newValue[0] = newValue[0] + primaryGDelay.getReal() + secondaryGDelay.getReal();
266         estimated.setEstimatedValue(newValue);
267 
268     }
269 
270 }