AbstractPropagatorConverter.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.OrekitExceptionWrapper;
  35. import org.orekit.errors.OrekitMessages;
  36. import org.orekit.frames.Frame;
  37. import org.orekit.propagation.Propagator;
  38. import org.orekit.propagation.SpacecraftState;
  39. import org.orekit.propagation.integration.AbstractIntegratedPropagator;
  40. import org.orekit.time.AbsoluteDate;
  41. import org.orekit.utils.PVCoordinates;
  42. import org.orekit.utils.ParameterDriver;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  96.     }

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

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

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

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

  169.     /** Get the adapted propagator.
  170.      * @return adapted propagator
  171.      */
  172.     public Propagator getAdaptedPropagator() {
  173.         return adapted;
  174.     }

  175.     /** Get the Root Mean Square Deviation of the fitting.
  176.      * @return RMSD
  177.      */
  178.     public double getRMS() {
  179.         return rms;
  180.     }

  181.     /** Get the number of objective function evaluations.
  182.      *  @return the number of objective function evaluations.
  183.      */
  184.     public int getEvaluations() {
  185.         return optimum.getEvaluations();
  186.     }

  187.     /** Get the function computing position/velocity at sample points.
  188.      * @return function computing position/velocity at sample points
  189.      */
  190.     protected abstract MultivariateVectorFunction getObjectiveFunction();

  191.     /** Get the Jacobian of the function computing position/velocity at sample points.
  192.      * @return Jacobian of the function computing position/velocity at sample points
  193.      */
  194.     protected abstract MultivariateJacobianFunction getModel();

  195.     /** Check if fitting uses only sample positions.
  196.      * @return true if fitting uses only sample positions
  197.      */
  198.     protected boolean isOnlyPosition() {
  199.         return onlyPosition;
  200.     }

  201.     /** Get the size of the target.
  202.      * @return target size
  203.      */
  204.     protected int getTargetSize() {
  205.         return target.length;
  206.     }

  207.     /** Get the frame of the initial state.
  208.      * @return the orbit frame
  209.      */
  210.     protected Frame getFrame() {
  211.         return frame;
  212.     }

  213.     /** Get the states sample.
  214.      * @return the states sample
  215.      */
  216.     protected List<SpacecraftState> getSample() {
  217.         return sample;
  218.     }

  219.     /** Create a sample of {@link SpacecraftState}.
  220.      * @param source initial propagator
  221.      * @param timeSpan time span for the sample
  222.      * @param nbPoints number of points for the sample over the time span
  223.      * @return a sample of {@link SpacecraftState}
  224.      * @exception OrekitException if one of the sample point cannot be computed
  225.      */
  226.     private List<SpacecraftState> createSample(final Propagator source,
  227.                                                final double timeSpan,
  228.                                                final int nbPoints) throws OrekitException {

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

  230.         final double stepSize = timeSpan / (nbPoints - 1);
  231.         final AbsoluteDate iniDate = source.getInitialState().getDate();
  232.         for (double dt = 0; dt < timeSpan; dt += stepSize) {
  233.             states.add(source.propagate(iniDate.shiftedBy(dt)));
  234.         }

  235.         return states;
  236.     }

  237.     /** Free some parameters.
  238.      * @param freeParameters names of the free parameters
  239.      * @exception OrekitException if one of the parameters cannot be free
  240.      */
  241.     private void setFreeParameters(final Iterable<String> freeParameters) throws OrekitException {

  242.         // start by setting all parameters as not estimated
  243.         for (final ParameterDriver driver : builder.getPropagationParametersDrivers().getDrivers()) {
  244.             driver.setSelected(false);
  245.         }

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

  270.     /** Adapt a propagator to minimize the mean square error for a set of {@link SpacecraftState states}.
  271.      * @param states set of spacecraft states to fit
  272.      * @param positionOnly if true, consider only position data otherwise both position and velocity are used
  273.      * @return adapted propagator
  274.      * @exception OrekitException if propagator cannot be adapted
  275.      */
  276.     private Propagator adapt(final List<SpacecraftState> states,
  277.                              final boolean positionOnly) throws OrekitException {

  278.         this.onlyPosition = positionOnly;

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

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

  284.         // final search using all points
  285.         setSample(states);
  286.         final double[] result = fit(intermediate);

  287.         rms = getRMS(result);
  288.         adapted = buildAdaptedPropagator(result);

  289.         return adapted;
  290.     }

  291.     /** Find the propagator that minimize the mean square error for a sample of {@link SpacecraftState states}.
  292.      * @param initial initial estimation parameters (position, velocity, free parameters)
  293.      * @return fitted parameters
  294.      * @exception OrekitException if propagator cannot be adapted
  295.      * @exception MathRuntimeException if maximal number of iterations is exceeded
  296.      */
  297.     private double[] fit(final double[] initial)
  298.         throws OrekitException, MathRuntimeException {

  299.         final LeastSquaresProblem problem = new LeastSquaresBuilder().
  300.                                             maxIterations(maxIterations).
  301.                                             maxEvaluations(Integer.MAX_VALUE).
  302.                                             model(getModel()).
  303.                                             target(target).
  304.                                             weight(new DiagonalMatrix(weight)).
  305.                                             start(initial).
  306.                                             checker(checker).
  307.                                             build();

  308.         optimum = optimizer.optimize(problem);
  309.         return optimum.getPoint().toArray();

  310.     }

  311.     /** Get the Root Mean Square Deviation for a given parameters set.
  312.      * @param parameterSet position/velocity parameters set
  313.      * @return RMSD
  314.      * @exception OrekitException if position/velocity cannot be computed at some date
  315.      */
  316.     private double getRMS(final double[] parameterSet) throws OrekitException {
  317.         try {
  318.             final double[] residuals = getObjectiveFunction().value(parameterSet);
  319.             for (int i = 0; i < residuals.length; ++i) {
  320.                 residuals[i] = target[i] - residuals[i];
  321.             }
  322.             double sum2 = 0;
  323.             for (final double residual : residuals) {
  324.                 sum2 += residual * residual;
  325.             }
  326.             return FastMath.sqrt(sum2 / residuals.length);

  327.         } catch (OrekitExceptionWrapper oew) {
  328.             throw oew.getException();
  329.         }
  330.     }

  331.     /** Build the adpated propagator for a given position/velocity(/free) parameters set.
  332.      * @param parameterSet position/velocity(/free) parameters set
  333.      * @return adapted propagator
  334.      * @exception OrekitException if propagator cannot be build
  335.      */
  336.     private Propagator buildAdaptedPropagator(final double[] parameterSet)
  337.         throws OrekitException {
  338.         return builder.buildPropagator(parameterSet);
  339.     }

  340.     /** Set the states sample.
  341.      * @param states spacecraft states sample
  342.      * @exception OrekitException if position/velocity cannot be extracted from sample
  343.      */
  344.     private void setSample(final List<SpacecraftState> states) throws OrekitException {

  345.         this.sample = states;

  346.         if (onlyPosition) {
  347.             target = new double[states.size() * 3];
  348.             weight = new double[states.size() * 3];
  349.         } else {
  350.             target = new double[states.size() * 6];
  351.             weight = new double[states.size() * 6];
  352.         }

  353.         int k = 0;
  354.         for (int i = 0; i < states.size(); i++) {

  355.             final PVCoordinates pv = states.get(i).getPVCoordinates(frame);

  356.             // position
  357.             target[k]   = pv.getPosition().getX();
  358.             weight[k++] = 1;
  359.             target[k]   = pv.getPosition().getY();
  360.             weight[k++] = 1;
  361.             target[k]   = pv.getPosition().getZ();
  362.             weight[k++] = 1;

  363.             // velocity
  364.             if (!onlyPosition) {
  365.                 // velocity weight relative to position
  366.                 final double r2 = pv.getPosition().getNormSq();
  367.                 final double v  = pv.getVelocity().getNorm();
  368.                 final double vWeight = v * r2 / states.get(i).getMu();

  369.                 target[k]   = pv.getVelocity().getX();
  370.                 weight[k++] = vWeight;
  371.                 target[k]   = pv.getVelocity().getY();
  372.                 weight[k++] = vWeight;
  373.                 target[k]   = pv.getVelocity().getZ();
  374.                 weight[k++] = vWeight;
  375.             }

  376.         }

  377.     }

  378. }