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