Model.java

  1. /* Copyright 2002-2017 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.estimation.leastsquares;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.HashMap;
  21. import java.util.IdentityHashMap;
  22. import java.util.List;
  23. import java.util.Map;

  24. import org.hipparchus.linear.Array2DRowRealMatrix;
  25. import org.hipparchus.linear.ArrayRealVector;
  26. import org.hipparchus.linear.MatrixUtils;
  27. import org.hipparchus.linear.RealMatrix;
  28. import org.hipparchus.linear.RealVector;
  29. import org.hipparchus.optim.nonlinear.vector.leastsquares.MultivariateJacobianFunction;
  30. import org.hipparchus.util.Incrementor;
  31. import org.hipparchus.util.Pair;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitExceptionWrapper;
  34. import org.orekit.estimation.measurements.EstimatedMeasurement;
  35. import org.orekit.estimation.measurements.ObservedMeasurement;
  36. import org.orekit.orbits.Orbit;
  37. import org.orekit.propagation.Propagator;
  38. import org.orekit.propagation.PropagatorsParallelizer;
  39. import org.orekit.propagation.SpacecraftState;
  40. import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
  41. import org.orekit.propagation.numerical.JacobiansMapper;
  42. import org.orekit.propagation.numerical.NumericalPropagator;
  43. import org.orekit.propagation.numerical.PartialDerivativesEquations;
  44. import org.orekit.propagation.sampling.MultiSatStepHandler;
  45. import org.orekit.time.AbsoluteDate;
  46. import org.orekit.time.ChronologicalComparator;
  47. import org.orekit.utils.ParameterDriver;
  48. import org.orekit.utils.ParameterDriversList;
  49. import org.orekit.utils.ParameterDriversList.DelegatingDriver;

  50. /** Bridge between {@link ObservedMeasurement measurements} and {@link
  51.  * org.hipparchus.fitting.leastsquares.LeastSquaresProblem
  52.  * least squares problems}.
  53.  * @author Luc Maisonobe
  54.  * @since 8.0
  55.  */
  56. class Model implements MultivariateJacobianFunction {

  57.     /** Builders for propagators. */
  58.     private final NumericalPropagatorBuilder[] builders;

  59.     /** Array of each builder's selected propagation drivers. */
  60.     private final ParameterDriversList[] estimatedPropagationParameters;

  61.     /** Estimated measurements parameters. */
  62.     private final ParameterDriversList estimatedMeasurementsParameters;

  63.     /** Measurements. */
  64.     private final List<ObservedMeasurement<?>> measurements;

  65.     /** Start columns for each estimated orbit. */
  66.     private final int[] orbitsStartColumns;

  67.     /** End columns for each estimated orbit. */
  68.     private final int[] orbitsEndColumns;

  69.     /** Map for propagation parameters columns. */
  70.     private final Map<String, Integer> propagationParameterColumns;

  71.     /** Map for measurements parameters columns. */
  72.     private final Map<String, Integer> measurementParameterColumns;

  73.     /** Last evaluations. */
  74.     private final Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> evaluations;

  75.     /** Observer to be notified at orbit changes. */
  76.     private final ModelObserver observer;

  77.     /** Counter for the evaluations. */
  78.     private Incrementor evaluationsCounter;

  79.     /** Counter for the iterations. */
  80.     private Incrementor iterationsCounter;

  81.     /** Date of the first enabled measurement. */
  82.     private AbsoluteDate firstDate;

  83.     /** Date of the last enabled measurement. */
  84.     private AbsoluteDate lastDate;

  85.     /** Mappers for Jacobians. */
  86.     private JacobiansMapper[] mappers;

  87.     /** Model function value. */
  88.     private RealVector value;

  89.     /** Model function Jacobian. */
  90.     private RealMatrix jacobian;

  91.     /** Simple constructor.
  92.      * @param builders builders to use for propagation
  93.      * @param measurements measurements
  94.      * @param estimatedMeasurementsParameters estimated measurements parameters
  95.      * @param observer observer to be notified at model calls
  96.      * @exception OrekitException if some propagator parameter cannot be set properly
  97.      */
  98.     Model(final NumericalPropagatorBuilder[] builders,
  99.           final List<ObservedMeasurement<?>> measurements, final ParameterDriversList estimatedMeasurementsParameters,
  100.           final ModelObserver observer)
  101.         throws OrekitException {

  102.         this.builders                        = builders;
  103.         this.measurements                    = measurements;
  104.         this.estimatedMeasurementsParameters = estimatedMeasurementsParameters;
  105.         this.measurementParameterColumns     = new HashMap<>(estimatedMeasurementsParameters.getDrivers().size());
  106.         this.estimatedPropagationParameters  = new ParameterDriversList[builders.length];
  107.         this.evaluations                     = new IdentityHashMap<>(measurements.size());
  108.         this.observer                        = observer;
  109.         this.mappers                         = new JacobiansMapper[builders.length];


  110.         // allocate vector and matrix
  111.         int rows = 0;
  112.         for (final ObservedMeasurement<?> measurement : measurements) {
  113.             rows += measurement.getDimension();
  114.         }

  115.         this.orbitsStartColumns = new int[builders.length];
  116.         this.orbitsEndColumns   = new int[builders.length];
  117.         int columns = 0;
  118.         for (int i = 0; i < builders.length; ++i) {
  119.             this.orbitsStartColumns[i] = columns;
  120.             for (final ParameterDriver driver : builders[i].getOrbitalParametersDrivers().getDrivers()) {
  121.                 if (driver.isSelected()) {
  122.                     ++columns;
  123.                 }
  124.             }
  125.             this.orbitsEndColumns[i] = columns;
  126.         }

  127.         // Gather all the propagation drivers names in a list
  128.         final List<String> estimatedPropagationParametersNames = new ArrayList<>();
  129.         for (int i = 0; i < builders.length; ++i) {
  130.             // The index i in array estimatedPropagationParameters (attribute of the class) is populated
  131.             // when the first call to getSelectedPropagationDriversForBuilder(i) is made
  132.             for (final DelegatingDriver delegating : getSelectedPropagationDriversForBuilder(i).getDrivers()) {
  133.                 final String driverName = delegating.getName();
  134.                 // Add the driver name if it has not been added yet
  135.                 if (!estimatedPropagationParametersNames.contains(driverName)) {
  136.                     estimatedPropagationParametersNames.add(driverName);
  137.                 }
  138.             }
  139.         }
  140.         // Populate the map of propagation drivers' columns and update the total number of columns
  141.         propagationParameterColumns = new HashMap<>(estimatedPropagationParametersNames.size());
  142.         for (final String driverName : estimatedPropagationParametersNames) {
  143.             propagationParameterColumns.put(driverName, columns);
  144.             ++columns;
  145.         }


  146.         // Populate the map of measurement drivers' columns and update the total number of columns
  147.         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
  148.             measurementParameterColumns.put(parameter.getName(), columns);
  149.             ++columns;
  150.         }

  151.         value    = new ArrayRealVector(rows);
  152.         jacobian = MatrixUtils.createRealMatrix(rows, columns);

  153.     }

  154.     /** Set the counter for evaluations.
  155.      * @param evaluationsCounter counter for evaluations
  156.      */
  157.     void setEvaluationsCounter(final Incrementor evaluationsCounter) {
  158.         this.evaluationsCounter = evaluationsCounter;
  159.     }

  160.     /** Set the counter for iterations.
  161.      * @param iterationsCounter counter for iterations
  162.      */
  163.     void setIterationsCounter(final Incrementor iterationsCounter) {
  164.         this.iterationsCounter = iterationsCounter;
  165.     }

  166.     /** {@inheritDoc} */
  167.     @Override
  168.     public Pair<RealVector, RealMatrix> value(final RealVector point)
  169.         throws OrekitExceptionWrapper {
  170.         try {

  171.             // Set up the propagators parallelizer
  172.             final NumericalPropagator[] propagators = createPropagators(point);
  173.             final Orbit[] orbits = new Orbit[propagators.length];
  174.             for (int i = 0; i < propagators.length; ++i) {
  175.                 mappers[i] = configureDerivatives(propagators[i]);
  176.                 orbits[i]  = propagators[i].getInitialState().getOrbit();
  177.             }
  178.             final PropagatorsParallelizer parallelizer =
  179.                             new PropagatorsParallelizer(Arrays.asList(propagators), configureMeasurements(point));

  180.             // Reset value and Jacobian
  181.             evaluations.clear();
  182.             value.set(0.0);
  183.             for (int i = 0; i < jacobian.getRowDimension(); ++i) {
  184.                 for (int j = 0; j < jacobian.getColumnDimension(); ++j) {
  185.                     jacobian.setEntry(i, j, 0.0);
  186.                 }
  187.             }

  188.             // run the propagation, gathering residuals on the fly
  189.             parallelizer.propagate(firstDate.shiftedBy(-1.0), lastDate.shiftedBy(+1.0));

  190.             observer.modelCalled(orbits, evaluations);

  191.             return new Pair<RealVector, RealMatrix>(value, jacobian);

  192.         } catch (OrekitException oe) {
  193.             throw new OrekitExceptionWrapper(oe);
  194.         }
  195.     }

  196.     /** Get the iterations count.
  197.      * @return iterations count
  198.      */
  199.     public int getIterationsCount() {
  200.         return iterationsCounter.getCount();
  201.     }

  202.     /** Get the evaluations count.
  203.      * @return evaluations count
  204.      */
  205.     public int getEvaluationsCount() {
  206.         return evaluationsCounter.getCount();
  207.     }

  208.     /** Get the selected propagation drivers for a propagatorBuilder.
  209.      * @param iBuilder index of the builder in the builders' array
  210.      * @return the list of selected propagation drivers for propagatorBuilder of index iBuilder
  211.      * @exception OrekitException if orbit cannot be created with the current point
  212.      */
  213.     public ParameterDriversList getSelectedPropagationDriversForBuilder(final int iBuilder)
  214.         throws OrekitException {

  215.         // Lazy evaluation, create the list only if it hasn't been created yet
  216.         if (estimatedPropagationParameters[iBuilder] == null) {

  217.             // Gather the drivers
  218.             final ParameterDriversList selectedPropagationDrivers = new ParameterDriversList();
  219.             for (final DelegatingDriver delegating : builders[iBuilder].getPropagationParametersDrivers().getDrivers()) {
  220.                 if (delegating.isSelected()) {
  221.                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
  222.                         selectedPropagationDrivers.add(driver);
  223.                     }
  224.                 }
  225.             }

  226.             // List of propagation drivers are sorted in the BatchLSEstimator class.
  227.             // Hence we need to sort this list so the parameters' indexes match
  228.             selectedPropagationDrivers.sort();

  229.             // Add the list of selected propagation drivers to the array
  230.             estimatedPropagationParameters[iBuilder] = selectedPropagationDrivers;
  231.         }
  232.         return estimatedPropagationParameters[iBuilder];
  233.     }

  234.     /** Create the propagators and parameters corresponding to an evaluation point.
  235.      * @param point evaluation point
  236.      * @return an array of new propagators
  237.      * @exception OrekitException if orbit cannot be created with the current point
  238.      */
  239.     public NumericalPropagator[] createPropagators(final RealVector point)
  240.         throws OrekitException {

  241.         final NumericalPropagator[] propagators = new NumericalPropagator[builders.length];

  242.         // Set up the propagators
  243.         for (int i = 0; i < builders.length; ++i) {

  244.             // Get the number of selected orbital drivers in the builder
  245.             final int nbOrb    = orbitsEndColumns[i] - orbitsStartColumns[i];

  246.             // Get the list of selected propagation drivers in the builder and its size
  247.             final ParameterDriversList selectedPropagationDrivers = getSelectedPropagationDriversForBuilder(i);
  248.             final int nbParams = selectedPropagationDrivers.getNbParams();

  249.             // Init the array of normalized parameters for the builder
  250.             final double[] propagatorArray = new double[nbOrb + nbParams];

  251.             // Add the orbital drivers normalized values
  252.             for (int j = 0; j < nbOrb; ++j) {
  253.                 propagatorArray[j] = point.getEntry(orbitsStartColumns[i] + j);
  254.             }

  255.             // Add the propagation drivers normalized values
  256.             for (int j = 0; j < nbParams; ++j) {
  257.                 propagatorArray[nbOrb + j] =
  258.                                 point.getEntry(propagationParameterColumns.get(selectedPropagationDrivers.getDrivers().get(j).getName()));
  259.             }

  260.             // Build the propagator
  261.             propagators[i] = builders[i].buildPropagator(propagatorArray);
  262.         }

  263.         return propagators;

  264.     }

  265.     /** Configure the multi-satellites handler to handle measurements.
  266.      * @param point evaluation point
  267.      * @return multi-satellites handler to handle measurements
  268.      * @exception OrekitException if measurements parameters cannot be set with the current point
  269.      */
  270.     private MultiSatStepHandler configureMeasurements(final RealVector point)
  271.         throws OrekitException {

  272.         // Set up the measurement parameters
  273.         int index = orbitsEndColumns[builders.length - 1] + propagationParameterColumns.size();
  274.         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
  275.             parameter.setNormalizedValue(point.getEntry(index++));
  276.         }

  277.         // Set up measurements handler
  278.         final List<PreCompensation> precompensated = new ArrayList<>();
  279.         for (final ObservedMeasurement<?> measurement : measurements) {
  280.             if (measurement.isEnabled()) {
  281.                 precompensated.add(new PreCompensation(measurement, evaluations.get(measurement)));
  282.             }
  283.         }
  284.         precompensated.sort(new ChronologicalComparator());

  285.         firstDate = precompensated.get(0).getDate();
  286.         lastDate  = precompensated.get(precompensated.size() - 1).getDate();

  287.         return new MeasurementHandler(this, precompensated);

  288.     }

  289.     /** Configure the propagator to compute derivatives.
  290.      * @param propagator {@link Propagator} to configure
  291.      * @return mapper for this propagator
  292.      * @exception OrekitException if orbit cannot be created with the current point
  293.      */
  294.     private JacobiansMapper configureDerivatives(final NumericalPropagator propagator)
  295.         throws OrekitException {

  296.         final String equationName = Model.class.getName() + "-derivatives";
  297.         final PartialDerivativesEquations partials = new PartialDerivativesEquations(equationName, propagator);

  298.         // add the derivatives to the initial state
  299.         final SpacecraftState rawState = propagator.getInitialState();
  300.         final SpacecraftState stateWithDerivatives = partials.setInitialJacobians(rawState);
  301.         propagator.resetInitialState(stateWithDerivatives);

  302.         return partials.getMapper();

  303.     }

  304.     /** Fetch a measurement that was evaluated during propagation.
  305.      * @param index index of the measurement first component
  306.      * @param evaluation measurement evaluation
  307.      * @exception OrekitException if Jacobians cannot be computed
  308.      */
  309.     void fetchEvaluatedMeasurement(final int index, final EstimatedMeasurement<?> evaluation)
  310.         throws OrekitException {

  311.         // States and observed measurement
  312.         final SpacecraftState[]      evaluationStates    = evaluation.getStates();
  313.         final ObservedMeasurement<?> observedMeasurement = evaluation.getObservedMeasurement();

  314.         // compute weighted residuals
  315.         evaluations.put(observedMeasurement, evaluation);
  316.         final double[] evaluated = evaluation.getEstimatedValue();
  317.         final double[] observed  = observedMeasurement.getObservedValue();
  318.         final double[] sigma     = observedMeasurement.getTheoreticalStandardDeviation();
  319.         final double[] weight    = evaluation.getCurrentWeight();
  320.         for (int i = 0; i < evaluated.length; ++i) {
  321.             value.setEntry(index + i, weight[i] * (evaluated[i] - observed[i]) / sigma[i]);
  322.         }

  323.         for (int k = 0; k < evaluationStates.length; ++k) {

  324.             final int p = observedMeasurement.getPropagatorsIndices().get(k);

  325.             // partial derivatives of the current Cartesian coordinates with respect to current orbital state
  326.             final double[][] aCY = new double[6][6];
  327.             final Orbit currentOrbit = evaluationStates[k].getOrbit();
  328.             currentOrbit.getJacobianWrtParameters(builders[p].getPositionAngle(), aCY);
  329.             final RealMatrix dCdY = new Array2DRowRealMatrix(aCY, false);

  330.             // Jacobian of the measurement with respect to current orbital state
  331.             final RealMatrix dMdC = new Array2DRowRealMatrix(evaluation.getStateDerivatives(k), false);
  332.             final RealMatrix dMdY = dMdC.multiply(dCdY);

  333.             // Jacobian of the measurement with respect to initial orbital state
  334.             final double[][] aYY0 = new double[6][6];
  335.             mappers[p].getStateJacobian(evaluationStates[k], aYY0);
  336.             final RealMatrix dYdY0 = new Array2DRowRealMatrix(aYY0, false);
  337.             final RealMatrix dMdY0 = dMdY.multiply(dYdY0);
  338.             for (int i = 0; i < dMdY0.getRowDimension(); ++i) {
  339.                 int jOrb = orbitsStartColumns[p];
  340.                 for (int j = 0; j < dMdY0.getColumnDimension(); ++j) {
  341.                     final ParameterDriver driver = builders[p].getOrbitalParametersDrivers().getDrivers().get(j);
  342.                     if (driver.isSelected()) {
  343.                         jacobian.setEntry(index + i, jOrb++,
  344.                                           weight[i] * dMdY0.getEntry(i, j) / sigma[i] * driver.getScale());
  345.                     }
  346.                 }
  347.             }

  348.             // Jacobian of the measurement with respect to propagation parameters
  349.             final ParameterDriversList selectedPropagationDrivers = getSelectedPropagationDriversForBuilder(p);
  350.             final int nbParams = selectedPropagationDrivers.getNbParams();
  351.             if ( nbParams > 0) {
  352.                 final double[][] aYPp  = new double[6][nbParams];
  353.                 mappers[p].getParametersJacobian(evaluationStates[k], aYPp);
  354.                 final RealMatrix dYdPp = new Array2DRowRealMatrix(aYPp, false);
  355.                 final RealMatrix dMdPp = dMdY.multiply(dYdPp);
  356.                 for (int i = 0; i < dMdPp.getRowDimension(); ++i) {
  357.                     for (int j = 0; j < nbParams; ++j) {
  358.                         final ParameterDriver delegating = selectedPropagationDrivers.getDrivers().get(j);
  359.                         jacobian.addToEntry(index + i, propagationParameterColumns.get(delegating.getName()),
  360.                                             weight[i] * dMdPp.getEntry(i, j) / sigma[i] * delegating.getScale());
  361.                     }
  362.                 }
  363.             }

  364.         }

  365.         // Jacobian of the measurement with respect to measurements parameters
  366.         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
  367.             if (driver.isSelected()) {
  368.                 final double[] aMPm = evaluation.getParameterDerivatives(driver);
  369.                 for (int i = 0; i < aMPm.length; ++i) {
  370.                     jacobian.setEntry(index + i, measurementParameterColumns.get(driver.getName()),
  371.                                       weight[i] * aMPm[i] / sigma[i] * driver.getScale());
  372.                 }
  373.             }
  374.         }

  375.     }

  376. }