AbstractPropagatorConverter.java

  1. /* Copyright 2002-2016 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.Collection;
  20. import java.util.List;

  21. import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
  22. import org.apache.commons.math3.analysis.MultivariateVectorFunction;
  23. import org.apache.commons.math3.exception.MaxCountExceededException;
  24. import org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder;
  25. import org.apache.commons.math3.fitting.leastsquares.LeastSquaresFactory;
  26. import org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer;
  27. import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
  28. import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
  29. import org.apache.commons.math3.fitting.leastsquares.LevenbergMarquardtOptimizer;
  30. import org.apache.commons.math3.fitting.leastsquares.MultivariateJacobianFunction;
  31. import org.apache.commons.math3.linear.DiagonalMatrix;
  32. import org.apache.commons.math3.linear.MatrixUtils;
  33. import org.apache.commons.math3.linear.RealMatrix;
  34. import org.apache.commons.math3.linear.RealVector;
  35. import org.apache.commons.math3.optim.ConvergenceChecker;
  36. import org.apache.commons.math3.optim.SimpleVectorValueChecker;
  37. import org.apache.commons.math3.util.FastMath;
  38. import org.apache.commons.math3.util.Pair;
  39. import org.orekit.errors.OrekitException;
  40. import org.orekit.errors.OrekitExceptionWrapper;
  41. import org.orekit.errors.OrekitMessages;
  42. import org.orekit.frames.Frame;
  43. import org.orekit.propagation.Propagator;
  44. import org.orekit.propagation.SpacecraftState;
  45. import org.orekit.time.AbsoluteDate;
  46. import org.orekit.utils.PVCoordinates;

  47. /** Common handling of {@link PropagatorConverter} methods for propagators conversions.
  48.  * <p>
  49.  * This abstract class factorizes the common code for propagators conversion.
  50.  * Only one method must be implemented by derived classes: {@link #getObjectiveFunction()}.
  51.  * </p>
  52.  * <p>
  53.  * The converter uses the LevenbergMarquardtOptimizer from the <a
  54.  * href="http://commons.apache.org/math/">commons math</a> library.
  55.  * Different implementations correspond to different methods for computing the jacobian.
  56.  * </p>
  57.  * @author Pascal Parraud
  58.  * @since 6.0
  59.  */
  60. public abstract class AbstractPropagatorConverter implements PropagatorConverter {

  61.     /** Spacecraft states sample. */
  62.     private List<SpacecraftState> sample;

  63.     /** Reference date for conversion (1st date of the states sample). */
  64.     private AbsoluteDate date;

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

  67.     /** Weight for residuals. */
  68.     private double[] weight;

  69.     /** Auxiliary outputData: RMS of solution. */
  70.     private double rms;

  71.     /** Position use indicator. */
  72.     private boolean onlyPosition;

  73.     /** Adapted propagator. */
  74.     private Propagator adapted;

  75.     /** List of the desired free parameters names. */
  76.     private List<String> parameters;

  77.     /** List of the available free parameters names. */
  78.     private final Collection<String> availableParameters;

  79.     /** Propagator builder. */
  80.     private final PropagatorBuilder builder;

  81.     /** Frame. */
  82.     private final Frame frame;

  83.     /** Optimizer for fitting. */
  84.     private final LevenbergMarquardtOptimizer optimizer;

  85.     /** Optimum found. */
  86.     private LeastSquaresOptimizer.Optimum optimum;

  87.     /** Convergence checker for optimization algorithm. */
  88.     private final ConvergenceChecker<Evaluation> checker;

  89.     /** Maximum number of iterations for optimization. */
  90.     private final int maxIterations;

  91.     /** Build a new instance.
  92.      * @param builder propagator builder
  93.      * @param threshold absolute convergence threshold for optimization algorithm
  94.      * @param maxIterations maximum number of iterations for fitting
  95.      */
  96.     protected AbstractPropagatorConverter(final PropagatorBuilder builder,
  97.                                           final double threshold,
  98.                                           final int maxIterations) {
  99.         this.builder             = builder;
  100.         this.frame               = builder.getFrame();
  101.         this.availableParameters = builder.getSupportedParameters();
  102.         this.optimizer           = new LevenbergMarquardtOptimizer();
  103.         this.maxIterations       = maxIterations;
  104.         this.sample              = new ArrayList<SpacecraftState>();

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

  107.     }

  108.     /** Convert a propagator to another.
  109.      * @param source initial propagator
  110.      * @param timeSpan time span for fitting
  111.      * @param nbPoints number of fitting points over time span
  112.      * @param freeParameters names of the free parameters
  113.      * @return adapted propagator
  114.      * @exception OrekitException if propagator cannot be adapted
  115.      * @exception IllegalArgumentException if one of the parameters cannot be free
  116.      */
  117.     public Propagator convert(final Propagator source,
  118.                               final double timeSpan,
  119.                               final int nbPoints,
  120.                               final List<String> freeParameters)
  121.         throws OrekitException, IllegalArgumentException {

  122.         checkParameters(freeParameters);
  123.         final List<SpacecraftState> states = createSample(source, timeSpan, nbPoints);
  124.         return convert(states, false, freeParameters);
  125.     }

  126.     /** Convert a propagator to another.
  127.      * @param source initial propagator
  128.      * @param timeSpan time span for fitting
  129.      * @param nbPoints number of fitting points over time span
  130.      * @param freeParameters names of the free parameters
  131.      * @return adapted propagator
  132.      * @exception OrekitException if propagator cannot be adapted
  133.      * @exception IllegalArgumentException if one of the parameters cannot be free
  134.      */
  135.     public Propagator convert(final Propagator source,
  136.                               final double timeSpan,
  137.                               final int nbPoints,
  138.                               final String ... freeParameters)
  139.         throws OrekitException, IllegalArgumentException {

  140.         checkParameters(freeParameters);
  141.         final List<SpacecraftState> states = createSample(source, timeSpan, nbPoints);
  142.         return convert(states, false, freeParameters);
  143.     }

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

  156.         checkParameters(freeParameters);

  157.         parameters = new ArrayList<String>();
  158.         parameters.addAll(freeParameters);

  159.         builder.setFreeParameters(parameters);

  160.         return adapt(states, positionOnly);
  161.     }

  162.     /** Find the propagator that minimize the mean square error for a sample of {@link SpacecraftState states}.
  163.      * @param states spacecraft states sample to fit
  164.      * @param positionOnly if true, consider only position data otherwise both position and velocity are used
  165.      * @param freeParameters names of the free parameters
  166.      * @return adapted propagator
  167.      * @exception OrekitException if propagator cannot be adapted
  168.      * @exception IllegalArgumentException if one of the parameters cannot be free
  169.      */
  170.     public Propagator convert(final List<SpacecraftState> states,
  171.                               final boolean positionOnly,
  172.                               final String ... freeParameters)
  173.         throws OrekitException, IllegalArgumentException {

  174.         checkParameters(freeParameters);

  175.         parameters = new ArrayList<String>();
  176.         for (final String name : freeParameters) {
  177.             parameters.add(name);
  178.         }

  179.         builder.setFreeParameters(parameters);

  180.         return adapt(states, positionOnly);
  181.     }

  182.     /** Get the available free parameters.
  183.      * @return available free parameters
  184.      */
  185.     public Collection<String> getAvailableParameters() {
  186.         return availableParameters;
  187.     }

  188.     /** Check if a parameter can be free.
  189.      * @param name parameter name to check
  190.      * @return true if the parameter can be free
  191.      */
  192.     public boolean isAvailable(final String name) {
  193.         for (final String supportedName : availableParameters) {
  194.             if (supportedName.equals(name)) {
  195.                 return true;
  196.             }
  197.         }
  198.         return false;
  199.     }

  200.     /** Get the adapted propagator.
  201.      * @return adapted propagator
  202.      */
  203.     public Propagator getAdaptedPropagator() {
  204.         return adapted;
  205.     }

  206.     /** Get the Root Mean Square Deviation of the fitting.
  207.      * @return RMSD
  208.      */
  209.     public double getRMS() {
  210.         return rms;
  211.     }

  212.     /** Get the number of objective function evaluations.
  213.      *  @return the number of objective function evaluations.
  214.      */
  215.     public int getEvaluations() {
  216.         return optimum.getEvaluations();
  217.     }

  218.     /** Get the function computing position/velocity at sample points.
  219.      * @return function computing position/velocity at sample points
  220.      */
  221.     protected abstract MultivariateVectorFunction getObjectiveFunction();

  222.     /** Get the Jacobian of the function computing position/velocity at sample points.
  223.      * @return Jacobian of the function computing position/velocity at sample points
  224.      */
  225.     protected abstract MultivariateMatrixFunction getObjectiveFunctionJacobian();

  226.     /** Check if fitting uses only sample positions.
  227.      * @return true if fitting uses only sample positions
  228.      */
  229.     protected boolean isOnlyPosition() {
  230.         return onlyPosition;
  231.     }

  232.     /** Get the size of the target.
  233.      * @return target size
  234.      */
  235.     protected int getTargetSize() {
  236.         return target.length;
  237.     }

  238.     /** Get the date of the initial state.
  239.      * @return the date
  240.      */
  241.     protected AbsoluteDate getDate() {
  242.         return date;
  243.     }

  244.     /** Get the frame of the initial state.
  245.      * @return the orbit frame
  246.      */
  247.     protected Frame getFrame() {
  248.         return frame;
  249.     }

  250.     /** Get the states sample.
  251.      * @return the states sample
  252.      */
  253.     protected List<SpacecraftState> getSample() {
  254.         return sample;
  255.     }

  256.     /** Get the free parameters.
  257.      * @return the free parameters
  258.      */
  259.     protected Collection<String>  getFreeParameters() {
  260.         return parameters;
  261.     }

  262.     /** Create a sample of {@link SpacecraftState}.
  263.      * @param source initial propagator
  264.      * @param timeSpan time span for the sample
  265.      * @param nbPoints number of points for the sample over the time span
  266.      * @return a sample of {@link SpacecraftState}
  267.      * @exception OrekitException if one of the sample point cannot be computed
  268.      */
  269.     private List<SpacecraftState> createSample(final Propagator source,
  270.                                                final double timeSpan,
  271.                                                final int nbPoints) throws OrekitException {

  272.         final SpacecraftState initialState = source.getInitialState();

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

  274.         final double stepSize = timeSpan / (nbPoints - 1);
  275.         final AbsoluteDate iniDate = source.getInitialState().getDate();
  276.         for (double dt = 0; dt < timeSpan; dt += stepSize) {
  277.             states.add(source.propagate(iniDate.shiftedBy(dt)));
  278.         }

  279.         source.resetInitialState(initialState);

  280.         return states;
  281.     }

  282.     /** Check if parameters can be free.
  283.      * @param freeParameters names of the free parameters
  284.      * @exception OrekitException if one of the parameters cannot be free
  285.      */
  286.     private void checkParameters(final Collection<String> freeParameters) throws OrekitException {
  287.         for (String parameter : freeParameters) {
  288.             checkParameter(parameter);
  289.         }
  290.     }

  291.     /** Check if parameters can be free.
  292.      * @param freeParameters names of the free parameters
  293.      * @exception OrekitException if one of the parameters cannot be free
  294.      */
  295.     private void checkParameters(final String ... freeParameters) throws OrekitException {
  296.         for (String parameter : freeParameters) {
  297.             checkParameter(parameter);
  298.         }
  299.     }

  300.     /** Check if parameter can be free.
  301.      * @param parameter name of the free parameter
  302.      * @exception OrekitException if the parameter cannot be free
  303.      */
  304.     private void checkParameter(final String parameter) throws OrekitException {

  305.         if ( !availableParameters.contains(parameter) ) {

  306.             // build the list of supported parameters
  307.             final StringBuilder sBuilder = new StringBuilder();
  308.             for (final String available : availableParameters) {
  309.                 if (sBuilder.length() > 0) {
  310.                     sBuilder.append(", ");
  311.                 }
  312.                 sBuilder.append(available);
  313.             }

  314.             throw new OrekitException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
  315.                                       parameter, sBuilder.toString());

  316.         }

  317.     }

  318.     /** Adapt a propagator to minimize the mean square error for a set of {@link SpacecraftState states}.
  319.      * @param states set of spacecraft states to fit
  320.      * @param positionOnly if true, consider only position data otherwise both position and velocity are used
  321.      * @return adapted propagator
  322.      * @exception OrekitException if propagator cannot be adapted
  323.      */
  324.     private Propagator adapt(final List<SpacecraftState> states,
  325.                              final boolean positionOnly) throws OrekitException {

  326.         this.date = states.get(0).getDate();
  327.         this.onlyPosition = positionOnly;

  328.         // very rough first guess using osculating parameters of first sample point
  329.         final double[] initial = new double[6 + parameters.size()];
  330.         builder.getOrbitType().mapOrbitToArray(states.get(0).getOrbit(),
  331.                                                builder.getPositionAngle(),
  332.                                                initial);
  333.         int i = 6;
  334.         for (String name : parameters) {
  335.             initial[i++] = builder.getParameter(name);
  336.         }

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

  340.         // final search using all points
  341.         setSample(states);
  342.         final double[] result = fit(intermediate);

  343.         rms = getRMS(result);
  344.         adapted = buildAdaptedPropagator(result);

  345.         return adapted;
  346.     }

  347.     /** Find the propagator that minimize the mean square error for a sample of {@link SpacecraftState states}.
  348.      * @param initial initial estimation parameters (position, velocity, free parameters)
  349.      * @return fitted parameters
  350.      * @exception OrekitException if propagator cannot be adapted
  351.      * @exception MaxCountExceededException if maximal number of iterations is exceeded
  352.      */
  353.     private double[] fit(final double[] initial)
  354.         throws OrekitException, MaxCountExceededException {

  355.         final MultivariateVectorFunction f = getObjectiveFunction();
  356.         final MultivariateMatrixFunction jac = getObjectiveFunctionJacobian();
  357.         final MultivariateJacobianFunction fJac = new MultivariateJacobianFunction() {
  358.             /** {@inheritDoc} */
  359.             @Override
  360.             public Pair<RealVector, RealMatrix> value(final RealVector point) {
  361.                 final double[] p = point.toArray();
  362.                 return new Pair<RealVector, RealMatrix>(MatrixUtils.createRealVector(f.value(p)),
  363.                         MatrixUtils.createRealMatrix(jac.value(p)));
  364.             }
  365.         };
  366.         final LeastSquaresProblem problem = new LeastSquaresBuilder().
  367.                                             maxIterations(maxIterations).
  368.                                             maxEvaluations(Integer.MAX_VALUE).
  369.                                             model(fJac).
  370.                                             target(target).
  371.                                             weight(new DiagonalMatrix(weight)).
  372.                                             start(initial).
  373.                                             checker(checker).
  374.                                             build();
  375.         optimum = optimizer.optimize(problem);
  376.         return optimum.getPoint().toArray();
  377.     }

  378.     /** Get the Root Mean Square Deviation for a given parameters set.
  379.      * @param parameterSet position/velocity parameters set
  380.      * @return RMSD
  381.      * @exception OrekitException if position/velocity cannot be computed at some date
  382.      */
  383.     private double getRMS(final double[] parameterSet) throws OrekitException {

  384.         final double[] residuals = getResiduals(parameterSet);
  385.         double sum2 = 0;
  386.         for (final double residual : residuals) {
  387.             sum2 += residual * residual;
  388.         }
  389.         return FastMath.sqrt(sum2 / residuals.length);

  390.     }

  391.     /** Get the residuals for a given position/velocity parameters set.
  392.      * @param parameterSet position/velocity parameters set
  393.      * @return residuals
  394.      * @exception OrekitException if position/velocity cannot be computed at some date
  395.      */
  396.     private double[] getResiduals(final double[] parameterSet) throws OrekitException {
  397.         try {
  398.             final double[] residuals = getObjectiveFunction().value(parameterSet);
  399.             for (int i = 0; i < residuals.length; ++i) {
  400.                 residuals[i] = target[i] - residuals[i];
  401.             }
  402.             return residuals;
  403.         } catch (OrekitExceptionWrapper oew) {
  404.             throw oew.getException();
  405.         }
  406.     }

  407.     /** Build the adpated propagator for a given position/velocity(/free) parameters set.
  408.      * @param parameterSet position/velocity(/free) parameters set
  409.      * @return adapted propagator
  410.      * @exception OrekitException if propagator cannot be build
  411.      */
  412.     private Propagator buildAdaptedPropagator(final double[] parameterSet)
  413.         throws OrekitException {
  414.         return builder.buildPropagator(date, parameterSet);
  415.     }

  416.     /** Set the states sample.
  417.      * @param states spacecraft states sample
  418.      * @exception OrekitException if position/velocity cannot be extracted from sample
  419.      */
  420.     private void setSample(final List<SpacecraftState> states) throws OrekitException {

  421.         this.sample = states;

  422.         if (onlyPosition) {
  423.             target = new double[states.size() * 3];
  424.             weight = new double[states.size() * 3];
  425.         } else {
  426.             target = new double[states.size() * 6];
  427.             weight = new double[states.size() * 6];
  428.         }

  429.         int k = 0;
  430.         for (int i = 0; i < states.size(); i++) {

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

  432.             // position
  433.             target[k]   = pv.getPosition().getX();
  434.             weight[k++] = 1;
  435.             target[k]   = pv.getPosition().getY();
  436.             weight[k++] = 1;
  437.             target[k]   = pv.getPosition().getZ();
  438.             weight[k++] = 1;

  439.             // velocity
  440.             if (!onlyPosition) {
  441.                 // velocity weight relative to position
  442.                 final double r2 = pv.getPosition().getNormSq();
  443.                 final double v  = pv.getVelocity().getNorm();
  444.                 final double vWeight = v * r2 / states.get(i).getMu();

  445.                 target[k]   = pv.getVelocity().getX();
  446.                 weight[k++] = vWeight;
  447.                 target[k]   = pv.getVelocity().getY();
  448.                 weight[k++] = vWeight;
  449.                 target[k]   = pv.getVelocity().getZ();
  450.                 weight[k++] = vWeight;
  451.             }

  452.         }

  453.     }

  454. }