1   /* Copyright 2002-2024 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.CalculusFieldElement;
23  import org.hipparchus.analysis.differentiation.Gradient;
24  import org.orekit.attitudes.FrameAlignedProvider;
25  import org.orekit.estimation.measurements.EstimatedMeasurement;
26  import org.orekit.estimation.measurements.EstimatedMeasurementBase;
27  import org.orekit.estimation.measurements.EstimationModifier;
28  import org.orekit.estimation.measurements.GroundStation;
29  import org.orekit.estimation.measurements.TurnAroundRange;
30  import org.orekit.frames.TopocentricFrame;
31  import org.orekit.models.earth.ionosphere.IonosphericModel;
32  import org.orekit.propagation.FieldSpacecraftState;
33  import org.orekit.propagation.SpacecraftState;
34  import org.orekit.time.AbsoluteDate;
35  import org.orekit.utils.Differentiation;
36  import org.orekit.utils.ParameterDriver;
37  import org.orekit.utils.ParameterFunction;
38  import org.orekit.utils.TimeSpanMap.Span;
39  
40  /** Class modifying theoretical TurnAroundRange measurement with ionospheric delay.
41   * <p>
42   * The effect of ionospheric correction on the TurnAroundRange is directly computed
43   * through the computation of the ionospheric delay.
44   * </p>
45   * <p>
46   * The ionospheric delay depends on the frequency of the signal (GNSS, VLBI, ...).
47   * For optical measurements (e.g. SLR), the ray is not affected by ionosphere charged particles.
48   * </p>
49   * <p>
50   * Since 10.0, state derivatives and ionospheric parameters derivates are computed
51   * using automatic differentiation.
52   * </p>
53   * @author Maxime Journot
54   * @since 9.0
55   */
56  public class TurnAroundRangeIonosphericDelayModifier implements EstimationModifier<TurnAroundRange> {
57  
58      /** Ionospheric delay model. */
59      private final IonosphericModel ionoModel;
60  
61      /** Frequency [Hz]. */
62      private final double frequency;
63  
64      /** Constructor.
65       *
66       * @param model  Ionospheric delay model appropriate for the current TurnAroundRange measurement method.
67       * @param freq frequency of the signal in Hz
68       */
69      public TurnAroundRangeIonosphericDelayModifier(final IonosphericModel model,
70                                                     final double freq) {
71          ionoModel = model;
72          frequency = freq;
73      }
74  
75      /** Compute the measurement error due to ionosphere.
76       * @param station station
77       * @param state spacecraft state
78       * @return the measurement error due to ionosphere
79       */
80      private double rangeErrorIonosphericModel(final GroundStation station,
81                                                final SpacecraftState state) {
82          // Base frame associated with the station
83          final TopocentricFrame baseFrame = station.getBaseFrame();
84          // Delay in meters
85          return ionoModel.pathDelay(state, baseFrame, frequency, ionoModel.getParameters(state.getDate()));
86      }
87  
88      /** Compute the measurement error due to ionosphere.
89       * @param <T> type of the elements
90       * @param station station
91       * @param state spacecraft state
92       * @param parameters ionospheric model parameters
93       * @return the measurement error due to ionosphere
94       */
95      private <T extends CalculusFieldElement<T>> T rangeErrorIonosphericModel(final GroundStation station,
96                                                                               final FieldSpacecraftState<T> state,
97                                                                               final T[] parameters) {
98          // Base frame associated with the station
99          final TopocentricFrame baseFrame = station.getBaseFrame();
100         // Delay in meters
101         return ionoModel.pathDelay(state, baseFrame, frequency, parameters);
102     }
103 
104     /** Compute the Jacobian of the delay term wrt state using
105     * automatic differentiation.
106     *
107     * @param derivatives ionospheric delay derivatives
108     *
109     * @return Jacobian of the delay wrt state
110     */
111     private double[][] rangeErrorJacobianState(final double[] derivatives) {
112         final double[][] finiteDifferencesJacobian = new double[1][6];
113         System.arraycopy(derivatives, 0, finiteDifferencesJacobian[0], 0, 6);
114         return finiteDifferencesJacobian;
115     }
116 
117 
118     /** Compute the derivative of the delay term wrt parameters.
119      *
120      * @param station ground station
121      * @param driver driver for the station offset parameter
122      * @param state spacecraft state
123      * @return derivative of the delay wrt station offset parameter
124      */
125     private double rangeErrorParameterDerivative(final GroundStation station,
126                                                  final ParameterDriver driver,
127                                                  final SpacecraftState state) {
128 
129         final ParameterFunction rangeError = new ParameterFunction() {
130             /** {@inheritDoc} */
131             @Override
132             public double value(final ParameterDriver parameterDriver, final AbsoluteDate date) {
133                 return rangeErrorIonosphericModel(station, state);
134             }
135         };
136 
137         final ParameterFunction rangeErrorDerivative =
138                         Differentiation.differentiate(rangeError, 3, 10.0 * driver.getScale());
139 
140         return rangeErrorDerivative.value(driver, state.getDate());
141 
142     }
143 
144     /** Compute the derivative of the delay term wrt parameters using
145     * automatic differentiation.
146     *
147     * @param derivatives ionospheric delay derivatives
148     * @param freeStateParameters dimension of the state.
149     * @return derivative of the delay wrt ionospheric model parameters
150     */
151     private double[] rangeErrorParameterDerivative(final double[] derivatives, final int freeStateParameters) {
152         // 0 ... freeStateParameters - 1 -> derivatives of the delay wrt state
153         // freeStateParameters ... n     -> derivatives of the delay wrt ionospheric parameters
154         return Arrays.copyOfRange(derivatives, freeStateParameters, derivatives.length);
155     }
156 
157     /** {@inheritDoc} */
158     @Override
159     public List<ParameterDriver> getParametersDrivers() {
160         return ionoModel.getParametersDrivers();
161     }
162 
163     @Override
164     public void modifyWithoutDerivatives(final EstimatedMeasurementBase<TurnAroundRange> estimated) {
165 
166         final TurnAroundRange measurement      = estimated.getObservedMeasurement();
167         final GroundStation   primaryStation   = measurement.getPrimaryStation();
168         final GroundStation   secondaryStation = measurement.getSecondaryStation();
169         final SpacecraftState state            = estimated.getStates()[0];
170 
171         // Update estimated value taking into account the ionospheric delay.
172         // The ionospheric delay is directly added to the TurnAroundRange.
173         final double[] newValue     = estimated.getEstimatedValue();
174         final double primaryDelay   = rangeErrorIonosphericModel(primaryStation, state);
175         final double secondaryDelay = rangeErrorIonosphericModel(secondaryStation, state);
176         newValue[0] = newValue[0] + primaryDelay + secondaryDelay;
177         estimated.modifyEstimatedValue(this, newValue);
178 
179     }
180 
181     @Override
182     public void modify(final EstimatedMeasurement<TurnAroundRange> estimated) {
183         final TurnAroundRange measurement      = estimated.getObservedMeasurement();
184         final GroundStation   primaryStation   = measurement.getPrimaryStation();
185         final GroundStation   secondaryStation = measurement.getSecondaryStation();
186         final SpacecraftState state            = estimated.getStates()[0];
187 
188         // Update estimated derivatives with Jacobian of the measure wrt state
189         final ModifierGradientConverter converter =
190                 new ModifierGradientConverter(state, 6, new FrameAlignedProvider(state.getFrame()));
191         final FieldSpacecraftState<Gradient> gState = converter.getState(ionoModel);
192         final Gradient[] gParameters        = converter.getParametersAtStateDate(gState, ionoModel);
193         final Gradient primaryGDelay        = rangeErrorIonosphericModel(primaryStation, gState, gParameters);
194         final Gradient secondaryGDelay      = rangeErrorIonosphericModel(secondaryStation, gState, gParameters);
195         final double[] primaryDerivatives   = primaryGDelay.getGradient();
196         final double[] secondaryDerivatives = secondaryGDelay.getGradient();
197 
198         final double[][] primaryDjac      = rangeErrorJacobianState(primaryDerivatives);
199         final double[][] secondaryDjac    = rangeErrorJacobianState(secondaryDerivatives);
200         final double[][] stateDerivatives = estimated.getStateDerivatives(0);
201         for (int irow = 0; irow < stateDerivatives.length; ++irow) {
202             for (int jcol = 0; jcol < stateDerivatives[0].length; ++jcol) {
203                 stateDerivatives[irow][jcol] += primaryDjac[irow][jcol] + secondaryDjac[irow][jcol];
204             }
205         }
206         estimated.setStateDerivatives(0, stateDerivatives);
207 
208         int indexPrimary = 0;
209         for (final ParameterDriver driver : getParametersDrivers()) {
210             if (driver.isSelected()) {
211                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
212                     // update estimated derivatives with derivative of the modification wrt ionospheric parameters
213                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
214                     final double[] derivatives = rangeErrorParameterDerivative(primaryDerivatives, converter.getFreeStateParameters());
215                     parameterDerivative += derivatives[indexPrimary];
216                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
217                     indexPrimary += 1;
218                 }
219             }
220 
221         }
222 
223         int indexSecondary = 0;
224         for (final ParameterDriver driver : getParametersDrivers()) {
225             if (driver.isSelected()) {
226                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
227                     // update estimated derivatives with derivative of the modification wrt ionospheric parameters
228                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
229                     final double[] derivatives = rangeErrorParameterDerivative(secondaryDerivatives, converter.getFreeStateParameters());
230                     parameterDerivative += derivatives[indexSecondary];
231                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
232                     indexSecondary += 1;
233                 }
234             }
235 
236         }
237 
238         // Update derivatives with respect to primary station position
239         for (final ParameterDriver driver : Arrays.asList(primaryStation.getClockOffsetDriver(),
240                                                           primaryStation.getEastOffsetDriver(),
241                                                           primaryStation.getNorthOffsetDriver(),
242                                                           primaryStation.getZenithOffsetDriver())) {
243             if (driver.isSelected()) {
244                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
245 
246                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
247                     parameterDerivative += rangeErrorParameterDerivative(primaryStation, driver, state);
248                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
249                 }
250             }
251         }
252 
253         // Update derivatives with respect to secondary station position
254         for (final ParameterDriver driver : Arrays.asList(secondaryStation.getEastOffsetDriver(),
255                                                           secondaryStation.getNorthOffsetDriver(),
256                                                           secondaryStation.getZenithOffsetDriver())) {
257             if (driver.isSelected()) {
258                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
259 
260                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
261                     parameterDerivative += rangeErrorParameterDerivative(secondaryStation, driver, state);
262                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
263                 }
264             }
265         }
266 
267         // Update estimated value taking into account the ionospheric delay.
268         // The ionospheric delay is directly added to the TurnAroundRange.
269         final double[] newValue = estimated.getEstimatedValue();
270         newValue[0] = newValue[0] + primaryGDelay.getReal() + secondaryGDelay.getReal();
271         estimated.modifyEstimatedValue(this, newValue);
272     }
273 
274 }