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    * The ASF 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.gnss.Phase;
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.utils.Constants;
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  /**
41   * Class modifying theoretical phase measurement with ionospheric delay.
42   * The effect of ionospheric correction on the phase is directly computed
43   * through the computation of the ionospheric delay.
44   * @author David Soulard
45   * @author Bryan Cazabonne
46   * @since 10.2
47   */
48  public class PhaseIonosphericDelayModifier implements EstimationModifier<Phase> {
49  
50      /** Ionospheric delay model. */
51      private final IonosphericModel ionoModel;
52  
53      /** Frequency [Hz]. */
54      private final double frequency;
55  
56      /** Constructor.
57       *
58       * @param model  Ionospheric delay model appropriate for the current range measurement method.
59       * @param freq frequency of the signal in Hz
60       */
61      public PhaseIonosphericDelayModifier(final IonosphericModel model,
62                                           final double freq) {
63          ionoModel = model;
64          frequency = freq;
65      }
66  
67  /** {@inheritDoc} */
68      @Override
69          public String getEffectName() {
70          return "ionosphere";
71      }
72  
73      /** Compute the measurement error due to ionosphere.
74       * @param station station
75       * @param state spacecraft state
76       * @return the measurement error due to ionosphere
77       */
78      private double phaseErrorIonosphericModel(final GroundStation station,
79                                                final SpacecraftState state) {
80  
81          // Base frame associated with the station
82          final TopocentricFrame baseFrame = station.getBaseFrame();
83          final double wavelength  = Constants.SPEED_OF_LIGHT / frequency;
84          // delay in meters
85          final double delay = ionoModel.pathDelay(state, baseFrame, frequency, ionoModel.getParameters(state.getDate()));
86          return delay / wavelength;
87      }
88  
89      /** Compute the measurement error due to ionosphere.
90       * @param <T> type of the element
91       * @param station station
92       * @param state spacecraft state
93       * @param parameters ionospheric model parameters at state date
94       * @return the measurement error due to ionosphere
95       */
96      private <T extends CalculusFieldElement<T>> T phaseErrorIonosphericModel(final GroundStation station,
97                                                                               final FieldSpacecraftState<T> state,
98                                                                               final T[] parameters) {
99  
100         // Base frame associated with the station
101         final TopocentricFrame baseFrame = station.getBaseFrame();
102         final double wavelength  = Constants.SPEED_OF_LIGHT / frequency;
103         // delay in meters
104         final T delay = ionoModel.pathDelay(state, baseFrame, frequency, parameters);
105         return delay.divide(wavelength);
106     }
107 
108     /** Compute the Jacobian of the delay term wrt state using
109     * automatic differentiation.
110     *
111     * @param derivatives ionospheric delay derivatives
112     * @param freeStateParameters dimension of the state.
113     *
114     * @return Jacobian of the delay wrt state
115     */
116     private double[][] phaseErrorJacobianState(final double[] derivatives, final int freeStateParameters) {
117         final double[][] finiteDifferencesJacobian = new double[1][6];
118         System.arraycopy(derivatives, 0, finiteDifferencesJacobian[0], 0, freeStateParameters);
119         return finiteDifferencesJacobian;
120     }
121 
122 
123     /** Compute the derivative of the delay term wrt parameters.
124      *
125      * @param station ground station
126      * @param driver driver for the station offset parameter
127      * @param state spacecraft state
128      * @return derivative of the delay wrt station offset parameter
129      */
130     private double phaseErrorParameterDerivative(final GroundStation station,
131                                                  final ParameterDriver driver,
132                                                  final SpacecraftState state) {
133         final ParameterFunction phaseError = (parameterDriver, date) -> phaseErrorIonosphericModel(station, state);
134         final ParameterFunction phaseErrorDerivative =
135                         Differentiation.differentiate(phaseError, 3, 10.0 * driver.getScale());
136         return phaseErrorDerivative.value(driver, state.getDate());
137 
138     }
139 
140     /** Compute the derivative of the delay term wrt parameters using
141     * automatic differentiation.
142     *
143     * @param derivatives ionospheric delay derivatives
144     * @param freeStateParameters dimension of the state.
145     * @return derivative of the delay wrt ionospheric model parameters
146     */
147     private double[] phaseErrorParameterDerivative(final double[] derivatives, final int freeStateParameters) {
148         // 0 ... freeStateParameters - 1 -> derivatives of the delay wrt state
149         // freeStateParameters ... n     -> derivatives of the delay wrt ionospheric parameters
150         return Arrays.copyOfRange(derivatives, freeStateParameters, derivatives.length);
151     }
152 
153     /** {@inheritDoc} */
154     @Override
155     public List<ParameterDriver> getParametersDrivers() {
156         return ionoModel.getParametersDrivers();
157     }
158 
159     @Override
160     public void modifyWithoutDerivatives(final EstimatedMeasurementBase<Phase> estimated) {
161 
162         final Phase           measurement = estimated.getObservedMeasurement();
163         final GroundStation   station     = measurement.getStation();
164         final SpacecraftState state       = estimated.getStates()[0];
165 
166         // Update estimated value taking into account the ionospheric delay.
167         // The ionospheric delay is directly subtracted to the phase.
168         final double[] newValue = estimated.getEstimatedValue();
169         final double delay = phaseErrorIonosphericModel(station, state);
170         newValue[0] = newValue[0] - delay;
171         estimated.modifyEstimatedValue(this, newValue);
172 
173     }
174 
175     @Override
176     public void modify(final EstimatedMeasurement<Phase> estimated) {
177 
178         final Phase           measurement = estimated.getObservedMeasurement();
179         final GroundStation   station     = measurement.getStation();
180         final SpacecraftState state       = estimated.getStates()[0];
181 
182         // Compute ionospheric delay (the division by the wavelength is performed)
183         final ModifierGradientConverter converter =
184                         new ModifierGradientConverter(state, 6, new FrameAlignedProvider(state.getFrame()));
185         final FieldSpacecraftState<Gradient> gState = converter.getState(ionoModel);
186         final Gradient[] gParameters = converter.getParametersAtStateDate(gState, ionoModel);
187         final Gradient gDelay = phaseErrorIonosphericModel(station, gState, gParameters);
188         final double[] derivatives = gDelay.getGradient();
189 
190         // Update state derivatives
191         final double[][] djac = phaseErrorJacobianState(derivatives, converter.getFreeStateParameters());
192         final double[][] stateDerivatives = estimated.getStateDerivatives(0);
193         for (int irow = 0; irow < stateDerivatives.length; ++irow) {
194             for (int jcol = 0; jcol < stateDerivatives[0].length; ++jcol) {
195                 stateDerivatives[irow][jcol] -= djac[irow][jcol];
196             }
197         }
198         estimated.setStateDerivatives(0, stateDerivatives);
199 
200         // Update ionospheric parameter derivatives
201         int index = 0;
202         for (final ParameterDriver driver : getParametersDrivers()) {
203             if (driver.isSelected()) {
204                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
205                     // update estimated derivatives with derivative of the modification wrt ionospheric parameters
206                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
207                     final double[] dDelaydP    = phaseErrorParameterDerivative(derivatives, converter.getFreeStateParameters());
208                     parameterDerivative -= dDelaydP[index];
209                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
210                     index = index + 1;
211                 }
212             }
213 
214         }
215 
216         // Update station parameter derivatives
217         for (final ParameterDriver driver : Arrays.asList(station.getClockOffsetDriver(),
218                                                           station.getEastOffsetDriver(),
219                                                           station.getNorthOffsetDriver(),
220                                                           station.getZenithOffsetDriver())) {
221             if (driver.isSelected()) {
222                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
223                     // update estimated derivatives with derivative of the modification wrt station parameters
224                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
225                     parameterDerivative -= phaseErrorParameterDerivative(station, driver, state);
226                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
227                 }
228             }
229         }
230 
231         // Update estimated value taking into account the ionospheric delay.
232         modifyWithoutDerivatives(estimated);
233 
234     }
235 
236 }
237