1   /* Copyright 2002-2021 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  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.hipparchus.exception.LocalizedCoreFormats;
27  import org.hipparchus.exception.MathIllegalArgumentException;
28  import org.hipparchus.exception.MathRuntimeException;
29  import org.hipparchus.linear.RealMatrix;
30  import org.hipparchus.linear.RealVector;
31  import org.hipparchus.optim.ConvergenceChecker;
32  import org.hipparchus.optim.nonlinear.vector.leastsquares.EvaluationRmsChecker;
33  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresBuilder;
34  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer;
35  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer.Optimum;
36  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
37  import org.hipparchus.optim.nonlinear.vector.leastsquares.ParameterValidator;
38  import org.hipparchus.util.Incrementor;
39  import org.orekit.errors.OrekitException;
40  import org.orekit.estimation.measurements.EstimatedMeasurement;
41  import org.orekit.estimation.measurements.EstimationsProvider;
42  import org.orekit.estimation.measurements.ObservedMeasurement;
43  import org.orekit.orbits.Orbit;
44  import org.orekit.propagation.Propagator;
45  import org.orekit.propagation.conversion.AbstractPropagatorBuilder;
46  import org.orekit.propagation.conversion.OrbitDeterminationPropagatorBuilder;
47  import org.orekit.propagation.conversion.PropagatorBuilder;
48  import org.orekit.propagation.numerical.NumericalPropagator;
49  import org.orekit.propagation.semianalytical.dsst.DSSTPropagator;
50  import org.orekit.utils.ParameterDriver;
51  import org.orekit.utils.ParameterDriversList;
52  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
53  
54  
55  /** Least squares estimator for orbit determination.
56   * <p>
57   * Since 10.0, the least squares estimator can be used with both
58   * {@link NumericalPropagator numerical} and {@link DSSTPropagator DSST}
59   * orbit propagators.
60   * </p>
61   * @author Luc Maisonobe
62   * @since 8.0
63   */
64  public class BatchLSEstimator {
65  
66      /** Builders for propagator. */
67      private final OrbitDeterminationPropagatorBuilder[] builders;
68  
69      /** Measurements. */
70      private final List<ObservedMeasurement<?>> measurements;
71  
72      /** Solver for least squares problem. */
73      private final LeastSquaresOptimizer optimizer;
74  
75      /** Convergence checker. */
76      private ConvergenceChecker<LeastSquaresProblem.Evaluation> convergenceChecker;
77  
78      /** Builder for the least squares problem. */
79      private final LeastSquaresBuilder lsBuilder;
80  
81      /** Observer for iterations. */
82      private BatchLSObserver observer;
83  
84      /** Last estimations. */
85      private Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> estimations;
86  
87      /** Last orbits. */
88      private Orbit[] orbits;
89  
90      /** Optimum found. */
91      private Optimum optimum;
92  
93      /** Counter for the evaluations. */
94      private Incrementor evaluationsCounter;
95  
96      /** Counter for the iterations. */
97      private Incrementor iterationsCounter;
98  
99      /** Simple constructor.
100      * <p>
101      * If multiple {@link PropagatorBuilder propagator builders} are set up,
102      * the orbits of several spacecrafts will be used simultaneously.
103      * This is useful if the propagators share some model or measurements
104      * parameters (typically pole motion, prime meridian correction or
105      * ground stations positions).
106      * </p>
107      * <p>
108      * Setting up multiple {@link PropagatorBuilder propagator builders} is
109      * also useful when inter-satellite measurements are used, even if only one
110      * of the orbit is estimated and the other ones are fixed. This is typically
111      * used when very high accuracy GNSS measurements are needed and the
112      * navigation bulletins are not considered accurate enough and the navigation
113      * constellation must be propagated numerically.
114      * </p>
115      * @param optimizer solver for least squares problem
116      * @param propagatorBuilder builders to use for propagation
117      */
118     public BatchLSEstimator(final LeastSquaresOptimizer optimizer,
119                             final OrbitDeterminationPropagatorBuilder... propagatorBuilder) {
120 
121         this.builders                       = propagatorBuilder;
122         this.measurements                   = new ArrayList<ObservedMeasurement<?>>();
123         this.optimizer                      = optimizer;
124         this.lsBuilder                      = new LeastSquaresBuilder();
125         this.observer                       = null;
126         this.estimations                    = null;
127         this.orbits                         = new Orbit[builders.length];
128 
129         setParametersConvergenceThreshold(Double.NaN);
130 
131         // our model computes value and Jacobian in one call,
132         // so we don't use the lazy evaluation feature
133         lsBuilder.lazyEvaluation(false);
134 
135         // we manage weight by ourselves, as we change them during
136         // iterations (setting to 0 the identified outliers measurements)
137         // so the least squares problem should not see our weights
138         lsBuilder.weight(null);
139 
140     }
141 
142     /** Set an observer for iterations.
143      * @param observer observer to be notified at the end of each iteration
144      */
145     public void setObserver(final BatchLSObserver observer) {
146         this.observer = observer;
147     }
148 
149     /** Add a measurement.
150      * @param measurement measurement to add
151      */
152     public void addMeasurement(final ObservedMeasurement<?> measurement) {
153         measurements.add(measurement);
154     }
155 
156     /** Set the maximum number of iterations.
157      * <p>
158      * The iterations correspond to the top level iterations of
159      * the {@link LeastSquaresOptimizer least squares optimizer}.
160      * </p>
161      * @param maxIterations maxIterations maximum number of iterations
162      * @see #setMaxEvaluations(int)
163      * @see #getIterationsCount()
164      */
165     public void setMaxIterations(final int maxIterations) {
166         lsBuilder.maxIterations(maxIterations);
167     }
168 
169     /** Set the maximum number of model evaluations.
170      * <p>
171      * The evaluations correspond to the orbit propagations and
172      * measurements estimations performed with a set of estimated
173      * parameters.
174      * </p>
175      * <p>
176      * For {@link org.hipparchus.optim.nonlinear.vector.leastsquares.GaussNewtonOptimizer
177      * Gauss-Newton optimizer} there is one evaluation at each iteration,
178      * so the maximum numbers may be set to the same value. For {@link
179      * org.hipparchus.optim.nonlinear.vector.leastsquares.LevenbergMarquardtOptimizer
180      * Levenberg-Marquardt optimizer}, there can be several evaluations at
181      * some iterations (typically for the first couple of iterations), so the
182      * maximum number of evaluations may be set to a higher value than the
183      * maximum number of iterations.
184      * </p>
185      * @param maxEvaluations maximum number of model evaluations
186      * @see #setMaxIterations(int)
187      * @see #getEvaluationsCount()
188      */
189     public void setMaxEvaluations(final int maxEvaluations) {
190         lsBuilder.maxEvaluations(maxEvaluations);
191     }
192 
193     /** Get the orbital parameters supported by this estimator.
194      * <p>
195      * If there are more than one propagator builder, then the names
196      * of the drivers have an index marker in square brackets appended
197      * to them in order to distinguish the various orbits. So for example
198      * with one builder generating Keplerian orbits the names would be
199      * simply "a", "e", "i"... but if there are several builders the
200      * names would be "a[0]", "e[0]", "i[0]"..."a[1]", "e[1]", "i[1]"...
201      * </p>
202      * @param estimatedOnly if true, only estimated parameters are returned
203      * @return orbital parameters supported by this estimator
204      */
205     public ParameterDriversList getOrbitalParametersDrivers(final boolean estimatedOnly) {
206 
207         final ParameterDriversList estimated = new ParameterDriversList();
208         for (int i = 0; i < builders.length; ++i) {
209             final String suffix = builders.length > 1 ? "[" + i + "]" : null;
210             for (final DelegatingDriver delegating : builders[i].getOrbitalParametersDrivers().getDrivers()) {
211                 if (delegating.isSelected() || !estimatedOnly) {
212                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
213                         if (suffix != null && !driver.getName().endsWith(suffix)) {
214                             // we add suffix only conditionally because the method may already have been called
215                             // and suffixes may have already been appended
216                             driver.setName(driver.getName() + suffix);
217                         }
218                         estimated.add(driver);
219                     }
220                 }
221             }
222         }
223         return estimated;
224 
225     }
226 
227     /** Get the propagator parameters supported by this estimator.
228      * @param estimatedOnly if true, only estimated parameters are returned
229      * @return propagator parameters supported by this estimator
230      */
231     public ParameterDriversList getPropagatorParametersDrivers(final boolean estimatedOnly) {
232 
233         final ParameterDriversList estimated = new ParameterDriversList();
234         for (PropagatorBuilder builder : builders) {
235             for (final DelegatingDriver delegating : builder.getPropagationParametersDrivers().getDrivers()) {
236                 if (delegating.isSelected() || !estimatedOnly) {
237                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
238                         estimated.add(driver);
239                     }
240                 }
241             }
242         }
243         return estimated;
244 
245     }
246 
247     /** Get the measurements parameters supported by this estimator (including measurements and modifiers).
248      * @param estimatedOnly if true, only estimated parameters are returned
249      * @return measurements parameters supported by this estimator
250      */
251     public ParameterDriversList getMeasurementsParametersDrivers(final boolean estimatedOnly) {
252 
253         final ParameterDriversList parameters =  new ParameterDriversList();
254         for (final  ObservedMeasurement<?> measurement : measurements) {
255             for (final ParameterDriver driver : measurement.getParametersDrivers()) {
256                 if (!estimatedOnly || driver.isSelected()) {
257                     parameters.add(driver);
258                 }
259             }
260         }
261 
262         parameters.sort();
263 
264         return parameters;
265 
266     }
267 
268     /**
269      * Set convergence threshold.
270      * <p>
271      * The convergence used for estimation is based on the estimated
272      * parameters {@link ParameterDriver#getNormalizedValue() normalized values}.
273      * Convergence is considered to have been reached when the difference
274      * between previous and current normalized value is less than the
275      * convergence threshold for all parameters. The same value is used
276      * for all parameters since they are normalized and hence dimensionless.
277      * </p>
278      * <p>
279      * Normalized values are computed as {@code (current - reference)/scale},
280      * so convergence is reached when the following condition holds for
281      * all estimated parameters:
282      * {@code |current[i] - previous[i]| <= threshold * scale[i]}
283      * </p>
284      * <p>
285      * So the convergence threshold specified here can be considered as
286      * a multiplication factor applied to scale. Since for all parameters
287      * the scale is often small (typically about 1 m for orbital positions
288      * for example), then the threshold should not be too small. A value
289      * of 10⁻³ is often quite accurate.
290      * </p>
291      * <p>
292      * Calling this method overrides any checker that could have been set
293      * beforehand by calling {@link #setConvergenceChecker(ConvergenceChecker)}.
294      * Both methods are mutually exclusive.
295      * </p>
296      *
297      * @param parametersConvergenceThreshold convergence threshold on
298      * normalized parameters (dimensionless, related to parameters scales)
299      * @see #setConvergenceChecker(ConvergenceChecker)
300      * @see EvaluationRmsChecker
301      */
302     public void setParametersConvergenceThreshold(final double parametersConvergenceThreshold) {
303         setConvergenceChecker((iteration, previous, current) ->
304                               current.getPoint().getLInfDistance(previous.getPoint()) <= parametersConvergenceThreshold);
305     }
306 
307     /** Set a custom convergence checker.
308      * <p>
309      * Calling this method overrides any checker that could have been set
310      * beforehand by calling {@link #setParametersConvergenceThreshold(double)}.
311      * Both methods are mutually exclusive.
312      * </p>
313      * @param convergenceChecker convergence checker to set
314      * @see #setParametersConvergenceThreshold(double)
315      * @since 10.1
316      */
317     public void setConvergenceChecker(final ConvergenceChecker<LeastSquaresProblem.Evaluation> convergenceChecker) {
318         this.convergenceChecker = convergenceChecker;
319     }
320 
321     /** Estimate the orbital, propagation and measurements parameters.
322      * <p>
323      * The initial guess for all parameters must have been set before calling this method
324      * using {@link #getOrbitalParametersDrivers(boolean)}, {@link #getPropagatorParametersDrivers(boolean)},
325      * and {@link #getMeasurementsParametersDrivers(boolean)} and then {@link ParameterDriver#setValue(double)
326      * setting the values} of the parameters.
327      * </p>
328      * <p>
329      * For parameters whose reference date has not been set to a non-null date beforehand (i.e.
330      * the parameters for which {@link ParameterDriver#getReferenceDate()} returns {@code null},
331      * a default reference date will be set automatically at the start of the estimation to the
332      * {@link AbstractPropagatorBuilder#getInitialOrbitDate() initial orbit date} of the first
333      * propagator builder. For parameters whose reference date has been set to a non-null date,
334      * this reference date is untouched.
335      * </p>
336      * <p>
337      * After this method returns, the estimated parameters can be retrieved using
338      * {@link #getOrbitalParametersDrivers(boolean)}, {@link #getPropagatorParametersDrivers(boolean)},
339      * and {@link #getMeasurementsParametersDrivers(boolean)} and then {@link ParameterDriver#getValue()
340      * getting the values} of the parameters.
341      * </p>
342      * <p>
343      * As a convenience, the method also returns a fully configured and ready to use
344      * propagator set up with all the estimated values.
345      * </p>
346      * <p>
347      * For even more in-depth information, the {@link #getOptimum()} method provides detailed
348      * elements (covariance matrix, estimated parameters standard deviation, weighted Jacobian, RMS,
349      * χ², residuals and more).
350      * </p>
351      * @return propagators configured with estimated orbits as initial states, and all
352      * propagators estimated parameters also set
353      */
354     public Propagator[] estimate() {
355 
356         // set reference date for all parameters that lack one (including the not estimated parameters)
357         for (final ParameterDriver driver : getOrbitalParametersDrivers(false).getDrivers()) {
358             if (driver.getReferenceDate() == null) {
359                 driver.setReferenceDate(builders[0].getInitialOrbitDate());
360             }
361         }
362         for (final ParameterDriver driver : getPropagatorParametersDrivers(false).getDrivers()) {
363             if (driver.getReferenceDate() == null) {
364                 driver.setReferenceDate(builders[0].getInitialOrbitDate());
365             }
366         }
367         for (final ParameterDriver driver : getMeasurementsParametersDrivers(false).getDrivers()) {
368             if (driver.getReferenceDate() == null) {
369                 driver.setReferenceDate(builders[0].getInitialOrbitDate());
370             }
371         }
372 
373         // get all estimated parameters
374         final ParameterDriversList estimatedOrbitalParameters      = getOrbitalParametersDrivers(true);
375         final ParameterDriversList estimatedPropagatorParameters   = getPropagatorParametersDrivers(true);
376         final ParameterDriversList estimatedMeasurementsParameters = getMeasurementsParametersDrivers(true);
377 
378         // create start point
379         final double[] start = new double[estimatedOrbitalParameters.getNbParams() +
380                                           estimatedPropagatorParameters.getNbParams() +
381                                           estimatedMeasurementsParameters.getNbParams()];
382         int iStart = 0;
383         for (final ParameterDriver driver : estimatedOrbitalParameters.getDrivers()) {
384             start[iStart++] = driver.getNormalizedValue();
385         }
386         for (final ParameterDriver driver : estimatedPropagatorParameters.getDrivers()) {
387             start[iStart++] = driver.getNormalizedValue();
388         }
389         for (final ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
390             start[iStart++] = driver.getNormalizedValue();
391         }
392         lsBuilder.start(start);
393 
394         // create target (which is an array set to 0, as we compute weighted residuals ourselves)
395         int p = 0;
396         for (final ObservedMeasurement<?> measurement : measurements) {
397             if (measurement.isEnabled()) {
398                 p += measurement.getDimension();
399             }
400         }
401         final double[] target = new double[p];
402         lsBuilder.target(target);
403 
404         // set up the model
405         final ModelObserver modelObserver = new ModelObserver() {
406             /** {@inheritDoc} */
407             @Override
408             public void modelCalled(final Orbit[] newOrbits,
409                                     final Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> newEstimations) {
410                 BatchLSEstimator.this.orbits      = newOrbits;
411                 BatchLSEstimator.this.estimations = newEstimations;
412             }
413         };
414         final AbstractBatchLSModel model = builders[0].buildLSModel(builders, measurements, estimatedMeasurementsParameters, modelObserver);
415 
416         lsBuilder.model(model);
417 
418         // add a validator for orbital parameters
419         lsBuilder.parameterValidator(new Validator(estimatedOrbitalParameters,
420                                                    estimatedPropagatorParameters,
421                                                    estimatedMeasurementsParameters));
422 
423         lsBuilder.checker(convergenceChecker);
424 
425         // set up the problem to solve
426         final LeastSquaresProblem problem = new TappedLSProblem(lsBuilder.build(),
427                                                                 model,
428                                                                 estimatedOrbitalParameters,
429                                                                 estimatedPropagatorParameters,
430                                                                 estimatedMeasurementsParameters);
431 
432         try {
433 
434             // solve the problem
435             optimum = optimizer.optimize(problem);
436 
437             // create a new configured propagator with all estimated parameters
438             return model.createPropagators(optimum.getPoint());
439 
440         } catch (MathRuntimeException mrte) {
441             throw new OrekitException(mrte);
442         }
443     }
444 
445     /** Get the last estimations performed.
446      * @return last estimations performed
447      */
448     public Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> getLastEstimations() {
449         return Collections.unmodifiableMap(estimations);
450     }
451 
452     /** Get the optimum found.
453      * <p>
454      * The {@link Optimum} object contains detailed elements (covariance matrix, estimated
455      * parameters standard deviation, weighted Jacobian, RMS, χ², residuals and more).
456      * </p>
457      * <p>
458      * Beware that the returned object is the raw view from the underlying mathematical
459      * library. At this raw level, parameters have {@link ParameterDriver#getNormalizedValue()
460      * normalized values} whereas the space flight parameters have {@link ParameterDriver#getValue()
461      * physical values} with their units. So there are {@link ParameterDriver#getScale() scaling
462      * factors} to apply when using these elements.
463      * </p>
464      * @return optimum found after last call to {@link #estimate()}
465      */
466     public Optimum getOptimum() {
467         return optimum;
468     }
469 
470     /** Get the covariances matrix in space flight dynamics physical units.
471      * <p>
472      * This method retrieve the {@link
473      * org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem.Evaluation#getCovariances(double)
474      * covariances} from the [@link {@link #getOptimum() optimum} and applies the scaling factors
475      * to it in order to convert it from raw normalized values back to physical values.
476      * </p>
477      * @param threshold threshold to identify matrix singularity
478      * @return covariances matrix in space flight dynamics physical units
479      * @since 9.1
480      */
481     public RealMatrix getPhysicalCovariances(final double threshold) {
482         final RealMatrix covariances;
483         try {
484             // get the normalized matrix
485             covariances = optimum.getCovariances(threshold).copy();
486         } catch (MathIllegalArgumentException miae) {
487             // the problem is singular
488             throw new OrekitException(miae);
489         }
490 
491         // retrieve the scaling factors
492         final double[] scale = new double[covariances.getRowDimension()];
493         int index = 0;
494         for (final ParameterDriver driver : getOrbitalParametersDrivers(true).getDrivers()) {
495             scale[index++] = driver.getScale();
496         }
497         for (final ParameterDriver driver : getPropagatorParametersDrivers(true).getDrivers()) {
498             scale[index++] = driver.getScale();
499         }
500         for (final ParameterDriver driver : getMeasurementsParametersDrivers(true).getDrivers()) {
501             scale[index++] = driver.getScale();
502         }
503 
504         // unnormalize the matrix, to retrieve physical covariances
505         for (int i = 0; i < covariances.getRowDimension(); ++i) {
506             for (int j = 0; j < covariances.getColumnDimension(); ++j) {
507                 covariances.setEntry(i, j, scale[i] * scale[j] * covariances.getEntry(i, j));
508             }
509         }
510 
511         return covariances;
512 
513     }
514 
515     /** Get the number of iterations used for last estimation.
516      * @return number of iterations used for last estimation
517      * @see #setMaxIterations(int)
518      */
519     public int getIterationsCount() {
520         return iterationsCounter.getCount();
521     }
522 
523     /** Get the number of evaluations used for last estimation.
524      * @return number of evaluations used for last estimation
525      * @see #setMaxEvaluations(int)
526      */
527     public int getEvaluationsCount() {
528         return evaluationsCounter.getCount();
529     }
530 
531     /** Wrapper used to tap the various counters. */
532     private class TappedLSProblem implements LeastSquaresProblem {
533 
534         /** Underlying problem. */
535         private final LeastSquaresProblem problem;
536 
537         /** Multivariate function model. */
538         private final AbstractBatchLSModel model;
539 
540         /** Estimated orbital parameters. */
541         private final ParameterDriversList estimatedOrbitalParameters;
542 
543         /** Estimated propagator parameters. */
544         private final ParameterDriversList estimatedPropagatorParameters;
545 
546         /** Estimated measurements parameters. */
547         private final ParameterDriversList estimatedMeasurementsParameters;
548 
549         /** Simple constructor.
550          * @param problem underlying problem
551          * @param model multivariate function model
552          * @param estimatedOrbitalParameters estimated orbital parameters
553          * @param estimatedPropagatorParameters estimated propagator parameters
554          * @param estimatedMeasurementsParameters estimated measurements parameters
555          */
556         TappedLSProblem(final LeastSquaresProblem problem,
557                         final AbstractBatchLSModel model,
558                         final ParameterDriversList estimatedOrbitalParameters,
559                         final ParameterDriversList estimatedPropagatorParameters,
560                         final ParameterDriversList estimatedMeasurementsParameters) {
561             this.problem                         = problem;
562             this.model                           = model;
563             this.estimatedOrbitalParameters      = estimatedOrbitalParameters;
564             this.estimatedPropagatorParameters   = estimatedPropagatorParameters;
565             this.estimatedMeasurementsParameters = estimatedMeasurementsParameters;
566         }
567 
568         /** {@inheritDoc} */
569         @Override
570         public Incrementor getEvaluationCounter() {
571             // tap the evaluations counter
572             BatchLSEstimator.this.evaluationsCounter = problem.getEvaluationCounter();
573             model.setEvaluationsCounter(BatchLSEstimator.this.evaluationsCounter);
574             return BatchLSEstimator.this.evaluationsCounter;
575         }
576 
577         /** {@inheritDoc} */
578         @Override
579         public Incrementor getIterationCounter() {
580             // tap the iterations counter
581             BatchLSEstimator.this.iterationsCounter = problem.getIterationCounter();
582             model.setIterationsCounter(BatchLSEstimator.this.iterationsCounter);
583             return BatchLSEstimator.this.iterationsCounter;
584         }
585 
586         /** {@inheritDoc} */
587         @Override
588         public ConvergenceChecker<Evaluation> getConvergenceChecker() {
589             return problem.getConvergenceChecker();
590         }
591 
592         /** {@inheritDoc} */
593         @Override
594         public RealVector getStart() {
595             return problem.getStart();
596         }
597 
598         /** {@inheritDoc} */
599         @Override
600         public int getObservationSize() {
601             return problem.getObservationSize();
602         }
603 
604         /** {@inheritDoc} */
605         @Override
606         public int getParameterSize() {
607             return problem.getParameterSize();
608         }
609 
610         /** {@inheritDoc} */
611         @Override
612         public Evaluation evaluate(final RealVector point) {
613 
614             // perform the evaluation
615             final Evaluation evaluation = problem.evaluate(point);
616 
617             // notify the observer
618             if (observer != null) {
619                 observer.evaluationPerformed(iterationsCounter.getCount(),
620                                              evaluationsCounter.getCount(),
621                                              orbits,
622                                              estimatedOrbitalParameters,
623                                              estimatedPropagatorParameters,
624                                              estimatedMeasurementsParameters,
625                                              new Provider(),
626                                              evaluation);
627             }
628 
629             return evaluation;
630 
631         }
632 
633     }
634 
635     /** Provider for evaluations. */
636     private class Provider implements EstimationsProvider {
637 
638         /** Sorted estimations. */
639         private EstimatedMeasurement<?>[] sortedEstimations;
640 
641         /** {@inheritDoc} */
642         @Override
643         public int getNumber() {
644             return estimations.size();
645         }
646 
647         /** {@inheritDoc} */
648         @Override
649         public EstimatedMeasurement<?> getEstimatedMeasurement(final int index) {
650 
651             // safety checks
652             if (index < 0 || index >= estimations.size()) {
653                 throw new OrekitException(LocalizedCoreFormats.OUT_OF_RANGE_SIMPLE,
654                                           index, 0, estimations.size());
655             }
656 
657             if (sortedEstimations == null) {
658 
659                 // lazy evaluation of the sorted array
660                 sortedEstimations = new EstimatedMeasurement<?>[estimations.size()];
661                 int i = 0;
662                 for (final Map.Entry<ObservedMeasurement<?>, EstimatedMeasurement<?>> entry : estimations.entrySet()) {
663                     sortedEstimations[i++] = entry.getValue();
664                 }
665 
666                 // sort the array, primarily chronologically
667                 Arrays.sort(sortedEstimations, 0, sortedEstimations.length, Comparator.naturalOrder());
668 
669             }
670 
671             return sortedEstimations[index];
672 
673         }
674 
675     }
676 
677     /** Validator for estimated parameters. */
678     private static class Validator implements ParameterValidator {
679 
680         /** Estimated orbital parameters. */
681         private final ParameterDriversList estimatedOrbitalParameters;
682 
683         /** Estimated propagator parameters. */
684         private final ParameterDriversList estimatedPropagatorParameters;
685 
686         /** Estimated measurements parameters. */
687         private final ParameterDriversList estimatedMeasurementsParameters;
688 
689         /** Simple constructor.
690          * @param estimatedOrbitalParameters estimated orbital parameters
691          * @param estimatedPropagatorParameters estimated propagator parameters
692          * @param estimatedMeasurementsParameters estimated measurements parameters
693          */
694         Validator(final ParameterDriversList estimatedOrbitalParameters,
695                   final ParameterDriversList estimatedPropagatorParameters,
696                   final ParameterDriversList estimatedMeasurementsParameters) {
697             this.estimatedOrbitalParameters      = estimatedOrbitalParameters;
698             this.estimatedPropagatorParameters   = estimatedPropagatorParameters;
699             this.estimatedMeasurementsParameters = estimatedMeasurementsParameters;
700         }
701 
702         /** {@inheritDoc} */
703         @Override
704         public RealVector validate(final RealVector params) {
705 
706             int i = 0;
707             for (final ParameterDriver driver : estimatedOrbitalParameters.getDrivers()) {
708                 // let the parameter handle min/max clipping
709                 driver.setNormalizedValue(params.getEntry(i));
710                 params.setEntry(i++, driver.getNormalizedValue());
711             }
712             for (final ParameterDriver driver : estimatedPropagatorParameters.getDrivers()) {
713                 // let the parameter handle min/max clipping
714                 driver.setNormalizedValue(params.getEntry(i));
715                 params.setEntry(i++, driver.getNormalizedValue());
716             }
717             for (final ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
718                 // let the parameter handle min/max clipping
719                 driver.setNormalizedValue(params.getEntry(i));
720                 params.setEntry(i++, driver.getNormalizedValue());
721             }
722 
723             return params;
724         }
725     }
726 
727 }