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