TurnAroundRangeTroposphericDelayModifier.java

  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.  * 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. import java.util.Arrays;
  19. import java.util.List;

  20. import org.hipparchus.CalculusFieldElement;
  21. import org.hipparchus.Field;
  22. import org.hipparchus.analysis.differentiation.Gradient;
  23. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  24. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  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.TurnAroundRange;
  31. import org.orekit.models.earth.troposphere.TroposphericModel;
  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.FieldTrackingCoordinates;
  37. import org.orekit.utils.ParameterDriver;
  38. import org.orekit.utils.ParameterFunction;
  39. import org.orekit.utils.TimeSpanMap.Span;
  40. import org.orekit.utils.TrackingCoordinates;

  41. /** Class modifying theoretical turn-around TurnAroundRange measurement with tropospheric delay.
  42.  * <p>
  43.  * The effect of tropospheric correction on the TurnAroundRange is directly computed
  44.  * through the computation of the tropospheric delay.
  45.  * </p>
  46.  * <p>
  47.  * In general, for GNSS, VLBI, ... there is hardly any frequency dependence in the delay.
  48.  * For SLR techniques however, the frequency dependence is sensitive.
  49.  * </p>
  50.  *
  51.  * @author Maxime Journot
  52.  * @since 9.0
  53.  */
  54. public class TurnAroundRangeTroposphericDelayModifier implements EstimationModifier<TurnAroundRange> {

  55.     /** Tropospheric delay model. */
  56.     private final TroposphericModel tropoModel;

  57.     /** Constructor.
  58.      *
  59.      * @param model  Tropospheric delay model appropriate for the current TurnAroundRange measurement method.
  60.      * @since 12.1
  61.      */
  62.     public TurnAroundRangeTroposphericDelayModifier(final TroposphericModel model) {
  63.         tropoModel = model;
  64.     }

  65.     /** {@inheritDoc} */
  66.     @Override
  67.     public String getEffectName() {
  68.         return "troposphere";
  69.     }

  70.     /** Compute the measurement error due to Troposphere.
  71.      * @param station station
  72.      * @param state spacecraft state
  73.      * @return the measurement error due to Troposphere
  74.      */
  75.     private double rangeErrorTroposphericModel(final GroundStation station, final SpacecraftState state) {
  76.         //
  77.         final Vector3D position = state.getPosition();

  78.         // tracking
  79.         final TrackingCoordinates trackingCoordinates =
  80.                         station.getBaseFrame().getTrackingCoordinates(position, state.getFrame(), state.getDate());

  81.         // only consider measures above the horizon
  82.         if (trackingCoordinates.getElevation() > 0) {
  83.             // Delay in meters
  84.             return tropoModel.pathDelay(trackingCoordinates,
  85.                                         station.getOffsetGeodeticPoint(state.getDate()),
  86.                                         tropoModel.getParameters(state.getDate()), state.getDate()).
  87.                                  getDelay();
  88.         }

  89.         return 0;
  90.     }

  91.     /** Compute the measurement error due to Troposphere.
  92.      * @param <T> type of the element
  93.      * @param station station
  94.      * @param state spacecraft state
  95.      * @param parameters tropospheric model parameters
  96.      * @return the measurement error due to Troposphere
  97.      */
  98.     private <T extends CalculusFieldElement<T>> T rangeErrorTroposphericModel(final GroundStation station,
  99.                                                                           final FieldSpacecraftState<T> state,
  100.                                                                           final T[] parameters) {
  101.         // Field
  102.         final Field<T> field = state.getDate().getField();
  103.         final T zero         = field.getZero();

  104.         //
  105.         final FieldVector3D<T> position = state.getPosition();
  106.         final FieldTrackingCoordinates<T> trackingCoordinates =
  107.                         station.getBaseFrame().getTrackingCoordinates(position,  state.getFrame(), state.getDate());

  108.         // only consider measures above the horizon
  109.         if (trackingCoordinates.getElevation().getReal() > 0) {
  110.             // Delay in meters
  111.             return tropoModel.pathDelay(trackingCoordinates,
  112.                                         station.getOffsetGeodeticPoint(state.getDate()),
  113.                                         parameters, state.getDate()).
  114.                             getDelay();
  115.         }

  116.         return zero;
  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.     /** Compute the derivative of the delay term wrt parameters.
  131.      *
  132.      * @param station ground station
  133.      * @param driver driver for the station offset parameter
  134.      * @param state spacecraft state
  135.      * @return derivative of the delay wrt station offset parameter
  136.      */
  137.     private double rangeErrorParameterDerivative(final GroundStation station,
  138.                                                  final ParameterDriver driver,
  139.                                                  final SpacecraftState state) {

  140.         final ParameterFunction rangeError = new ParameterFunction() {
  141.             /** {@inheritDoc} */
  142.             @Override
  143.             public double value(final ParameterDriver parameterDriver, final AbsoluteDate date) {
  144.                 return rangeErrorTroposphericModel(station, state);
  145.             }
  146.         };

  147.         final ParameterFunction rangeErrorDerivative = Differentiation.differentiate(rangeError, 3, 10.0 * driver.getScale());

  148.         return rangeErrorDerivative.value(driver, state.getDate());

  149.     }

  150.     /** Compute the derivative of the delay term wrt parameters using
  151.     * automatic differentiation.
  152.     *
  153.     * @param derivatives tropospheric delay derivatives
  154.     * @param freeStateParameters dimension of the state.
  155.     * @return derivative of the delay wrt tropospheric model parameters
  156.     */
  157.     private double[] rangeErrorParameterDerivative(final double[] derivatives, final int freeStateParameters) {
  158.         // 0 ... freeStateParameters - 1 -> derivatives of the delay wrt state
  159.         // freeStateParameters ... n     -> derivatives of the delay wrt tropospheric parameters
  160.         return Arrays.copyOfRange(derivatives, freeStateParameters, derivatives.length);
  161.     }

  162.     /** {@inheritDoc} */
  163.     @Override
  164.     public List<ParameterDriver> getParametersDrivers() {
  165.         return tropoModel.getParametersDrivers();
  166.     }

  167.     /** {@inheritDoc} */
  168.     @Override
  169.     public void modifyWithoutDerivatives(final EstimatedMeasurementBase<TurnAroundRange> estimated) {

  170.         final TurnAroundRange measurement      = estimated.getObservedMeasurement();
  171.         final GroundStation   primaryStation   = measurement.getPrimaryStation();
  172.         final GroundStation   secondaryStation = measurement.getSecondaryStation();
  173.         final SpacecraftState state            = estimated.getStates()[0];

  174.         // Update estimated value taking into account the tropospheric delay.
  175.         // The tropospheric delay is directly added to the TurnAroundRange.
  176.         final double[] newValue       = estimated.getEstimatedValue();
  177.         final double   primaryDelay   = rangeErrorTroposphericModel(primaryStation, state);
  178.         final double   secondaryDelay = rangeErrorTroposphericModel(secondaryStation, state);
  179.         newValue[0] = newValue[0] + primaryDelay + secondaryDelay;
  180.         estimated.modifyEstimatedValue(this, newValue);

  181.     }
  182.     /** {@inheritDoc} */
  183.     @Override
  184.     public void modify(final EstimatedMeasurement<TurnAroundRange> estimated) {
  185.         final TurnAroundRange measurement      = estimated.getObservedMeasurement();
  186.         final GroundStation   primaryStation   = measurement.getPrimaryStation();
  187.         final GroundStation   secondaryStation = measurement.getSecondaryStation();
  188.         final SpacecraftState state            = estimated.getStates()[0];

  189.         final double[] oldValue = estimated.getEstimatedValue();

  190.         // Update estimated derivatives with Jacobian of the measure wrt state
  191.         final ModifierGradientConverter converter =
  192.                 new ModifierGradientConverter(state, 6, new FrameAlignedProvider(state.getFrame()));
  193.         final FieldSpacecraftState<Gradient> gState = converter.getState(tropoModel);
  194.         final Gradient[] gParameters = converter.getParametersAtStateDate(gState, tropoModel);
  195.         final Gradient   primaryGDelay        = rangeErrorTroposphericModel(primaryStation, gState, gParameters);
  196.         final Gradient   secondaryGDelay      = rangeErrorTroposphericModel(secondaryStation, gState, gParameters);
  197.         final double[]   primaryDerivatives   = primaryGDelay.getGradient();
  198.         final double[]   secondaryDerivatives = secondaryGDelay.getGradient();

  199.         final double[][] primaryDjac      = rangeErrorJacobianState(primaryDerivatives);
  200.         final double[][] secondaryDjac    = rangeErrorJacobianState(secondaryDerivatives);
  201.         final double[][] stateDerivatives = estimated.getStateDerivatives(0);
  202.         for (int irow = 0; irow < stateDerivatives.length; ++irow) {
  203.             for (int jcol = 0; jcol < stateDerivatives[0].length; ++jcol) {
  204.                 stateDerivatives[irow][jcol] += primaryDjac[irow][jcol] + secondaryDjac[irow][jcol];
  205.             }
  206.         }
  207.         estimated.setStateDerivatives(0, stateDerivatives);

  208.         int indexPrimary = 0;
  209.         for (final ParameterDriver driver : getParametersDrivers()) {
  210.             if (driver.isSelected()) {
  211.                 // update estimated derivatives with derivative of the modification wrt tropospheric parameters
  212.                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  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.         int indexSecondary = 0;
  222.         for (final ParameterDriver driver : getParametersDrivers()) {
  223.             if (driver.isSelected()) {
  224.                 // update estimated derivatives with derivative of the modification wrt tropospheric parameters
  225.                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  226.                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
  227.                     final double[] derivatives = rangeErrorParameterDerivative(secondaryDerivatives, converter.getFreeStateParameters());
  228.                     parameterDerivative += derivatives[indexSecondary];
  229.                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
  230.                     indexSecondary += 1;
  231.                 }
  232.             }

  233.         }

  234.         // Update derivatives with respect to primary station position
  235.         for (final ParameterDriver driver : Arrays.asList(primaryStation.getClockOffsetDriver(),
  236.                                                           primaryStation.getEastOffsetDriver(),
  237.                                                           primaryStation.getNorthOffsetDriver(),
  238.                                                           primaryStation.getZenithOffsetDriver())) {
  239.             if (driver.isSelected()) {
  240.                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {

  241.                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
  242.                     parameterDerivative += rangeErrorParameterDerivative(primaryStation, driver, state);
  243.                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
  244.                 }
  245.             }
  246.         }

  247.         // Update derivatives with respect to secondary station position
  248.         for (final ParameterDriver driver : Arrays.asList(secondaryStation.getEastOffsetDriver(),
  249.                                                           secondaryStation.getNorthOffsetDriver(),
  250.                                                           secondaryStation.getZenithOffsetDriver())) {
  251.             if (driver.isSelected()) {
  252.                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {

  253.                     double parameterDerivative = estimated.getParameterDerivatives(driver, span.getStart())[0];
  254.                     parameterDerivative += rangeErrorParameterDerivative(secondaryStation, driver, state);
  255.                     estimated.setParameterDerivatives(driver, span.getStart(), parameterDerivative);
  256.                 }
  257.             }
  258.         }

  259.         // Update estimated value taking into account the tropospheric delay.
  260.         // The tropospheric delay is directly added to the TurnAroundRange.
  261.         final double[] newValue = oldValue.clone();
  262.         newValue[0] = newValue[0] + primaryGDelay.getReal() + secondaryGDelay.getReal();
  263.         estimated.modifyEstimatedValue(this, newValue);

  264.     }

  265. }