BatchLSEstimator.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.leastsquares;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.Comparator;
  22. import java.util.List;
  23. import java.util.Map;

  24. import org.hipparchus.exception.LocalizedCoreFormats;
  25. import org.hipparchus.exception.MathIllegalArgumentException;
  26. import org.hipparchus.exception.MathRuntimeException;
  27. import org.hipparchus.linear.RealMatrix;
  28. import org.hipparchus.linear.RealVector;
  29. import org.hipparchus.optim.ConvergenceChecker;
  30. import org.hipparchus.optim.nonlinear.vector.leastsquares.EvaluationRmsChecker;
  31. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresBuilder;
  32. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer;
  33. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer.Optimum;
  34. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
  35. import org.hipparchus.optim.nonlinear.vector.leastsquares.ParameterValidator;
  36. import org.hipparchus.util.Incrementor;
  37. import org.orekit.errors.OrekitException;
  38. import org.orekit.estimation.measurements.EstimatedMeasurement;
  39. import org.orekit.estimation.measurements.EstimationsProvider;
  40. import org.orekit.estimation.measurements.ObservedMeasurement;
  41. import org.orekit.orbits.Orbit;
  42. import org.orekit.propagation.Propagator;
  43. import org.orekit.propagation.analytical.BrouwerLyddanePropagator;
  44. import org.orekit.propagation.analytical.EcksteinHechlerPropagator;
  45. import org.orekit.propagation.analytical.Ephemeris;
  46. import org.orekit.propagation.analytical.KeplerianPropagator;
  47. import org.orekit.propagation.analytical.tle.TLEPropagator;
  48. import org.orekit.propagation.conversion.AbstractPropagatorBuilder;
  49. import org.orekit.propagation.conversion.PropagatorBuilder;
  50. import org.orekit.propagation.numerical.NumericalPropagator;
  51. import org.orekit.propagation.semianalytical.dsst.DSSTPropagator;
  52. import org.orekit.time.AbsoluteDate;
  53. import org.orekit.utils.ParameterDriver;
  54. import org.orekit.utils.ParameterDriversList;
  55. import org.orekit.utils.ParameterDriversList.DelegatingDriver;
  56. import org.orekit.utils.TimeSpanMap.Span;


  57. /** Least squares estimator for orbit determination.
  58.  * <p>
  59.  * The least squares estimator can be used with different orbit propagators
  60.  * in Orekit. Current propagators list of usable propagators are {@link NumericalPropagator numerical},
  61.  * {@link DSSTPropagator DSST}, {@link BrouwerLyddanePropagator Brouwer-Lyddane},
  62.  * {@link EcksteinHechlerPropagator Eckstein-Hechler}, {@link TLEPropagator SGP4},
  63.  * {@link KeplerianPropagator Keplerian}, and {@link Ephemeris ephemeris-based}.
  64.  * </p>
  65.  * @author Luc Maisonobe
  66.  * @since 8.0
  67.  */
  68. public class BatchLSEstimator {

  69.     /** Builders for propagator. */
  70.     private final PropagatorBuilder[] builders;

  71.     /** Measurements. */
  72.     private final List<ObservedMeasurement<?>> measurements;

  73.     /** Solver for least squares problem. */
  74.     private final LeastSquaresOptimizer optimizer;

  75.     /** Convergence checker. */
  76.     private ConvergenceChecker<LeastSquaresProblem.Evaluation> convergenceChecker;

  77.     /** Builder for the least squares problem. */
  78.     private final LeastSquaresBuilder lsBuilder;

  79.     /** Observer for iterations. */
  80.     private BatchLSObserver observer;

  81.     /** Last estimations. */
  82.     private Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> estimations;

  83.     /** Last orbits. */
  84.     private Orbit[] orbits;

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

  87.     /** Counter for the evaluations. */
  88.     private Incrementor evaluationsCounter;

  89.     /** Counter for the iterations. */
  90.     private Incrementor iterationsCounter;

  91.     /** Simple constructor.
  92.      * <p>
  93.      * If multiple {@link PropagatorBuilder propagator builders} are set up,
  94.      * the orbits of several spacecrafts will be used simultaneously.
  95.      * This is useful if the propagators share some model or measurements
  96.      * parameters (typically pole motion, prime meridian correction or
  97.      * ground stations positions).
  98.      * </p>
  99.      * <p>
  100.      * Setting up multiple {@link PropagatorBuilder propagator builders} is
  101.      * also useful when inter-satellite measurements are used, even if only one
  102.      * of the orbit is estimated and the other ones are fixed. This is typically
  103.      * used when very high accuracy GNSS measurements are needed and the
  104.      * navigation bulletins are not considered accurate enough and the navigation
  105.      * constellation must be propagated numerically.
  106.      * </p>
  107.      * @param optimizer solver for least squares problem
  108.      * @param propagatorBuilder builders to use for propagation
  109.      */
  110.     public BatchLSEstimator(final LeastSquaresOptimizer optimizer,
  111.                             final PropagatorBuilder... propagatorBuilder) {

  112.         this.builders                       = propagatorBuilder;
  113.         this.measurements                   = new ArrayList<>();
  114.         this.optimizer                      = optimizer;
  115.         this.lsBuilder                      = new LeastSquaresBuilder();
  116.         this.observer                       = null;
  117.         this.estimations                    = null;
  118.         this.orbits                         = new Orbit[builders.length];

  119.         setParametersConvergenceThreshold(Double.NaN);

  120.         // our model computes value and Jacobian in one call,
  121.         // so we don't use the lazy evaluation feature
  122.         lsBuilder.lazyEvaluation(false);

  123.         // we manage weight by ourselves, as we change them during
  124.         // iterations (setting to 0 the identified outliers measurements)
  125.         // so the least squares problem should not see our weights
  126.         lsBuilder.weight(null);

  127.     }

  128.     /** Set an observer for iterations.
  129.      * @param observer observer to be notified at the end of each iteration
  130.      */
  131.     public void setObserver(final BatchLSObserver observer) {
  132.         this.observer = observer;
  133.     }

  134.     /** Add a measurement.
  135.      * @param measurement measurement to add
  136.      */
  137.     public void addMeasurement(final ObservedMeasurement<?> measurement) {
  138.         measurements.add(measurement);
  139.     }

  140.     /** Set the maximum number of iterations.
  141.      * <p>
  142.      * The iterations correspond to the top level iterations of
  143.      * the {@link LeastSquaresOptimizer least squares optimizer}.
  144.      * </p>
  145.      * @param maxIterations maxIterations maximum number of iterations
  146.      * @see #setMaxEvaluations(int)
  147.      * @see #getIterationsCount()
  148.      */
  149.     public void setMaxIterations(final int maxIterations) {
  150.         lsBuilder.maxIterations(maxIterations);
  151.     }

  152.     /** Set the maximum number of model evaluations.
  153.      * <p>
  154.      * The evaluations correspond to the orbit propagations and
  155.      * measurements estimations performed with a set of estimated
  156.      * parameters.
  157.      * </p>
  158.      * <p>
  159.      * For {@link org.hipparchus.optim.nonlinear.vector.leastsquares.GaussNewtonOptimizer
  160.      * Gauss-Newton optimizer} there is one evaluation at each iteration,
  161.      * so the maximum numbers may be set to the same value. For {@link
  162.      * org.hipparchus.optim.nonlinear.vector.leastsquares.LevenbergMarquardtOptimizer
  163.      * Levenberg-Marquardt optimizer}, there can be several evaluations at
  164.      * some iterations (typically for the first couple of iterations), so the
  165.      * maximum number of evaluations may be set to a higher value than the
  166.      * maximum number of iterations.
  167.      * </p>
  168.      * @param maxEvaluations maximum number of model evaluations
  169.      * @see #setMaxIterations(int)
  170.      * @see #getEvaluationsCount()
  171.      */
  172.     public void setMaxEvaluations(final int maxEvaluations) {
  173.         lsBuilder.maxEvaluations(maxEvaluations);
  174.     }

  175.     /** Get the orbital parameters supported by this estimator.
  176.      * <p>
  177.      * If there are more than one propagator builder, then the names
  178.      * of the drivers have an index marker in square brackets appended
  179.      * to them in order to distinguish the various orbits. So for example
  180.      * with one builder generating Keplerian orbits the names would be
  181.      * simply "a", "e", "i"... but if there are several builders the
  182.      * names would be "a[0]", "e[0]", "i[0]"..."a[1]", "e[1]", "i[1]"...
  183.      * </p>
  184.      * @param estimatedOnly if true, only estimated parameters are returned
  185.      * @return orbital parameters supported by this estimator
  186.      */
  187.     public ParameterDriversList getOrbitalParametersDrivers(final boolean estimatedOnly) {

  188.         final ParameterDriversList estimated = new ParameterDriversList();
  189.         for (int i = 0; i < builders.length; ++i) {
  190.             final String suffix = builders.length > 1 ? "[" + i + "]" : null;
  191.             for (final DelegatingDriver delegating : builders[i].getOrbitalParametersDrivers().getDrivers()) {
  192.                 if (delegating.isSelected() || !estimatedOnly) {
  193.                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
  194.                         if (suffix != null && !driver.getName().endsWith(suffix)) {
  195.                             // we add suffix only conditionally because the method may already have been called
  196.                             // and suffixes may have already been appended
  197.                             driver.setName(driver.getName() + suffix);
  198.                         }
  199.                         estimated.add(driver);
  200.                     }
  201.                 }
  202.             }
  203.         }
  204.         return estimated;

  205.     }

  206.     /** Get the propagator parameters supported by this estimator.
  207.      * @param estimatedOnly if true, only estimated parameters are returned
  208.      * @return propagator parameters supported by this estimator
  209.      */
  210.     public ParameterDriversList getPropagatorParametersDrivers(final boolean estimatedOnly) {

  211.         final ParameterDriversList estimated = new ParameterDriversList();
  212.         for (PropagatorBuilder builder : builders) {
  213.             for (final DelegatingDriver delegating : builder.getPropagationParametersDrivers().getDrivers()) {
  214.                 if (delegating.isSelected() || !estimatedOnly) {
  215.                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
  216.                         estimated.add(driver);
  217.                     }
  218.                 }
  219.             }
  220.         }
  221.         return estimated;

  222.     }

  223.     /** Get the measurements parameters supported by this estimator (including measurements and modifiers).
  224.      * @param estimatedOnly if true, only estimated parameters are returned
  225.      * @return measurements parameters supported by this estimator
  226.      */
  227.     public ParameterDriversList getMeasurementsParametersDrivers(final boolean estimatedOnly) {

  228.         final ParameterDriversList parameters =  new ParameterDriversList();
  229.         for (final  ObservedMeasurement<?> measurement : measurements) {
  230.             for (final ParameterDriver driver : measurement.getParametersDrivers()) {
  231.                 if (!estimatedOnly || driver.isSelected()) {
  232.                     parameters.add(driver);
  233.                 }
  234.             }
  235.         }

  236.         parameters.sort();

  237.         return parameters;

  238.     }

  239.     /**
  240.      * Set convergence threshold.
  241.      * <p>
  242.      * The convergence used for estimation is based on the estimated
  243.      * parameters {@link ParameterDriver#getNormalizedValue() normalized values}.
  244.      * Convergence is considered to have been reached when the difference
  245.      * between previous and current normalized value is less than the
  246.      * convergence threshold for all parameters. The same value is used
  247.      * for all parameters since they are normalized and hence dimensionless.
  248.      * </p>
  249.      * <p>
  250.      * Normalized values are computed as {@code (current - reference)/scale},
  251.      * so convergence is reached when the following condition holds for
  252.      * all estimated parameters:
  253.      * {@code |current[i] - previous[i]| <= threshold * scale[i]}
  254.      * </p>
  255.      * <p>
  256.      * So the convergence threshold specified here can be considered as
  257.      * a multiplication factor applied to scale. Since for all parameters
  258.      * the scale is often small (typically about 1 m for orbital positions
  259.      * for example), then the threshold should not be too small. A value
  260.      * of 10⁻³ is often quite accurate.
  261.      * </p>
  262.      * <p>
  263.      * Calling this method overrides any checker that could have been set
  264.      * beforehand by calling {@link #setConvergenceChecker(ConvergenceChecker)}.
  265.      * Both methods are mutually exclusive.
  266.      * </p>
  267.      *
  268.      * @param parametersConvergenceThreshold convergence threshold on
  269.      * normalized parameters (dimensionless, related to parameters scales)
  270.      * @see #setConvergenceChecker(ConvergenceChecker)
  271.      * @see EvaluationRmsChecker
  272.      */
  273.     public void setParametersConvergenceThreshold(final double parametersConvergenceThreshold) {
  274.         setConvergenceChecker((iteration, previous, current) ->
  275.                               current.getPoint().getLInfDistance(previous.getPoint()) <= parametersConvergenceThreshold);
  276.     }

  277.     /** Set a custom convergence checker.
  278.      * <p>
  279.      * Calling this method overrides any checker that could have been set
  280.      * beforehand by calling {@link #setParametersConvergenceThreshold(double)}.
  281.      * Both methods are mutually exclusive.
  282.      * </p>
  283.      * @param convergenceChecker convergence checker to set
  284.      * @see #setParametersConvergenceThreshold(double)
  285.      * @since 10.1
  286.      */
  287.     public void setConvergenceChecker(final ConvergenceChecker<LeastSquaresProblem.Evaluation> convergenceChecker) {
  288.         this.convergenceChecker = convergenceChecker;
  289.     }

  290.     /** Estimate the orbital, propagation and measurements parameters.
  291.      * <p>
  292.      * The initial guess for all parameters must have been set before calling this method
  293.      * using {@link #getOrbitalParametersDrivers(boolean)}, {@link #getPropagatorParametersDrivers(boolean)},
  294.      * and {@link #getMeasurementsParametersDrivers(boolean)} and then {@link ParameterDriver#setValue(double)
  295.      * setting the values} of the parameters.
  296.      * </p>
  297.      * <p>
  298.      * For parameters whose reference date has not been set to a non-null date beforehand (i.e.
  299.      * the parameters for which {@link ParameterDriver#getReferenceDate()} returns {@code null},
  300.      * a default reference date will be set automatically at the start of the estimation to the
  301.      * {@link AbstractPropagatorBuilder#getInitialOrbitDate() initial orbit date} of the first
  302.      * propagator builder. For parameters whose reference date has been set to a non-null date,
  303.      * this reference date is untouched.
  304.      * </p>
  305.      * <p>
  306.      * After this method returns, the estimated parameters can be retrieved using
  307.      * {@link #getOrbitalParametersDrivers(boolean)}, {@link #getPropagatorParametersDrivers(boolean)},
  308.      * and {@link #getMeasurementsParametersDrivers(boolean)} and then {@link ParameterDriver#getValue()
  309.      * getting the values} of the parameters.
  310.      * </p>
  311.      * <p>
  312.      * As a convenience, the method also returns a fully configured and ready to use
  313.      * propagator set up with all the estimated values.
  314.      * </p>
  315.      * <p>
  316.      * For even more in-depth information, the {@link #getOptimum()} method provides detailed
  317.      * elements (covariance matrix, estimated parameters standard deviation, weighted Jacobian, RMS,
  318.      * χ², residuals and more).
  319.      * </p>
  320.      * @return propagators configured with estimated orbits as initial states, and all
  321.      * propagators estimated parameters also set
  322.      */
  323.     public Propagator[] estimate() {

  324.         // set reference date for all parameters that lack one (including the not estimated parameters)
  325.         for (final ParameterDriver driver : getOrbitalParametersDrivers(false).getDrivers()) {
  326.             if (driver.getReferenceDate() == null) {
  327.                 driver.setReferenceDate(builders[0].getInitialOrbitDate());
  328.             }
  329.         }
  330.         for (final ParameterDriver driver : getPropagatorParametersDrivers(false).getDrivers()) {
  331.             if (driver.getReferenceDate() == null) {
  332.                 driver.setReferenceDate(builders[0].getInitialOrbitDate());
  333.             }
  334.         }
  335.         for (final ParameterDriver driver : getMeasurementsParametersDrivers(false).getDrivers()) {
  336.             if (driver.getReferenceDate() == null) {
  337.                 driver.setReferenceDate(builders[0].getInitialOrbitDate());
  338.             }
  339.         }

  340.         // get all estimated parameters
  341.         final ParameterDriversList estimatedOrbitalParameters      = getOrbitalParametersDrivers(true);
  342.         final ParameterDriversList estimatedPropagatorParameters   = getPropagatorParametersDrivers(true);
  343.         final ParameterDriversList estimatedMeasurementsParameters = getMeasurementsParametersDrivers(true);

  344.         // create start point
  345.         final double[] start = new double[estimatedOrbitalParameters.getNbValuesToEstimate() +
  346.                                           estimatedPropagatorParameters.getNbValuesToEstimate() +
  347.                                           estimatedMeasurementsParameters.getNbValuesToEstimate()];

  348.         int iStart = 0;
  349.         for (final ParameterDriver driver : estimatedOrbitalParameters.getDrivers()) {
  350.             Span<Double> span = driver.getValueSpanMap().getFirstSpan();
  351.             start[iStart++] = driver.getNormalizedValue(span.getStart());
  352.             for (int spanNumber = 0; spanNumber < driver.getNbOfValues() - 1; ++spanNumber) {
  353.                 span = driver.getValueSpanMap().getSpan(span.getEnd());
  354.                 start[iStart++] = driver.getNormalizedValue(span.getStart());
  355.             }
  356.         }
  357.         for (final ParameterDriver driver : estimatedPropagatorParameters.getDrivers()) {
  358.             Span<Double> span = driver.getValueSpanMap().getFirstSpan();
  359.             start[iStart++] = driver.getNormalizedValue(span.getStart());
  360.             for (int spanNumber = 0; spanNumber < driver.getNbOfValues() - 1; ++spanNumber) {
  361.                 span = driver.getValueSpanMap().getSpan(span.getEnd());
  362.                 start[iStart++] = driver.getNormalizedValue(span.getStart());
  363.             }
  364.         }
  365.         for (final ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
  366.             Span<Double> span = driver.getValueSpanMap().getFirstSpan();
  367.             start[iStart++] = driver.getNormalizedValue(span.getStart());
  368.             for (int spanNumber = 0; spanNumber < driver.getNbOfValues() - 1; ++spanNumber) {
  369.                 span = driver.getValueSpanMap().getSpan(span.getEnd());
  370.                 start[iStart++] = driver.getNormalizedValue(span.getStart());
  371.             }
  372.         }
  373.         lsBuilder.start(start);

  374.         // create target (which is an array set to 0, as we compute weighted residuals ourselves)
  375.         int p = 0;
  376.         for (final ObservedMeasurement<?> measurement : measurements) {
  377.             if (measurement.isEnabled()) {
  378.                 p += measurement.getDimension();
  379.             }
  380.         }
  381.         final double[] target = new double[p];
  382.         lsBuilder.target(target);

  383.         // set up the model
  384.         final ModelObserver modelObserver = new ModelObserver() {
  385.             /** {@inheritDoc} */
  386.             @Override
  387.             public void modelCalled(final Orbit[] newOrbits,
  388.                                     final Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> newEstimations) {
  389.                 BatchLSEstimator.this.orbits      = newOrbits;
  390.                 BatchLSEstimator.this.estimations = newEstimations;
  391.             }
  392.         };
  393.         final AbstractBatchLSModel model = builders[0].buildLeastSquaresModel(builders, measurements, estimatedMeasurementsParameters, modelObserver);

  394.         lsBuilder.model(model);

  395.         // add a validator for orbital parameters
  396.         lsBuilder.parameterValidator(new Validator(estimatedOrbitalParameters,
  397.                                                    estimatedPropagatorParameters,
  398.                                                    estimatedMeasurementsParameters));

  399.         lsBuilder.checker(convergenceChecker);

  400.         // set up the problem to solve
  401.         final LeastSquaresProblem problem = new TappedLSProblem(lsBuilder.build(),
  402.                                                                 model,
  403.                                                                 estimatedOrbitalParameters,
  404.                                                                 estimatedPropagatorParameters,
  405.                                                                 estimatedMeasurementsParameters);

  406.         try {

  407.             // solve the problem
  408.             optimum = optimizer.optimize(problem);

  409.             // create a new configured propagator with all estimated parameters
  410.             return model.createPropagators(optimum.getPoint());

  411.         } catch (MathRuntimeException mrte) {
  412.             throw new OrekitException(mrte);
  413.         }
  414.     }

  415.     /** Get the last estimations performed.
  416.      * @return last estimations performed
  417.      */
  418.     public Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> getLastEstimations() {
  419.         return Collections.unmodifiableMap(estimations);
  420.     }

  421.     /** Get the optimum found.
  422.      * <p>
  423.      * The {@link Optimum} object contains detailed elements (covariance matrix, estimated
  424.      * parameters standard deviation, weighted Jacobian, RMS, χ², residuals and more).
  425.      * </p>
  426.      * <p>
  427.      * Beware that the returned object is the raw view from the underlying mathematical
  428.      * library. At this raw level, parameters have {@link ParameterDriver#getNormalizedValue()
  429.      * normalized values} whereas the space flight parameters have {@link ParameterDriver#getValue()
  430.      * physical values} with their units. So there are {@link ParameterDriver#getScale() scaling
  431.      * factors} to apply when using these elements.
  432.      * </p>
  433.      * @return optimum found after last call to {@link #estimate()}
  434.      */
  435.     public Optimum getOptimum() {
  436.         return optimum;
  437.     }

  438.     /** Get the covariances matrix in space flight dynamics physical units.
  439.      * <p>
  440.      * This method retrieve the {@link
  441.      * org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem.Evaluation#getCovariances(double)
  442.      * covariances} from the [@link {@link #getOptimum() optimum} and applies the scaling factors
  443.      * to it in order to convert it from raw normalized values back to physical values.
  444.      * </p>
  445.      * @param threshold threshold to identify matrix singularity
  446.      * @return covariances matrix in space flight dynamics physical units
  447.      * @since 9.1
  448.      */
  449.     public RealMatrix getPhysicalCovariances(final double threshold) {
  450.         final RealMatrix covariances;
  451.         try {
  452.             // get the normalized matrix
  453.             covariances = optimum.getCovariances(threshold).copy();
  454.         } catch (MathIllegalArgumentException miae) {
  455.             // the problem is singular
  456.             throw new OrekitException(miae);
  457.         }

  458.         // retrieve the scaling factors
  459.         final double[] scale = new double[covariances.getRowDimension()];
  460.         int index = 0;
  461.         for (final ParameterDriver driver : getOrbitalParametersDrivers(true).getDrivers()) {
  462.             for (int i = 0; i < driver.getNbOfValues(); ++i) {
  463.                 scale[index++] = driver.getScale();
  464.             }
  465.         }
  466.         for (final ParameterDriver driver : getPropagatorParametersDrivers(true).getDrivers()) {
  467.             for (int i = 0; i < driver.getNbOfValues(); ++i) {
  468.                 scale[index++] = driver.getScale();
  469.             }
  470.         }
  471.         for (final ParameterDriver driver : getMeasurementsParametersDrivers(true).getDrivers()) {
  472.             for (int i = 0; i < driver.getNbOfValues(); ++i) {
  473.                 scale[index++] = driver.getScale();
  474.             }
  475.         }

  476.         // unnormalize the matrix, to retrieve physical covariances
  477.         for (int i = 0; i < covariances.getRowDimension(); ++i) {
  478.             for (int j = 0; j < covariances.getColumnDimension(); ++j) {
  479.                 covariances.setEntry(i, j, scale[i] * scale[j] * covariances.getEntry(i, j));
  480.             }
  481.         }

  482.         return covariances;

  483.     }

  484.     /** Get the number of iterations used for last estimation.
  485.      * @return number of iterations used for last estimation
  486.      * @see #setMaxIterations(int)
  487.      */
  488.     public int getIterationsCount() {
  489.         return iterationsCounter.getCount();
  490.     }

  491.     /** Get the number of evaluations used for last estimation.
  492.      * @return number of evaluations used for last estimation
  493.      * @see #setMaxEvaluations(int)
  494.      */
  495.     public int getEvaluationsCount() {
  496.         return evaluationsCounter.getCount();
  497.     }

  498.     /** Wrapper used to tap the various counters. */
  499.     private class TappedLSProblem implements LeastSquaresProblem {

  500.         /** Underlying problem. */
  501.         private final LeastSquaresProblem problem;

  502.         /** Multivariate function model. */
  503.         private final AbstractBatchLSModel model;

  504.         /** Estimated orbital parameters. */
  505.         private final ParameterDriversList estimatedOrbitalParameters;

  506.         /** Estimated propagator parameters. */
  507.         private final ParameterDriversList estimatedPropagatorParameters;

  508.         /** Estimated measurements parameters. */
  509.         private final ParameterDriversList estimatedMeasurementsParameters;

  510.         /** Simple constructor.
  511.          * @param problem underlying problem
  512.          * @param model multivariate function model
  513.          * @param estimatedOrbitalParameters estimated orbital parameters
  514.          * @param estimatedPropagatorParameters estimated propagator parameters
  515.          * @param estimatedMeasurementsParameters estimated measurements parameters
  516.          */
  517.         TappedLSProblem(final LeastSquaresProblem problem,
  518.                         final AbstractBatchLSModel model,
  519.                         final ParameterDriversList estimatedOrbitalParameters,
  520.                         final ParameterDriversList estimatedPropagatorParameters,
  521.                         final ParameterDriversList estimatedMeasurementsParameters) {
  522.             this.problem                         = problem;
  523.             this.model                           = model;
  524.             this.estimatedOrbitalParameters      = estimatedOrbitalParameters;
  525.             this.estimatedPropagatorParameters   = estimatedPropagatorParameters;
  526.             this.estimatedMeasurementsParameters = estimatedMeasurementsParameters;
  527.         }

  528.         /** {@inheritDoc} */
  529.         @Override
  530.         public Incrementor getEvaluationCounter() {
  531.             // tap the evaluations counter
  532.             BatchLSEstimator.this.evaluationsCounter = problem.getEvaluationCounter();
  533.             model.setEvaluationsCounter(BatchLSEstimator.this.evaluationsCounter);
  534.             return BatchLSEstimator.this.evaluationsCounter;
  535.         }

  536.         /** {@inheritDoc} */
  537.         @Override
  538.         public Incrementor getIterationCounter() {
  539.             // tap the iterations counter
  540.             BatchLSEstimator.this.iterationsCounter = problem.getIterationCounter();
  541.             model.setIterationsCounter(BatchLSEstimator.this.iterationsCounter);
  542.             return BatchLSEstimator.this.iterationsCounter;
  543.         }

  544.         /** {@inheritDoc} */
  545.         @Override
  546.         public ConvergenceChecker<Evaluation> getConvergenceChecker() {
  547.             return problem.getConvergenceChecker();
  548.         }

  549.         /** {@inheritDoc} */
  550.         @Override
  551.         public RealVector getStart() {
  552.             return problem.getStart();
  553.         }

  554.         /** {@inheritDoc} */
  555.         @Override
  556.         public int getObservationSize() {
  557.             return problem.getObservationSize();
  558.         }

  559.         /** {@inheritDoc} */
  560.         @Override
  561.         public int getParameterSize() {
  562.             return problem.getParameterSize();
  563.         }

  564.         /** {@inheritDoc} */
  565.         @Override
  566.         public Evaluation evaluate(final RealVector point) {

  567.             // perform the evaluation
  568.             final Evaluation evaluation = problem.evaluate(point);

  569.             // notify the observer
  570.             if (observer != null) {
  571.                 observer.evaluationPerformed(iterationsCounter.getCount(),
  572.                                              evaluationsCounter.getCount(),
  573.                                              orbits,
  574.                                              estimatedOrbitalParameters,
  575.                                              estimatedPropagatorParameters,
  576.                                              estimatedMeasurementsParameters,
  577.                                              new Provider(),
  578.                                              evaluation);
  579.             }

  580.             return evaluation;

  581.         }

  582.     }

  583.     /** Provider for evaluations. */
  584.     private class Provider implements EstimationsProvider {

  585.         /** Sorted estimations. */
  586.         private EstimatedMeasurement<?>[] sortedEstimations;

  587.         /** {@inheritDoc} */
  588.         @Override
  589.         public int getNumber() {
  590.             return estimations.size();
  591.         }

  592.         /** {@inheritDoc} */
  593.         @Override
  594.         public EstimatedMeasurement<?> getEstimatedMeasurement(final int index) {

  595.             // safety checks
  596.             if (index < 0 || index >= estimations.size()) {
  597.                 throw new OrekitException(LocalizedCoreFormats.OUT_OF_RANGE_SIMPLE,
  598.                                           index, 0, estimations.size());
  599.             }

  600.             if (sortedEstimations == null) {

  601.                 // lazy evaluation of the sorted array
  602.                 sortedEstimations = new EstimatedMeasurement<?>[estimations.size()];
  603.                 int i = 0;
  604.                 for (final Map.Entry<ObservedMeasurement<?>, EstimatedMeasurement<?>> entry : estimations.entrySet()) {
  605.                     sortedEstimations[i++] = entry.getValue();
  606.                 }

  607.                 // sort the array, primarily chronologically
  608.                 Arrays.sort(sortedEstimations, 0, sortedEstimations.length, Comparator.naturalOrder());

  609.             }

  610.             return sortedEstimations[index];

  611.         }

  612.     }

  613.     /** Validator for estimated parameters. */
  614.     private static class Validator implements ParameterValidator {

  615.         /** Estimated orbital parameters. */
  616.         private final ParameterDriversList estimatedOrbitalParameters;

  617.         /** Estimated propagator parameters. */
  618.         private final ParameterDriversList estimatedPropagatorParameters;

  619.         /** Estimated measurements parameters. */
  620.         private final ParameterDriversList estimatedMeasurementsParameters;

  621.         /** Simple constructor.
  622.          * @param estimatedOrbitalParameters estimated orbital parameters
  623.          * @param estimatedPropagatorParameters estimated propagator parameters
  624.          * @param estimatedMeasurementsParameters estimated measurements parameters
  625.          */
  626.         Validator(final ParameterDriversList estimatedOrbitalParameters,
  627.                   final ParameterDriversList estimatedPropagatorParameters,
  628.                   final ParameterDriversList estimatedMeasurementsParameters) {
  629.             this.estimatedOrbitalParameters      = estimatedOrbitalParameters;
  630.             this.estimatedPropagatorParameters   = estimatedPropagatorParameters;
  631.             this.estimatedMeasurementsParameters = estimatedMeasurementsParameters;
  632.         }

  633.         /** {@inheritDoc} */
  634.         @Override
  635.         public RealVector validate(final RealVector params) {

  636.             int i = 0;
  637.             for (final ParameterDriver driver : estimatedOrbitalParameters.getDrivers()) {
  638.                 // let the parameter handle min/max clipping
  639.                 if (driver.getNbOfValues() == 1) {
  640.                     driver.setNormalizedValue(params.getEntry(i), null);
  641.                     params.setEntry(i++, driver.getNormalizedValue(null));

  642.                 // If the parameter driver contains only 1 value to estimate over the all time range
  643.                 } else {
  644.                     // initialization getting the value of the first Span
  645.                     Span<Double> span = driver.getValueSpanMap().getFirstSpan();
  646.                     driver.setNormalizedValue(params.getEntry(i), span.getStart());
  647.                     params.setEntry(i++, driver.getNormalizedValue(span.getStart()));

  648.                     for (int spanNumber = 0; spanNumber < driver.getNbOfValues() - 1; ++spanNumber) {
  649.                         final AbsoluteDate modificationDate = span.getEnd();
  650.                         // get next span, previousSpan.getEnd = span.getStart
  651.                         span = driver.getValueSpanMap().getSpan(modificationDate);
  652.                         driver.setNormalizedValue(params.getEntry(i), modificationDate);
  653.                         params.setEntry(i++, driver.getNormalizedValue(modificationDate));
  654.                     }
  655.                 }

  656.             }
  657.             for (final ParameterDriver driver : estimatedPropagatorParameters.getDrivers()) {
  658.                 // let the parameter handle min/max clipping
  659.                 if (driver.getNbOfValues() == 1) {
  660.                     driver.setNormalizedValue(params.getEntry(i), null);
  661.                     params.setEntry(i++, driver.getNormalizedValue(null));

  662.                 // If the parameter driver contains only 1 value to estimate over the all time range
  663.                 } else {
  664.                     // initialization getting the value of the first Span
  665.                     Span<Double> span = driver.getValueSpanMap().getFirstSpan();
  666.                     driver.setNormalizedValue(params.getEntry(i), span.getStart());
  667.                     params.setEntry(i++, driver.getNormalizedValue(span.getStart()));

  668.                     for (int spanNumber = 0; spanNumber < driver.getNbOfValues() - 1; ++spanNumber) {
  669.                         final AbsoluteDate modificationDate = span.getEnd();
  670.                         // get next span, previousSpan.getEnd = span.getStart
  671.                         span = driver.getValueSpanMap().getSpan(modificationDate);
  672.                         driver.setNormalizedValue(params.getEntry(i), modificationDate);
  673.                         params.setEntry(i++, driver.getNormalizedValue(modificationDate));
  674.                     }
  675.                 }
  676.             }
  677.             for (final ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
  678.                 // let the parameter handle min/max clipping
  679.                 if (driver.getNbOfValues() == 1) {
  680.                     driver.setNormalizedValue(params.getEntry(i), null);
  681.                     params.setEntry(i++, driver.getNormalizedValue(null));

  682.                 // If the parameter driver contains only 1 value to estimate over the all time range
  683.                 } else {
  684.                     // initialization getting the value of the first Span
  685.                     Span<Double> span = driver.getValueSpanMap().getFirstSpan();
  686.                     driver.setNormalizedValue(params.getEntry(i), span.getStart());
  687.                     params.setEntry(i++, driver.getNormalizedValue(span.getStart()));

  688.                     for (int spanNumber = 0; spanNumber < driver.getNbOfValues() - 1; ++spanNumber) {
  689.                         final AbsoluteDate modificationDate = span.getEnd();
  690.                         // get next span, previousSpan.getEnd = span.getStart
  691.                         span = driver.getValueSpanMap().getSpan(modificationDate);
  692.                         driver.setNormalizedValue(params.getEntry(i), modificationDate);
  693.                         params.setEntry(i++, driver.getNormalizedValue(modificationDate));
  694.                     }
  695.                 }
  696.             }
  697.             return params;
  698.         }
  699.     }

  700. }