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