1   /* Copyright 2002-2026 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 org.hipparchus.linear.Array2DRowRealMatrix;
20  import org.hipparchus.linear.ArrayRealVector;
21  import org.hipparchus.linear.MatrixUtils;
22  import org.hipparchus.linear.RealMatrix;
23  import org.hipparchus.linear.RealVector;
24  import org.hipparchus.optim.nonlinear.vector.leastsquares.MultivariateJacobianFunction;
25  import org.hipparchus.util.FastMath;
26  import org.hipparchus.util.Incrementor;
27  import org.hipparchus.util.Pair;
28  import org.orekit.estimation.measurements.EstimatedMeasurement;
29  import org.orekit.estimation.measurements.EstimatedMeasurementBase;
30  import org.orekit.estimation.measurements.ObservedMeasurement;
31  import org.orekit.orbits.Orbit;
32  import org.orekit.propagation.MatricesHarvester;
33  import org.orekit.propagation.Propagator;
34  import org.orekit.propagation.PropagatorsParallelizer;
35  import org.orekit.propagation.SpacecraftState;
36  import org.orekit.propagation.conversion.PropagatorBuilder;
37  import org.orekit.propagation.sampling.MultiSatStepHandler;
38  import org.orekit.time.AbsoluteDate;
39  import org.orekit.time.ChronologicalComparator;
40  import org.orekit.utils.ParameterDriver;
41  import org.orekit.utils.ParameterDriversList;
42  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
43  import org.orekit.utils.TimeSpanMap;
44  import org.orekit.utils.TimeSpanMap.Span;
45  
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.Collections;
49  import java.util.HashMap;
50  import java.util.IdentityHashMap;
51  import java.util.List;
52  import java.util.Map;
53  
54  /** Bridge between {@link ObservedMeasurement measurements} and {@link
55   * org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem
56   * least squares problems}.
57   * @author Luc Maisonobe
58   * @author Bryan Cazabonne
59   * @author Thomas Paulet
60   * @author Melina Vanel
61   * @since 11.0
62   */
63  public abstract class AbstractBatchLSModel implements MultivariateJacobianFunction {
64  
65      /** Builders for propagators. */
66      private final PropagatorBuilder[] builders;
67  
68      /** Array of each builder's selected orbit drivers. Orbit drivers
69       * should have only 1 span on their value TimeSpanMap.
70       * @since 11.1
71       */
72      private final ParameterDriversList[] estimatedOrbitalParameters;
73  
74      /** Array of each builder's selected propagation drivers. */
75      private final ParameterDriversList[] estimatedPropagationParameters;
76  
77      /** Estimated measurements parameters. */
78      private final ParameterDriversList estimatedMeasurementsParameters;
79  
80      /** Measurements. */
81      private final List<ObservedMeasurement<?>> measurements;
82  
83      /** Start columns for each estimated orbit. */
84      private final int[] orbitsStartColumns;
85  
86      /** End columns for each estimated orbit. */
87      private final int[] orbitsEndColumns;
88  
89      /** Indirection array in measurements jacobians.
90       * @since 11.2
91       */
92      private final int[] orbitsJacobianColumns;
93  
94      /** Map for propagation parameters columns. */
95      private final Map<String, Integer> propagationParameterColumns;
96  
97      /** Map for measurements parameters columns. */
98      private final Map<String, Integer> measurementParameterColumns;
99  
100     /** Last evaluations. */
101     private final Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> evaluations;
102 
103     /** Observer to be notified at orbit changes. */
104     private final ModelObserver observer;
105 
106     /** Counter for the evaluations. */
107     private Incrementor evaluationsCounter;
108 
109     /** Counter for the iterations. */
110     private Incrementor iterationsCounter;
111 
112     /** Date of the first enabled measurement. */
113     private AbsoluteDate firstDate;
114 
115     /** Date of the last enabled measurement. */
116     private AbsoluteDate lastDate;
117 
118     /** Boolean indicating if the propagation will go forward or backward. */
119     private final boolean forwardPropagation;
120 
121     /** Model function value. */
122     private final RealVector value;
123 
124     /** Harvesters for extracting State Transition Matrices and Jacobians from integrated states.
125      * @since 11.1
126      */
127     private final MatricesHarvester[] harvesters;
128 
129     /** Initial states of the propagators built at the current iteration.
130      * <p>
131      * These are the states the builder parameters refer to, hence the states at which the
132      * {@link MatricesHarvester#getStateJacobianVsBuilderParameters(SpacecraftState) dY₀/dB₀} Jacobians must be
133      * evaluated.
134      * </p>
135      * @since 14.0
136      */
137     private final SpacecraftState[] initialStates;
138 
139     /** Model function Jacobian. */
140     private final RealMatrix jacobian;
141 
142     /**
143      * Constructor.
144      * @param propagatorBuilders builders to use for propagation
145      * @param measurements measurements
146      * @param estimatedMeasurementsParameters estimated measurements parameters
147      * @param observer observer to be notified at model calls
148      */
149     protected AbstractBatchLSModel(final PropagatorBuilder[] propagatorBuilders,
150                                    final List<ObservedMeasurement<?>> measurements,
151                                    final ParameterDriversList estimatedMeasurementsParameters,
152                                    final ModelObserver observer) {
153 
154         this.builders                        = propagatorBuilders.clone();
155         this.measurements                    = measurements;
156         this.estimatedMeasurementsParameters = estimatedMeasurementsParameters;
157         this.measurementParameterColumns     = new HashMap<>(estimatedMeasurementsParameters.getNbValuesToEstimate());
158         this.estimatedOrbitalParameters      = new ParameterDriversList[builders.length];
159         this.estimatedPropagationParameters  = new ParameterDriversList[builders.length];
160         this.evaluations                     = new IdentityHashMap<>(measurements.size());
161         this.observer                        = observer;
162         this.harvesters                      = new MatricesHarvester[builders.length];
163         this.initialStates                   = new SpacecraftState[builders.length];
164 
165         // allocate vector and matrix
166         int rows = 0;
167         for (final ObservedMeasurement<?> measurement : measurements) {
168             rows += measurement.getDimension();
169         }
170 
171         this.orbitsStartColumns    = new int[builders.length];
172         this.orbitsEndColumns      = new int[builders.length];
173         this.orbitsJacobianColumns = new int[builders.length * 6];
174         Arrays.fill(orbitsJacobianColumns, -1);
175         int columns = 0;
176         for (int i = 0; i < builders.length; ++i) {
177             this.orbitsStartColumns[i] = columns;
178             final List<ParameterDriversList.DelegatingDriver> orbitalParametersDrivers =
179                             builders[i].getOrbitalParameterFactory().getOrbitalParametersDrivers().getDrivers();
180             for (int j = 0; j < orbitalParametersDrivers.size(); ++j) {
181                 if (orbitalParametersDrivers.get(j).isSelected()) {
182                     orbitsJacobianColumns[columns] = j;
183                     ++columns;
184                 }
185             }
186             this.orbitsEndColumns[i] = columns;
187         }
188 
189         // Gather all the propagation drivers names in a list
190         final List<String> estimatedPropagationParametersNames = new ArrayList<>();
191         for (int i = 0; i < builders.length; ++i) {
192             // The index i in array estimatedPropagationParameters (attribute of the class) is populated
193             // when the first call to getSelectedPropagationDriversForBuilder(i) is made
194             for (final DelegatingDriver delegating : getSelectedPropagationDriversForBuilder(i).getDrivers()) {
195 
196                 final TimeSpanMap<String> delegatingNameSpanMap = delegating.getNamesSpanMap();
197                 // for each span (for each estimated value) corresponding name is added
198                 Span<String> currentNameSpan = delegatingNameSpanMap.getFirstSpan();
199                 // Add the driver name if it has not been added yet and the number of estimated values for this param
200                 if (!estimatedPropagationParametersNames.contains(currentNameSpan.getData())) {
201                     estimatedPropagationParametersNames.add(currentNameSpan.getData());
202                 }
203                 for (int spanNumber = 1; spanNumber < delegatingNameSpanMap.getSpansNumber(); ++spanNumber) {
204                     currentNameSpan = delegatingNameSpanMap.getSpan(currentNameSpan.getEnd());
205                     // Add the driver name if it has not been added yet and the number of estimated values for this param
206                     if (!estimatedPropagationParametersNames.contains(currentNameSpan.getData())) {
207                         estimatedPropagationParametersNames.add(currentNameSpan.getData());
208                     }
209                 }
210             }
211         }
212 
213         // Populate the map of propagation drivers' columns and update the total number of columns
214         propagationParameterColumns = new HashMap<>(estimatedPropagationParametersNames.size());
215         for (final String driverName : estimatedPropagationParametersNames) {
216             propagationParameterColumns.put(driverName, columns);
217             ++columns;
218         }
219         // Populate the map of measurement drivers' columns and update the total number of columns
220         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
221             for (Span<String> span = parameter.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
222                 measurementParameterColumns.put(span.getData(), columns);
223                 columns++;
224             }
225         }
226 
227         // Initialize point and value
228         value    = new ArrayRealVector(rows);
229         jacobian = MatrixUtils.createRealMatrix(rows, columns);
230 
231         // Decide whether the propagation will be done forward or backward.
232         // Minimize the duration between first measurement treated and orbit determination date
233         // Propagator builder number 0 holds the reference date for orbit determination
234         final AbsoluteDate refDate = builders[0].getOrbitalParameterFactory().getDate();
235 
236         // Sort the measurement list chronologically
237         measurements.sort(new ChronologicalComparator());
238         firstDate = measurements.getFirst().getDate();
239         lastDate  = measurements.getLast().getDate();
240 
241         // Decide the direction of propagation
242         forwardPropagation = FastMath.abs(refDate.durationFrom(firstDate)) <= FastMath.abs(refDate.durationFrom(lastDate));
243     }
244 
245     /** Set the counter for evaluations.
246      * @param evaluationsCounter counter for evaluations
247      */
248     public void setEvaluationsCounter(final Incrementor evaluationsCounter) {
249         this.evaluationsCounter = evaluationsCounter;
250     }
251 
252     /** Set the counter for iterations.
253      * @param iterationsCounter counter for iterations
254      */
255     public void setIterationsCounter(final Incrementor iterationsCounter) {
256         this.iterationsCounter = iterationsCounter;
257     }
258 
259     /** Return the forward propagation flag.
260      * @return the forward propagation flag
261      */
262     public boolean isForwardPropagation() {
263         return forwardPropagation;
264     }
265 
266     /** Configure the propagator to compute derivatives.
267      * @param propagator {@link Propagator} to configure
268      * @return harvester harvester to retrive the State Transition Matrix and Jacobian Matrix
269      */
270     protected abstract MatricesHarvester configureHarvester(Propagator propagator);
271 
272     /** Configure the current estimated orbits.
273      * <p>
274      * For DSST orbit determination, short period derivatives are also calculated.
275      * </p>
276      * @param harvester harvester for matrices
277      * @param propagator the orbit propagator
278      * @return the current estimated orbits
279      */
280     protected abstract Orbit configureOrbits(MatricesHarvester harvester, Propagator propagator);
281 
282     /** {@inheritDoc} */
283     @Override
284     public Pair<RealVector, RealMatrix> value(final RealVector point) {
285 
286         // Set up the propagators parallelizer
287         final Propagator[] propagators = createPropagators(point);
288         final Orbit[] orbits = new Orbit[propagators.length];
289         for (int i = 0; i < propagators.length; ++i) {
290             harvesters[i] = configureHarvester(propagators[i]);
291             orbits[i]     = configureOrbits(harvesters[i], propagators[i]);
292             // base state is used here on purpose: retrieving the full initial state would
293             // trigger the additional data providers, hence the harvester derivatives cache
294             initialStates[i] = propagators[i].getBaseInitialState();
295         }
296         final PropagatorsParallelizer parallelizer =
297                         new PropagatorsParallelizer(Arrays.asList(propagators), configureMeasurements(point));
298 
299         // Reset value and Jacobian
300         evaluations.clear();
301         value.set(0.0);
302         for (int i = 0; i < jacobian.getRowDimension(); ++i) {
303             for (int j = 0; j < jacobian.getColumnDimension(); ++j) {
304                 jacobian.setEntry(i, j, 0.0);
305             }
306         }
307 
308         // Run the propagation, gathering residuals on the fly
309         if (isForwardPropagation()) {
310             // Propagate forward from firstDate
311             parallelizer.propagate(firstDate.shiftedBy(-1.0), lastDate.shiftedBy(+1.0));
312         } else {
313             // Propagate backward from lastDate
314             parallelizer.propagate(lastDate.shiftedBy(+1.0), firstDate.shiftedBy(-1.0));
315         }
316 
317         observer.modelCalled(orbits, evaluations);
318 
319         return new Pair<>(value, jacobian);
320 
321     }
322 
323     /** Get the selected orbital drivers for a propagatorBuilder.
324      * @param iBuilder index of the builder in the builders' array
325      * @return the list of selected orbital drivers for propagatorBuilder of index iBuilder
326      * @since 11.1
327      */
328     public ParameterDriversList getSelectedOrbitalParametersDriversForBuilder(final int iBuilder) {
329 
330         // Lazy evaluation, create the list only if it hasn't been created yet
331         if (estimatedOrbitalParameters[iBuilder] == null) {
332 
333             // Gather the drivers
334             final ParameterDriversList drivers = builders[iBuilder].
335                                                  getOrbitalParameterFactory().
336                                                  getOrbitalParametersDrivers();
337             final ParameterDriversList selectedOrbitalDrivers = new ParameterDriversList();
338             for (final DelegatingDriver delegating : drivers.getDrivers()) {
339                 if (delegating.isSelected()) {
340                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
341                         selectedOrbitalDrivers.add(driver);
342                     }
343                 }
344             }
345 
346             // Add the list of selected orbital parameters drivers to the array
347             estimatedOrbitalParameters[iBuilder] = selectedOrbitalDrivers;
348         }
349         return estimatedOrbitalParameters[iBuilder];
350     }
351 
352     /** Get the selected propagation drivers for a propagatorBuilder.
353      * @param iBuilder index of the builder in the builders' array
354      * @return the list of selected propagation drivers for propagatorBuilder of index iBuilder
355      */
356     public ParameterDriversList getSelectedPropagationDriversForBuilder(final int iBuilder) {
357 
358         // Lazy evaluation, create the list only if it hasn't been created yet
359         if (estimatedPropagationParameters[iBuilder] == null) {
360 
361             // Gather the drivers
362             final ParameterDriversList selectedPropagationDrivers = new ParameterDriversList();
363             for (final DelegatingDriver delegating : builders[iBuilder].getPropagationParametersDrivers().getDrivers()) {
364                 if (delegating.isSelected()) {
365                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
366                         selectedPropagationDrivers.add(driver);
367                     }
368                 }
369             }
370 
371             // List of propagation drivers are sorted in the BatchLSEstimator class.
372             // Hence we need to sort this list so the parameters' indexes match
373             selectedPropagationDrivers.sort();
374 
375             // Add the list of selected propagation drivers to the array
376             estimatedPropagationParameters[iBuilder] = selectedPropagationDrivers;
377         }
378         return estimatedPropagationParameters[iBuilder];
379     }
380 
381     /** Create the propagators and parameters corresponding to an evaluation point.
382      * @param point evaluation point
383      * @return an array of new propagators
384      */
385     public Propagator[] createPropagators(final RealVector point) {
386 
387         final Propagator[] propagators = new Propagator[builders.length];
388 
389 
390         // Set up the propagators
391         for (int i = 0; i < builders.length; ++i) {
392 
393             int element = 0;
394             // Get the number of values to estimate for selected orbital drivers in the builder
395             final int nbOrb    = orbitsEndColumns[i] - orbitsStartColumns[i];
396 
397             // Get the list of selected propagation drivers in the builder and its size
398             final ParameterDriversList selectedPropagationDrivers = getSelectedPropagationDriversForBuilder(i);
399             final int nbParams = selectedPropagationDrivers.getNbParams();
400             final int nbValuesToEstimate = selectedPropagationDrivers.getNbValuesToEstimate();
401 
402             // Init the array of normalized parameters for the builder
403             final double[] propagatorArray = new double[nbOrb + nbValuesToEstimate];
404 
405             // Add the orbital drivers normalized values
406             for (int j = 0; j < nbOrb; ++j) {
407                 propagatorArray[element++] = point.getEntry(orbitsStartColumns[i] + j);
408             }
409 
410             // Add the propagation drivers normalized values
411             for (int j = 0; j < nbParams; ++j) {
412                 final DelegatingDriver driver = selectedPropagationDrivers.getDrivers().get(j);
413                 final TimeSpanMap<String> delegatingNameSpanMap = driver.getNamesSpanMap();
414                 // get point entry for each span (for each estimated value), point is sorted
415                 // with following parameters values and for each parameter driver
416                 // span value are sorted in chronological order
417                 Span<String> currentNameSpan = delegatingNameSpanMap.getFirstSpan();
418                 propagatorArray[element++] = point.getEntry(propagationParameterColumns.get(currentNameSpan.getData()));
419 
420                 for (int spanNumber = 1; spanNumber < delegatingNameSpanMap.getSpansNumber(); ++spanNumber) {
421                     currentNameSpan = delegatingNameSpanMap.getSpan(currentNameSpan.getEnd());
422                     propagatorArray[element++] = point.getEntry(propagationParameterColumns.get(currentNameSpan.getData()));
423 
424                 }
425             }
426 
427             // Build the propagator
428             propagators[i] = builders[i].buildPropagator(propagatorArray);
429         }
430 
431         return propagators;
432 
433     }
434 
435     /** Fetch a measurement that was evaluated during propagation.
436      * @param index index of the measurement first component
437      * @param evaluation measurement evaluation
438      */
439     public void fetchEvaluatedMeasurement(final int index, final EstimatedMeasurement<?> evaluation) {
440 
441         // States and observed measurement
442         final SpacecraftState[]      evaluationStates    = evaluation.getStates();
443         final ObservedMeasurement<?> observedMeasurement = evaluation.getObservedMeasurement();
444 
445         // compute weighted residuals
446         evaluations.put(observedMeasurement, evaluation);
447         if (evaluation.getStatus() == EstimatedMeasurementBase.Status.REJECTED) {
448             return;
449         }
450 
451         final double[] evaluated = evaluation.getEstimatedValue();
452         final double[] observed  = observedMeasurement.getObservedValue();
453         final double[] sigma     = observedMeasurement.getTheoreticalStandardDeviation();
454         final double[] weight    = evaluation.getObservedMeasurement().getBaseWeight();
455         for (int i = 0; i < evaluated.length; ++i) {
456             value.setEntry(index + i, weight[i] * (evaluated[i] - observed[i]) / sigma[i]);
457         }
458 
459         for (int k = 0; k < evaluationStates.length; ++k) {
460 
461             final int p = observedMeasurement.getSatellites().get(k).getPropagatorIndex();
462 
463             // partial derivatives of the current Cartesian coordinates with respect to current orbital state
464             final double[][] aCY = new double[6][6];
465             final Orbit currentOrbit = evaluationStates[k].getOrbit();
466             currentOrbit.getJacobianWrtParameters(builders[p].getOrbitalParameterFactory().getPositionAngleType(),
467                                                   aCY);
468             final RealMatrix dCdY = new Array2DRowRealMatrix(aCY, false);
469 
470             // Jacobian of the measurement with respect to current orbital state
471             final RealMatrix dMdC = new Array2DRowRealMatrix(evaluation.getStateDerivatives(k), false);
472             final RealMatrix dMdY = dMdC.multiply(dCdY);
473 
474             // Jacobian of the measurement with respect to initial orbital state
475             final ParameterDriversList selectedOrbitalDrivers = getSelectedOrbitalParametersDriversForBuilder(p);
476             final int nbOrbParams = selectedOrbitalDrivers.getNbParams();
477             if (nbOrbParams > 0) {
478                 RealMatrix dYdY0 = harvesters[p].getStateTransitionMatrix(evaluationStates[k]);
479                 if (dYdY0.getRowDimension() == 7) {
480                     // mass was included in STM propagation, removed it now
481                     dYdY0 = dYdY0.getSubMatrix(0, 5, 0, 5);
482                 }
483                 final RealMatrix dMdY0  = dMdY.multiply(dYdY0);
484                 final RealMatrix dY0dB0 = harvesters[p].getStateJacobianVsBuilderParameters(initialStates[p]);
485                 final RealMatrix dMdB0  = dY0dB0 == null ? dMdY0 : dMdY0.multiply(dY0dB0);
486                 for (int i = 0; i < dMdB0.getRowDimension(); ++i) {
487                     for (int j = orbitsStartColumns[p]; j < orbitsEndColumns[p]; ++j) {
488                         final ParameterDriver driver =
489                                         selectedOrbitalDrivers.getDrivers().get(j - orbitsStartColumns[p]);
490                         final double partial = dMdB0.getEntry(i, orbitsJacobianColumns[j]);
491                         jacobian.setEntry(index + i, j,
492                                           weight[i] * partial / sigma[i] * driver.getScale());
493                     }
494                 }
495             }
496 
497             // Jacobian of the measurement with respect to propagation parameters
498             final ParameterDriversList selectedPropagationDrivers = getSelectedPropagationDriversForBuilder(p);
499             final int nbParams = selectedPropagationDrivers.getNbParams();
500             if (nbParams > 0) {
501                 RealMatrix dYdPp = harvesters[p].getParametersJacobian(evaluationStates[k]);
502                 if (dYdPp.getRowDimension() == 7) {
503                     // mass was included in STM propagation, removed it now
504                     dYdPp = dYdPp.getSubMatrix(0, 5, 0, dYdPp.getColumnDimension() - 1);
505                 }
506                 final RealMatrix dMdPp = dMdY.multiply(dYdPp);
507 
508                 for (int i = 0; i < dMdPp.getRowDimension(); ++i) {
509                     int col = 0;
510 
511                     // Add the propagation drivers normalized values
512                     for (int j = 0; j < nbParams; ++j) {
513                         final ParameterDriver delegating = selectedPropagationDrivers.getDrivers().get(j);
514                         final TimeSpanMap<String> delegatingNameSpanMap = delegating.getNamesSpanMap();
515                         // get point entry for each span (for each estimated value), point is sorted
516                         for (Span<String> currentNameSpan = delegatingNameSpanMap.getFirstSpan(); currentNameSpan != null; currentNameSpan = currentNameSpan.next()) {
517                             jacobian.addToEntry(index + i, propagationParameterColumns.get(currentNameSpan.getData()),
518                                     weight[i] * dMdPp.getEntry(i, col++) / sigma[i] * delegating.getScale());
519                         }
520                     }
521                 }
522             }
523         }
524         // Jacobian of the measurement with respect to measurements parameters
525         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
526             if (driver.isSelected()) {
527                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
528                     final double[] aMPm = evaluation.getParameterDerivatives(driver, span.getStart());
529                     for (int i = 0; i < aMPm.length; ++i) {
530                         jacobian.setEntry(index + i, measurementParameterColumns.get(span.getData()),
531                                           weight[i] * aMPm[i] / sigma[i] * driver.getScale());
532                     }
533                 }
534             }
535         }
536 
537     }
538 
539     /** Configure the multi-satellites handler to handle measurements.
540      * @param point evaluation point
541      * @return multi-satellites handler to handle measurements
542      */
543     private MultiSatStepHandler configureMeasurements(final RealVector point) {
544 
545         // Set up the measurement parameters
546         int index = orbitsEndColumns[builders.length - 1] + propagationParameterColumns.size();
547         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
548 
549             for (Span<Double> span = parameter.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
550                 parameter.setNormalizedValue(point.getEntry(index++), span.getStart());
551             }
552         }
553 
554         // Set up measurements handler
555         final List<PreCompensation> precompensated = new ArrayList<>();
556         for (final ObservedMeasurement<?> measurement : measurements) {
557             if (measurement.isEnabled()) {
558                 precompensated.add(new PreCompensation(measurement, evaluations.get(measurement)));
559             }
560         }
561         precompensated.sort(new ChronologicalComparator());
562 
563         // Assign first and last date
564         firstDate = precompensated.getFirst().getDate();
565         lastDate  = precompensated.getLast().getDate();
566 
567         // Reverse the list in case of backward propagation
568         if (!forwardPropagation) {
569             Collections.reverse(precompensated);
570         }
571 
572         return new MeasurementHandler(this, precompensated);
573 
574     }
575 
576     /** Get the iterations count.
577      * @return iterations count
578      */
579     public int getIterationsCount() {
580         return iterationsCounter.getCount();
581     }
582 
583     /** Get the evaluations count.
584      * @return evaluations count
585      */
586     public int getEvaluationsCount() {
587         return evaluationsCounter.getCount();
588     }
589 
590 }