AbstractPropagatorConverter.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.propagation.conversion;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.List;

  21. import org.hipparchus.analysis.MultivariateVectorFunction;
  22. import org.hipparchus.exception.MathRuntimeException;
  23. import org.hipparchus.linear.DiagonalMatrix;
  24. import org.hipparchus.optim.ConvergenceChecker;
  25. import org.hipparchus.optim.SimpleVectorValueChecker;
  26. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresBuilder;
  27. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresFactory;
  28. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer;
  29. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
  30. import org.hipparchus.optim.nonlinear.vector.leastsquares.LevenbergMarquardtOptimizer;
  31. import org.hipparchus.optim.nonlinear.vector.leastsquares.MultivariateJacobianFunction;
  32. import org.hipparchus.util.FastMath;
  33. import org.orekit.errors.OrekitException;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.frames.Frame;
  36. import org.orekit.propagation.Propagator;
  37. import org.orekit.propagation.SpacecraftState;
  38. import org.orekit.propagation.integration.AbstractIntegratedPropagator;
  39. import org.orekit.time.AbsoluteDate;
  40. import org.orekit.utils.PVCoordinates;
  41. import org.orekit.utils.ParameterDriver;

  42. /** Common handling of {@link PropagatorConverter} methods for propagators conversions.
  43.  * <p>
  44.  * This abstract class factors the common code for propagators conversion.
  45.  * Only one method must be implemented by derived classes: {@link #getObjectiveFunction()}.
  46.  * </p>
  47.  * <p>
  48.  * The converter uses the LevenbergMarquardtOptimizer from the <a
  49.  * href="https://hipparchus.org/">Hipparchus</a> library.
  50.  * Different implementations correspond to different methods for computing the Jacobian.
  51.  * </p>
  52.  * @author Pascal Parraud
  53.  * @since 6.0
  54.  */
  55. public abstract class AbstractPropagatorConverter implements PropagatorConverter {

  56.     /** Spacecraft states sample. */
  57.     private List<SpacecraftState> sample;

  58.     /** Target position and velocities at sample points. */
  59.     private double[] target;

  60.     /** Weight for residuals. */
  61.     private double[] weight;

  62.     /** Auxiliary outputData: RMS of solution. */
  63.     private double rms;

  64.     /** Position use indicator. */
  65.     private boolean onlyPosition;

  66.     /** Adapted propagator. */
  67.     private Propagator adapted;

  68.     /** Propagator builder. */
  69.     private final PropagatorBuilder builder;

  70.     /** Frame. */
  71.     private final Frame frame;

  72.     /** Optimizer for fitting. */
  73.     private final LevenbergMarquardtOptimizer optimizer;

  74.     /** Optimum found. */
  75.     private LeastSquaresOptimizer.Optimum optimum;

  76.     /** Convergence checker for optimization algorithm. */
  77.     private final ConvergenceChecker<LeastSquaresProblem.Evaluation> checker;

  78.     /** Maximum number of iterations for optimization. */
  79.     private final int maxIterations;

  80.     /** Build a new instance.
  81.      * @param builder propagator builder
  82.      * @param threshold absolute convergence threshold for optimization algorithm
  83.      * @param maxIterations maximum number of iterations for fitting
  84.      */
  85.     protected AbstractPropagatorConverter(final PropagatorBuilder builder,
  86.                                           final double threshold,
  87.                                           final int maxIterations) {
  88.         this.builder       = builder;
  89.         this.frame         = builder.getFrame();
  90.         this.optimizer     = new LevenbergMarquardtOptimizer();
  91.         this.maxIterations = maxIterations;
  92.         this.sample        = new ArrayList<>();

  93.         final SimpleVectorValueChecker svvc = new SimpleVectorValueChecker(-1.0, threshold);
  94.         this.checker = LeastSquaresFactory.evaluationChecker(svvc);

  95.     }

  96.     /** Convert a propagator to another.
  97.      * @param source initial propagator (the propagator will be used for sample
  98.      * generation, if it is a numerical propagator, its initial state will
  99.      * be reset unless {@link AbstractIntegratedPropagator#setResetAtEnd(boolean)}
  100.      * has been called beforehand)
  101.      * @param timeSpan time span for fitting
  102.      * @param nbPoints number of fitting points over time span
  103.      * @param freeParameters names of the free parameters
  104.      * @return adapted propagator
  105.           * @exception IllegalArgumentException if one of the parameters cannot be free
  106.      */
  107.     public Propagator convert(final Propagator source,
  108.                               final double timeSpan,
  109.                               final int nbPoints,
  110.                               final List<String> freeParameters)
  111.         throws IllegalArgumentException {
  112.         setFreeParameters(freeParameters);
  113.         final List<SpacecraftState> states = createSample(source, timeSpan, nbPoints);
  114.         return convert(states, false, freeParameters);
  115.     }

  116.     /** Convert a propagator to another.
  117.      * @param source initial propagator (the propagator will be used for sample
  118.      * generation, if it is a numerical propagator, its initial state will
  119.      * be reset unless {@link AbstractIntegratedPropagator#setResetAtEnd(boolean)}
  120.      * has been called beforehand)
  121.      * @param timeSpan time span for fitting
  122.      * @param nbPoints number of fitting points over time span
  123.      * @param freeParameters names of the free parameters
  124.      * @return adapted propagator
  125.           * @exception IllegalArgumentException if one of the parameters cannot be free
  126.      */
  127.     public Propagator convert(final Propagator source,
  128.                               final double timeSpan,
  129.                               final int nbPoints,
  130.                               final String... freeParameters)
  131.         throws IllegalArgumentException {
  132.         setFreeParameters(Arrays.asList(freeParameters));
  133.         final List<SpacecraftState> states = createSample(source, timeSpan, nbPoints);
  134.         return convert(states, false, freeParameters);
  135.     }

  136.     /** Find the propagator that minimize the mean square error for a sample of {@link SpacecraftState states}.
  137.      * @param states spacecraft states sample to fit
  138.      * @param positionOnly if true, consider only position data otherwise both position and velocity are used
  139.      * @param freeParameters names of the free parameters
  140.      * @return adapted propagator
  141.           * @exception IllegalArgumentException if one of the parameters cannot be free
  142.      */
  143.     public Propagator convert(final List<SpacecraftState> states,
  144.                               final boolean positionOnly,
  145.                               final List<String> freeParameters)
  146.         throws IllegalArgumentException {
  147.         setFreeParameters(freeParameters);
  148.         return adapt(states, positionOnly);
  149.     }

  150.     /** Find the propagator that minimize the mean square error for a sample of {@link SpacecraftState states}.
  151.      * @param states spacecraft states sample to fit
  152.      * @param positionOnly if true, consider only position data otherwise both position and velocity are used
  153.      * @param freeParameters names of the free parameters
  154.      * @return adapted propagator
  155.           * @exception IllegalArgumentException if one of the parameters cannot be free
  156.      */
  157.     public Propagator convert(final List<SpacecraftState> states,
  158.                               final boolean positionOnly,
  159.                               final String... freeParameters)
  160.         throws IllegalArgumentException {
  161.         setFreeParameters(Arrays.asList(freeParameters));
  162.         return adapt(states, positionOnly);
  163.     }

  164.     /** Get the adapted propagator.
  165.      * @return adapted propagator
  166.      */
  167.     public Propagator getAdaptedPropagator() {
  168.         return adapted;
  169.     }

  170.     /** Get the Root Mean Square Deviation of the fitting.
  171.      * @return RMSD
  172.      */
  173.     public double getRMS() {
  174.         return rms;
  175.     }

  176.     /** Get the number of objective function evaluations.
  177.      *  @return the number of objective function evaluations.
  178.      */
  179.     public int getEvaluations() {
  180.         return optimum.getEvaluations();
  181.     }

  182.     /** Get the function computing position/velocity at sample points.
  183.      * @return function computing position/velocity at sample points
  184.      */
  185.     protected abstract MultivariateVectorFunction getObjectiveFunction();

  186.     /** Get the Jacobian of the function computing position/velocity at sample points.
  187.      * @return Jacobian of the function computing position/velocity at sample points
  188.      */
  189.     protected abstract MultivariateJacobianFunction getModel();

  190.     /** Check if fitting uses only sample positions.
  191.      * @return true if fitting uses only sample positions
  192.      */
  193.     protected boolean isOnlyPosition() {
  194.         return onlyPosition;
  195.     }

  196.     /** Get the size of the target.
  197.      * @return target size
  198.      */
  199.     protected int getTargetSize() {
  200.         return target.length;
  201.     }

  202.     /** Get the frame of the initial state.
  203.      * @return the orbit frame
  204.      */
  205.     protected Frame getFrame() {
  206.         return frame;
  207.     }

  208.     /** Get the states sample.
  209.      * @return the states sample
  210.      */
  211.     protected List<SpacecraftState> getSample() {
  212.         return sample;
  213.     }

  214.     /** Create a sample of {@link SpacecraftState}.
  215.      * @param source initial propagator
  216.      * @param timeSpan time span for the sample
  217.      * @param nbPoints number of points for the sample over the time span
  218.      * @return a sample of {@link SpacecraftState}
  219.      */
  220.     private List<SpacecraftState> createSample(final Propagator source,
  221.                                                final double timeSpan,
  222.                                                final int nbPoints) {

  223.         final List<SpacecraftState> states = new ArrayList<>();

  224.         final double stepSize = timeSpan / (nbPoints - 1);
  225.         final AbsoluteDate iniDate = source.getInitialState().getDate();
  226.         for (double dt = 0; dt < timeSpan; dt += stepSize) {
  227.             states.add(source.propagate(iniDate.shiftedBy(dt)));
  228.         }

  229.         return states;
  230.     }

  231.     /** Free some parameters.
  232.      * @param freeParameters names of the free parameters
  233.      */
  234.     private void setFreeParameters(final Iterable<String> freeParameters) {

  235.         // start by setting all parameters as not estimated
  236.         for (final ParameterDriver driver : builder.getPropagationParametersDrivers().getDrivers()) {
  237.             driver.setSelected(false);
  238.         }

  239.         // set only the selected parameters as estimated
  240.         for (final String parameter : freeParameters) {
  241.             boolean found = false;
  242.             for (final ParameterDriver driver : builder.getPropagationParametersDrivers().getDrivers()) {
  243.                 if (driver.getName().equals(parameter)) {
  244.                     found = true;
  245.                     driver.setSelected(true);
  246.                     break;
  247.                 }
  248.             }
  249.             if (!found) {
  250.                 // build the list of supported parameters
  251.                 final StringBuilder sBuilder = new StringBuilder();
  252.                 for (final ParameterDriver driver : builder.getPropagationParametersDrivers().getDrivers()) {
  253.                     if (sBuilder.length() > 0) {
  254.                         sBuilder.append(", ");
  255.                     }
  256.                     sBuilder.append(driver.getName());
  257.                 }
  258.                 throw new OrekitException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
  259.                                           parameter, sBuilder.toString());
  260.             }
  261.         }
  262.     }

  263.     /** Adapt a propagator to minimize the mean square error for a set of {@link SpacecraftState states}.
  264.      * @param states set of spacecraft states to fit
  265.      * @param positionOnly if true, consider only position data otherwise both position and velocity are used
  266.      * @return adapted propagator
  267.      */
  268.     private Propagator adapt(final List<SpacecraftState> states,
  269.                              final boolean positionOnly) {

  270.         this.onlyPosition = positionOnly;

  271.         // very rough first guess using osculating parameters of first sample point
  272.         final double[] initial = builder.getSelectedNormalizedParameters();

  273.         // warm-up iterations, using only a few points
  274.         setSample(states.subList(0, onlyPosition ? 2 : 1));
  275.         final double[] intermediate = fit(initial);

  276.         // final search using all points
  277.         setSample(states);
  278.         final double[] result = fit(intermediate);

  279.         rms = getRMS(result);
  280.         adapted = buildAdaptedPropagator(result);

  281.         return adapted;
  282.     }

  283.     /** Find the propagator that minimize the mean square error for a sample of {@link SpacecraftState states}.
  284.      * @param initial initial estimation parameters (position, velocity, free parameters)
  285.      * @return fitted parameters
  286.           * @exception MathRuntimeException if maximal number of iterations is exceeded
  287.      */
  288.     private double[] fit(final double[] initial)
  289.         throws MathRuntimeException {

  290.         final LeastSquaresProblem problem = new LeastSquaresBuilder().
  291.                                             maxIterations(maxIterations).
  292.                                             maxEvaluations(Integer.MAX_VALUE).
  293.                                             model(getModel()).
  294.                                             target(target).
  295.                                             weight(new DiagonalMatrix(weight)).
  296.                                             start(initial).
  297.                                             checker(checker).
  298.                                             build();

  299.         optimum = optimizer.optimize(problem);
  300.         return optimum.getPoint().toArray();

  301.     }

  302.     /** Get the Root Mean Square Deviation for a given parameters set.
  303.      * @param parameterSet position/velocity parameters set
  304.      * @return RMSD
  305.      */
  306.     private double getRMS(final double[] parameterSet) {
  307.         final double[] residuals = getObjectiveFunction().value(parameterSet);
  308.         for (int i = 0; i < residuals.length; ++i) {
  309.             residuals[i] = target[i] - residuals[i];
  310.         }
  311.         double sum2 = 0;
  312.         for (final double residual : residuals) {
  313.             sum2 += residual * residual;
  314.         }
  315.         return FastMath.sqrt(sum2 / residuals.length);
  316.     }

  317.     /** Build the adpated propagator for a given position/velocity(/free) parameters set.
  318.      * @param parameterSet position/velocity(/free) parameters set
  319.      * @return adapted propagator
  320.      */
  321.     private Propagator buildAdaptedPropagator(final double[] parameterSet) {
  322.         return builder.buildPropagator(parameterSet);
  323.     }

  324.     /** Set the states sample.
  325.      * @param states spacecraft states sample
  326.      */
  327.     private void setSample(final List<SpacecraftState> states) {

  328.         this.sample = states;

  329.         if (onlyPosition) {
  330.             target = new double[states.size() * 3];
  331.             weight = new double[states.size() * 3];
  332.         } else {
  333.             target = new double[states.size() * 6];
  334.             weight = new double[states.size() * 6];
  335.         }

  336.         int k = 0;
  337.         for (SpacecraftState state : states) {

  338.             final PVCoordinates pv = state.getPVCoordinates(frame);

  339.             // position
  340.             target[k] = pv.getPosition().getX();
  341.             weight[k++] = 1;
  342.             target[k] = pv.getPosition().getY();
  343.             weight[k++] = 1;
  344.             target[k] = pv.getPosition().getZ();
  345.             weight[k++] = 1;

  346.             // velocity
  347.             if (!onlyPosition) {
  348.                 // velocity weight relative to position
  349.                 final double r2 = pv.getPosition().getNormSq();
  350.                 final double v = pv.getVelocity().getNorm();
  351.                 final double vWeight = v * r2 / state.getOrbit().getMu();

  352.                 target[k] = pv.getVelocity().getX();
  353.                 weight[k++] = vWeight;
  354.                 target[k] = pv.getVelocity().getY();
  355.                 weight[k++] = vWeight;
  356.                 target[k] = pv.getVelocity().getZ();
  357.                 weight[k++] = vWeight;
  358.             }

  359.         }

  360.     }

  361. }