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  
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     /** Model function Jacobian. */
130     private final RealMatrix jacobian;
131 
132     /**
133      * Constructor.
134      * @param propagatorBuilders builders to use for propagation
135      * @param measurements measurements
136      * @param estimatedMeasurementsParameters estimated measurements parameters
137      * @param observer observer to be notified at model calls
138      */
139     protected AbstractBatchLSModel(final PropagatorBuilder[] propagatorBuilders,
140                                    final List<ObservedMeasurement<?>> measurements,
141                                    final ParameterDriversList estimatedMeasurementsParameters,
142                                    final ModelObserver observer) {
143 
144         this.builders                        = propagatorBuilders.clone();
145         this.measurements                    = measurements;
146         this.estimatedMeasurementsParameters = estimatedMeasurementsParameters;
147         this.measurementParameterColumns     = new HashMap<>(estimatedMeasurementsParameters.getNbValuesToEstimate());
148         this.estimatedOrbitalParameters      = new ParameterDriversList[builders.length];
149         this.estimatedPropagationParameters  = new ParameterDriversList[builders.length];
150         this.evaluations                     = new IdentityHashMap<>(measurements.size());
151         this.observer                        = observer;
152         this.harvesters                      = new MatricesHarvester[builders.length];
153 
154         // allocate vector and matrix
155         int rows = 0;
156         for (final ObservedMeasurement<?> measurement : measurements) {
157             rows += measurement.getDimension();
158         }
159 
160         this.orbitsStartColumns    = new int[builders.length];
161         this.orbitsEndColumns      = new int[builders.length];
162         this.orbitsJacobianColumns = new int[builders.length * 6];
163         Arrays.fill(orbitsJacobianColumns, -1);
164         int columns = 0;
165         for (int i = 0; i < builders.length; ++i) {
166             this.orbitsStartColumns[i] = columns;
167             final List<ParameterDriversList.DelegatingDriver> orbitalParametersDrivers =
168                             builders[i].getOrbitalParametersDrivers().getDrivers();
169             for (int j = 0; j < orbitalParametersDrivers.size(); ++j) {
170                 if (orbitalParametersDrivers.get(j).isSelected()) {
171                     orbitsJacobianColumns[columns] = j;
172                     ++columns;
173                 }
174             }
175             this.orbitsEndColumns[i] = columns;
176         }
177 
178         // Gather all the propagation drivers names in a list
179         final List<String> estimatedPropagationParametersNames = new ArrayList<>();
180         for (int i = 0; i < builders.length; ++i) {
181             // The index i in array estimatedPropagationParameters (attribute of the class) is populated
182             // when the first call to getSelectedPropagationDriversForBuilder(i) is made
183             for (final DelegatingDriver delegating : getSelectedPropagationDriversForBuilder(i).getDrivers()) {
184 
185                 final TimeSpanMap<String> delegatingNameSpanMap = delegating.getNamesSpanMap();
186                 // for each span (for each estimated value) corresponding name is added
187                 Span<String> currentNameSpan = delegatingNameSpanMap.getFirstSpan();
188                 // Add the driver name if it has not been added yet and the number of estimated values for this param
189                 if (!estimatedPropagationParametersNames.contains(currentNameSpan.getData())) {
190                     estimatedPropagationParametersNames.add(currentNameSpan.getData());
191                 }
192                 for (int spanNumber = 1; spanNumber < delegatingNameSpanMap.getSpansNumber(); ++spanNumber) {
193                     currentNameSpan = delegatingNameSpanMap.getSpan(currentNameSpan.getEnd());
194                     // Add the driver name if it has not been added yet and the number of estimated values for this param
195                     if (!estimatedPropagationParametersNames.contains(currentNameSpan.getData())) {
196                         estimatedPropagationParametersNames.add(currentNameSpan.getData());
197                     }
198                 }
199             }
200         }
201 
202         // Populate the map of propagation drivers' columns and update the total number of columns
203         propagationParameterColumns = new HashMap<>(estimatedPropagationParametersNames.size());
204         for (final String driverName : estimatedPropagationParametersNames) {
205             propagationParameterColumns.put(driverName, columns);
206             ++columns;
207         }
208         // Populate the map of measurement drivers' columns and update the total number of columns
209         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
210             for (Span<String> span = parameter.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
211                 measurementParameterColumns.put(span.getData(), columns);
212                 columns++;
213             }
214         }
215 
216         // Initialize point and value
217         value    = new ArrayRealVector(rows);
218         jacobian = MatrixUtils.createRealMatrix(rows, columns);
219 
220         // Decide whether the propagation will be done forward or backward.
221         // Minimize the duration between first measurement treated and orbit determination date
222         // Propagator builder number 0 holds the reference date for orbit determination
223         final AbsoluteDate refDate = builders[0].getInitialOrbitDate();
224 
225         // Sort the measurement list chronologically
226         measurements.sort(new ChronologicalComparator());
227         firstDate = measurements.get(0).getDate();
228         lastDate  = measurements.get(measurements.size() - 1).getDate();
229 
230         // Decide the direction of propagation
231         forwardPropagation = FastMath.abs(refDate.durationFrom(firstDate)) <= FastMath.abs(refDate.durationFrom(lastDate));
232     }
233 
234     /** Set the counter for evaluations.
235      * @param evaluationsCounter counter for evaluations
236      */
237     public void setEvaluationsCounter(final Incrementor evaluationsCounter) {
238         this.evaluationsCounter = evaluationsCounter;
239     }
240 
241     /** Set the counter for iterations.
242      * @param iterationsCounter counter for iterations
243      */
244     public void setIterationsCounter(final Incrementor iterationsCounter) {
245         this.iterationsCounter = iterationsCounter;
246     }
247 
248     /** Return the forward propagation flag.
249      * @return the forward propagation flag
250      */
251     public boolean isForwardPropagation() {
252         return forwardPropagation;
253     }
254 
255     /** Configure the propagator to compute derivatives.
256      * @param propagator {@link Propagator} to configure
257      * @return harvester harvester to retrive the State Transition Matrix and Jacobian Matrix
258      */
259     protected abstract MatricesHarvester configureHarvester(Propagator propagator);
260 
261     /** Configure the current estimated orbits.
262      * <p>
263      * For DSST orbit determination, short period derivatives are also calculated.
264      * </p>
265      * @param harvester harvester for matrices
266      * @param propagator the orbit propagator
267      * @return the current estimated orbits
268      */
269     protected abstract Orbit configureOrbits(MatricesHarvester harvester, Propagator propagator);
270 
271     /** {@inheritDoc} */
272     @Override
273     public Pair<RealVector, RealMatrix> value(final RealVector point) {
274 
275         // Set up the propagators parallelizer
276         final Propagator[] propagators = createPropagators(point);
277         final Orbit[] orbits = new Orbit[propagators.length];
278         for (int i = 0; i < propagators.length; ++i) {
279             harvesters[i] = configureHarvester(propagators[i]);
280             orbits[i]     = configureOrbits(harvesters[i], propagators[i]);
281         }
282         final PropagatorsParallelizer parallelizer =
283                         new PropagatorsParallelizer(Arrays.asList(propagators), configureMeasurements(point));
284 
285         // Reset value and Jacobian
286         evaluations.clear();
287         value.set(0.0);
288         for (int i = 0; i < jacobian.getRowDimension(); ++i) {
289             for (int j = 0; j < jacobian.getColumnDimension(); ++j) {
290                 jacobian.setEntry(i, j, 0.0);
291             }
292         }
293 
294         // Run the propagation, gathering residuals on the fly
295         if (isForwardPropagation()) {
296             // Propagate forward from firstDate
297             parallelizer.propagate(firstDate.shiftedBy(-1.0), lastDate.shiftedBy(+1.0));
298         } else {
299             // Propagate backward from lastDate
300             parallelizer.propagate(lastDate.shiftedBy(+1.0), firstDate.shiftedBy(-1.0));
301         }
302 
303         observer.modelCalled(orbits, evaluations);
304 
305         return new Pair<>(value, jacobian);
306 
307     }
308 
309     /** Get the selected orbital drivers for a propagatorBuilder.
310      * @param iBuilder index of the builder in the builders' array
311      * @return the list of selected orbital drivers for propagatorBuilder of index iBuilder
312      * @since 11.1
313      */
314     public ParameterDriversList getSelectedOrbitalParametersDriversForBuilder(final int iBuilder) {
315 
316         // Lazy evaluation, create the list only if it hasn't been created yet
317         if (estimatedOrbitalParameters[iBuilder] == null) {
318 
319             // Gather the drivers
320             final ParameterDriversList selectedOrbitalDrivers = new ParameterDriversList();
321             for (final DelegatingDriver delegating : builders[iBuilder].getOrbitalParametersDrivers().getDrivers()) {
322                 if (delegating.isSelected()) {
323                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
324                         selectedOrbitalDrivers.add(driver);
325                     }
326                 }
327             }
328 
329             // Add the list of selected orbital parameters drivers to the array
330             estimatedOrbitalParameters[iBuilder] = selectedOrbitalDrivers;
331         }
332         return estimatedOrbitalParameters[iBuilder];
333     }
334 
335     /** Get the selected propagation drivers for a propagatorBuilder.
336      * @param iBuilder index of the builder in the builders' array
337      * @return the list of selected propagation drivers for propagatorBuilder of index iBuilder
338      */
339     public ParameterDriversList getSelectedPropagationDriversForBuilder(final int iBuilder) {
340 
341         // Lazy evaluation, create the list only if it hasn't been created yet
342         if (estimatedPropagationParameters[iBuilder] == null) {
343 
344             // Gather the drivers
345             final ParameterDriversList selectedPropagationDrivers = new ParameterDriversList();
346             for (final DelegatingDriver delegating : builders[iBuilder].getPropagationParametersDrivers().getDrivers()) {
347                 if (delegating.isSelected()) {
348                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
349                         selectedPropagationDrivers.add(driver);
350                     }
351                 }
352             }
353 
354             // List of propagation drivers are sorted in the BatchLSEstimator class.
355             // Hence we need to sort this list so the parameters' indexes match
356             selectedPropagationDrivers.sort();
357 
358             // Add the list of selected propagation drivers to the array
359             estimatedPropagationParameters[iBuilder] = selectedPropagationDrivers;
360         }
361         return estimatedPropagationParameters[iBuilder];
362     }
363 
364     /** Create the propagators and parameters corresponding to an evaluation point.
365      * @param point evaluation point
366      * @return an array of new propagators
367      */
368     public Propagator[] createPropagators(final RealVector point) {
369 
370         final Propagator[] propagators = new Propagator[builders.length];
371 
372 
373         // Set up the propagators
374         for (int i = 0; i < builders.length; ++i) {
375 
376             int element = 0;
377             // Get the number of values to estimate for selected orbital drivers in the builder
378             final int nbOrb    = orbitsEndColumns[i] - orbitsStartColumns[i];
379 
380             // Get the list of selected propagation drivers in the builder and its size
381             final ParameterDriversList selectedPropagationDrivers = getSelectedPropagationDriversForBuilder(i);
382             final int nbParams = selectedPropagationDrivers.getNbParams();
383             final int nbValuesToEstimate = selectedPropagationDrivers.getNbValuesToEstimate();
384 
385             // Init the array of normalized parameters for the builder
386             final double[] propagatorArray = new double[nbOrb + nbValuesToEstimate];
387 
388             // Add the orbital drivers normalized values
389             for (int j = 0; j < nbOrb; ++j) {
390                 propagatorArray[element++] = point.getEntry(orbitsStartColumns[i] + j);
391             }
392 
393             // Add the propagation drivers normalized values
394             for (int j = 0; j < nbParams; ++j) {
395                 final DelegatingDriver driver = selectedPropagationDrivers.getDrivers().get(j);
396                 final TimeSpanMap<String> delegatingNameSpanMap = driver.getNamesSpanMap();
397                 // get point entry for each span (for each estimated value), point is sorted
398                 // with following parameters values and for each parameter driver
399                 // span value are sorted in chronological order
400                 Span<String> currentNameSpan = delegatingNameSpanMap.getFirstSpan();
401                 propagatorArray[element++] = point.getEntry(propagationParameterColumns.get(currentNameSpan.getData()));
402 
403                 for (int spanNumber = 1; spanNumber < delegatingNameSpanMap.getSpansNumber(); ++spanNumber) {
404                     currentNameSpan = delegatingNameSpanMap.getSpan(currentNameSpan.getEnd());
405                     propagatorArray[element++] = point.getEntry(propagationParameterColumns.get(currentNameSpan.getData()));
406 
407                 }
408             }
409 
410             // Build the propagator
411             propagators[i] = builders[i].buildPropagator(propagatorArray);
412         }
413 
414         return propagators;
415 
416     }
417 
418     /** Fetch a measurement that was evaluated during propagation.
419      * @param index index of the measurement first component
420      * @param evaluation measurement evaluation
421      */
422     public void fetchEvaluatedMeasurement(final int index, final EstimatedMeasurement<?> evaluation) {
423 
424         // States and observed measurement
425         final SpacecraftState[]      evaluationStates    = evaluation.getStates();
426         final ObservedMeasurement<?> observedMeasurement = evaluation.getObservedMeasurement();
427 
428         // compute weighted residuals
429         evaluations.put(observedMeasurement, evaluation);
430         if (evaluation.getStatus() == EstimatedMeasurementBase.Status.REJECTED) {
431             return;
432         }
433 
434         final double[] evaluated = evaluation.getEstimatedValue();
435         final double[] observed  = observedMeasurement.getObservedValue();
436         final double[] sigma     = observedMeasurement.getTheoreticalStandardDeviation();
437         final double[] weight    = evaluation.getObservedMeasurement().getBaseWeight();
438         for (int i = 0; i < evaluated.length; ++i) {
439             value.setEntry(index + i, weight[i] * (evaluated[i] - observed[i]) / sigma[i]);
440         }
441 
442         for (int k = 0; k < evaluationStates.length; ++k) {
443 
444             final int p = observedMeasurement.getSatellites().get(k).getPropagatorIndex();
445 
446             // partial derivatives of the current Cartesian coordinates with respect to current orbital state
447             final double[][] aCY = new double[6][6];
448             final Orbit currentOrbit = evaluationStates[k].getOrbit();
449             currentOrbit.getJacobianWrtParameters(builders[p].getPositionAngleType(), aCY);
450             final RealMatrix dCdY = new Array2DRowRealMatrix(aCY, false);
451 
452             // Jacobian of the measurement with respect to current orbital state
453             final RealMatrix dMdC = new Array2DRowRealMatrix(evaluation.getStateDerivatives(k), false);
454             final RealMatrix dMdY = dMdC.multiply(dCdY);
455 
456             // Jacobian of the measurement with respect to initial orbital state
457             final ParameterDriversList selectedOrbitalDrivers = getSelectedOrbitalParametersDriversForBuilder(p);
458             final int nbOrbParams = selectedOrbitalDrivers.getNbParams();
459             if (nbOrbParams > 0) {
460                 RealMatrix dYdY0 = harvesters[p].getStateTransitionMatrix(evaluationStates[k]);
461                 if (dYdY0.getRowDimension() == 7) {
462                     // mass was included in STM propagation, removed it now
463                     dYdY0 = dYdY0.getSubMatrix(0, 5, 0, 5);
464                 }
465                 final RealMatrix dMdY0 = dMdY.multiply(dYdY0);
466                 for (int i = 0; i < dMdY0.getRowDimension(); ++i) {
467                     for (int j = orbitsStartColumns[p]; j < orbitsEndColumns[p]; ++j) {
468                         final ParameterDriver driver =
469                                         selectedOrbitalDrivers.getDrivers().get(j - orbitsStartColumns[p]);
470                         final double partial = dMdY0.getEntry(i, orbitsJacobianColumns[j]);
471                         jacobian.setEntry(index + i, j,
472                                           weight[i] * partial / sigma[i] * driver.getScale());
473                     }
474                 }
475             }
476 
477             // Jacobian of the measurement with respect to propagation parameters
478             final ParameterDriversList selectedPropagationDrivers = getSelectedPropagationDriversForBuilder(p);
479             final int nbParams = selectedPropagationDrivers.getNbParams();
480             if (nbParams > 0) {
481                 RealMatrix dYdPp = harvesters[p].getParametersJacobian(evaluationStates[k]);
482                 if (dYdPp.getRowDimension() == 7) {
483                     // mass was included in STM propagation, removed it now
484                     dYdPp = dYdPp.getSubMatrix(0, 5, 0, dYdPp.getColumnDimension() - 1);
485                 }
486                 final RealMatrix dMdPp = dMdY.multiply(dYdPp);
487 
488                 for (int i = 0; i < dMdPp.getRowDimension(); ++i) {
489                     int col = 0;
490 
491                     // Add the propagation drivers normalized values
492                     for (int j = 0; j < nbParams; ++j) {
493                         final ParameterDriver delegating = selectedPropagationDrivers.getDrivers().get(j);
494                         final TimeSpanMap<String> delegatingNameSpanMap = delegating.getNamesSpanMap();
495                         // get point entry for each span (for each estimated value), point is sorted
496                         for (Span<String> currentNameSpan = delegatingNameSpanMap.getFirstSpan(); currentNameSpan != null; currentNameSpan = currentNameSpan.next()) {
497                             jacobian.addToEntry(index + i, propagationParameterColumns.get(currentNameSpan.getData()),
498                                     weight[i] * dMdPp.getEntry(i, col++) / sigma[i] * delegating.getScale());
499                         }
500                     }
501                 }
502             }
503         }
504         // Jacobian of the measurement with respect to measurements parameters
505         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
506             if (driver.isSelected()) {
507                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
508                     final double[] aMPm = evaluation.getParameterDerivatives(driver, span.getStart());
509                     for (int i = 0; i < aMPm.length; ++i) {
510                         jacobian.setEntry(index + i, measurementParameterColumns.get(span.getData()),
511                                           weight[i] * aMPm[i] / sigma[i] * driver.getScale());
512                     }
513                 }
514             }
515         }
516 
517     }
518 
519     /** Configure the multi-satellites handler to handle measurements.
520      * @param point evaluation point
521      * @return multi-satellites handler to handle measurements
522      */
523     private MultiSatStepHandler configureMeasurements(final RealVector point) {
524 
525         // Set up the measurement parameters
526         int index = orbitsEndColumns[builders.length - 1] + propagationParameterColumns.size();
527         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
528 
529             for (Span<Double> span = parameter.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
530                 parameter.setNormalizedValue(point.getEntry(index++), span.getStart());
531             }
532         }
533 
534         // Set up measurements handler
535         final List<PreCompensation> precompensated = new ArrayList<>();
536         for (final ObservedMeasurement<?> measurement : measurements) {
537             if (measurement.isEnabled()) {
538                 precompensated.add(new PreCompensation(measurement, evaluations.get(measurement)));
539             }
540         }
541         precompensated.sort(new ChronologicalComparator());
542 
543         // Assign first and last date
544         firstDate = precompensated.get(0).getDate();
545         lastDate  = precompensated.get(precompensated.size() - 1).getDate();
546 
547         // Reverse the list in case of backward propagation
548         if (!forwardPropagation) {
549             Collections.reverse(precompensated);
550         }
551 
552         return new MeasurementHandler(this, precompensated);
553 
554     }
555 
556     /** Get the iterations count.
557      * @return iterations count
558      */
559     public int getIterationsCount() {
560         return iterationsCounter.getCount();
561     }
562 
563     /** Get the evaluations count.
564      * @return evaluations count
565      */
566     public int getEvaluationsCount() {
567         return evaluationsCounter.getCount();
568     }
569 
570 }