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.Field;
24  import org.hipparchus.analysis.differentiation.Gradient;
25  import org.orekit.attitudes.FrameAlignedProvider;
26  import org.orekit.estimation.measurements.EstimatedMeasurement;
27  import org.orekit.estimation.measurements.EstimatedMeasurementBase;
28  import org.orekit.estimation.measurements.EstimationModifier;
29  import org.orekit.estimation.measurements.GroundStation;
30  import org.orekit.estimation.measurements.gnss.Phase;
31  import org.orekit.models.earth.troposphere.TroposphericModel;
32  import org.orekit.propagation.FieldSpacecraftState;
33  import org.orekit.propagation.SpacecraftState;
34  import org.orekit.utils.Differentiation;
35  import org.orekit.utils.FieldTrackingCoordinates;
36  import org.orekit.utils.ParameterDriver;
37  import org.orekit.utils.ParameterFunction;
38  import org.orekit.utils.TimeSpanMap.Span;
39  import org.orekit.utils.TrackingCoordinates;
40  
41  /**
42   * Class modifying theoretical phase measurement with tropospheric delay.
43   * The effect of tropospheric correction on the phase is directly computed
44   * through the computation of the tropospheric delay.
45   * @author David Soulard
46   * @author Bryan Cazabonne
47   * @since 10.2
48   */
49  public class PhaseTroposphericDelayModifier implements EstimationModifier<Phase> {
50  
51      /** Tropospheric delay model. */
52      private final TroposphericModel tropoModel;
53  
54      /** Constructor.
55       *
56       * @param model  Tropospheric delay model appropriate for the current range measurement method.
57       * @since 12.1
58       */
59      public PhaseTroposphericDelayModifier(final TroposphericModel model) {
60          tropoModel = model;
61      }
62  
63  /** {@inheritDoc} */
64      @Override
65          public String getEffectName() {
66          return "troposphere";
67      }
68  
69      /** Compute the measurement error due to Troposphere.
70       * @param station station
71       * @param state spacecraft state
72       * @param wavelength wavelength of the signal
73       * @return the measurement error due to Troposphere
74       */
75      private double phaseErrorTroposphericModel(final GroundStation station, final SpacecraftState state, final double wavelength) {
76  
77          // tracking
78          final TrackingCoordinates trackingCoordinates =
79                          station.getBaseFrame().getTrackingCoordinates(state.getPosition(), state.getFrame(), state.getDate());
80  
81          // only consider measures above the horizon
82          if (trackingCoordinates.getElevation() > 0) {
83              // delay in meters
84              final double delay = tropoModel.pathDelay(trackingCoordinates,
85                                                        station.getOffsetGeodeticPoint(state.getDate()),
86                                                        tropoModel.getParameters(state.getDate()), state.getDate()).
87                                   getDelay();
88  
89              return delay / wavelength;
90          }
91  
92          return 0;
93      }
94  
95      /** Compute the measurement error due to Troposphere.
96       * @param <T> type of the element
97       * @param station station
98       * @param state spacecraft state
99       * @param parameters tropospheric model parameters
100      * @param wavelength of the measurements
101      * @return the measurement error due to Troposphere
102      */
103     private <T extends CalculusFieldElement<T>> T phaseErrorTroposphericModel(final GroundStation station,
104                                                                           final FieldSpacecraftState<T> state,
105                                                                           final T[] parameters, final double wavelength) {
106 
107         // Field
108         final Field<T> field = state.getDate().getField();
109         final T zero         = field.getZero();
110 
111         // tracking
112         final FieldTrackingCoordinates<T> trackingCoordinates =
113                         station.getBaseFrame().getTrackingCoordinates(state.getPosition(), state.getFrame(), state.getDate());
114 
115 
116         // only consider measures above the horizon
117         if (trackingCoordinates.getElevation().getReal() > 0) {
118             // delay in meters
119             final T delay = tropoModel.pathDelay(trackingCoordinates,
120                                                  station.getOffsetGeodeticPoint(state.getDate()),
121                                                  parameters, state.getDate()).
122                             getDelay();
123 
124             return delay.divide(wavelength);
125         }
126 
127         return zero;
128     }
129 
130     /** Compute the Jacobian of the delay term wrt state using
131     * automatic differentiation.
132     *
133     * @param derivatives tropospheric delay derivatives
134     *
135     * @return Jacobian of the delay wrt state
136     */
137     private double[][] phaseErrorJacobianState(final double[] derivatives) {
138         final double[][] finiteDifferencesJacobian = new double[1][6];
139         System.arraycopy(derivatives, 0, finiteDifferencesJacobian[0], 0, 6);
140         return finiteDifferencesJacobian;
141     }
142 
143     /** Compute the derivative of the delay term wrt parameters.
144      *
145      * @param station ground station
146      * @param driver driver for the station offset parameter
147      * @param state spacecraft state
148      * @param wavelength wavelength of the signal
149      * @return derivative of the delay wrt station offset parameter
150      */
151     private double phaseErrorParameterDerivative(final GroundStation station,
152                                                  final ParameterDriver driver,
153                                                  final SpacecraftState state,
154                                                  final double wavelength) {
155         final ParameterFunction rangeError = (parameterDriver, date) -> phaseErrorTroposphericModel(station, state, wavelength);
156         final ParameterFunction phaseErrorDerivative =
157                         Differentiation.differentiate(rangeError, 3, 10.0 * driver.getScale());
158         return phaseErrorDerivative.value(driver, state.getDate());
159 
160     }
161 
162     /** Compute the derivative of the delay term wrt parameters using
163     * automatic differentiation.
164     *
165     * @param derivatives tropospheric delay derivatives
166     * @param freeStateParameters dimension of the state.
167     * @return derivative of the delay wrt tropospheric model parameters
168     */
169     private double[] phaseErrorParameterDerivative(final double[] derivatives, final int freeStateParameters) {
170         // 0 ... freeStateParameters - 1   -> derivatives of the delay wrt state
171         // freeStateParameters ... n       -> derivatives of the delay wrt tropospheric parameters
172         return Arrays.copyOfRange(derivatives, freeStateParameters, derivatives.length);
173     }
174 
175     /** {@inheritDoc} */
176     @Override
177     public List<ParameterDriver> getParametersDrivers() {
178         return tropoModel.getParametersDrivers();
179     }
180 
181     /** {@inheritDoc} */
182     @Override
183     public void modifyWithoutDerivatives(final EstimatedMeasurementBase<Phase> estimated) {
184 
185         final Phase           measurement = estimated.getObservedMeasurement();
186         final GroundStation   station     = measurement.getStation();
187         final SpacecraftState state       = estimated.getStates()[0];
188 
189         // Update estimated value taking into account the tropospheric delay.
190         // The tropospheric delay is directly added to the phase.
191         final double[] newValue = estimated.getEstimatedValue();
192         final double delay = phaseErrorTroposphericModel(station, state, measurement.getWavelength());
193         newValue[0] = newValue[0] + delay;
194         estimated.modifyEstimatedValue(this, newValue);
195 
196     }
197 
198     /** {@inheritDoc} */
199     @Override
200     public void modify(final EstimatedMeasurement<Phase> estimated) {
201         final Phase           measurement = estimated.getObservedMeasurement();
202         final GroundStation   station     = measurement.getStation();
203         final SpacecraftState state       = estimated.getStates()[0];
204 
205         // update estimated derivatives with Jacobian of the measure wrt state
206         final ModifierGradientConverter converter = new ModifierGradientConverter(state, 6, new FrameAlignedProvider(state.getFrame()));
207         final FieldSpacecraftState<Gradient> gState = converter.getState(tropoModel);
208         final Gradient[] gParameters = converter.getParametersAtStateDate(gState, tropoModel);
209         final Gradient gDelay = phaseErrorTroposphericModel(station, gState, gParameters, measurement.getWavelength());
210         final double[] derivatives = gDelay.getGradient();
211 
212         // Update state derivatives
213         final double[][] djac = phaseErrorJacobianState(derivatives);
214         final double[][] stateDerivatives = estimated.getStateDerivatives(0);
215         for (int irow = 0; irow < stateDerivatives.length; ++irow) {
216             for (int jcol = 0; jcol < stateDerivatives[0].length; ++jcol) {
217                 stateDerivatives[irow][jcol] += djac[irow][jcol];
218             }
219         }
220         estimated.setStateDerivatives(0, stateDerivatives);
221 
222 
223         // Update tropospheric parameter derivatives
224         int index = 0;
225         for (final ParameterDriver driver : getParametersDrivers()) {
226             if (driver.isSelected()) {
227                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
228 
229                     // update estimated derivatives with derivative of the modification wrt tropospheric parameters
230                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
231                     final double[] dDelaydP    = phaseErrorParameterDerivative(derivatives, converter.getFreeStateParameters());
232                     parameterDerivative += dDelaydP[index];
233                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
234                     index = index + 1;
235                 }
236             }
237         }
238 
239         // Update station parameter derivatives
240         for (final ParameterDriver driver : Arrays.asList(station.getClockOffsetDriver(),
241                                                           station.getEastOffsetDriver(),
242                                                           station.getNorthOffsetDriver(),
243                                                           station.getZenithOffsetDriver())) {
244             if (driver.isSelected()) {
245                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
246                     // update estimated derivatives with derivative of the modification wrt station parameters
247                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
248                     parameterDerivative += phaseErrorParameterDerivative(station, driver, state, measurement.getWavelength());
249                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
250                 }
251             }
252         }
253 
254         // Update estimated value taking into account the tropospheric delay.
255         modifyWithoutDerivatives(estimated);
256 
257     }
258 
259 }
260