1   /* Copyright 2002-2021 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.estimation.leastsquares;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.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.Propagator;
40  import org.orekit.propagation.PropagatorsParallelizer;
41  import org.orekit.propagation.SpacecraftState;
42  import org.orekit.propagation.conversion.OrbitDeterminationPropagatorBuilder;
43  import org.orekit.propagation.integration.AbstractJacobiansMapper;
44  import org.orekit.propagation.sampling.MultiSatStepHandler;
45  import org.orekit.time.AbsoluteDate;
46  import org.orekit.time.ChronologicalComparator;
47  import org.orekit.utils.ParameterDriver;
48  import org.orekit.utils.ParameterDriversList;
49  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
50  
51  /** Bridge between {@link ObservedMeasurement measurements} and {@link
52   * org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem
53   * least squares problems}.
54   * @author Luc Maisonobe
55   * @author Bryan Cazabonne
56   * @author Thomas Paulet
57   * @since 11.0
58   */
59  public abstract class AbstractBatchLSModel implements MultivariateJacobianFunction {
60  
61      /** Builders for propagators. */
62      private final OrbitDeterminationPropagatorBuilder[] builders;
63  
64      /** Array of each builder's selected propagation drivers. */
65      private final ParameterDriversList[] estimatedPropagationParameters;
66  
67      /** Estimated measurements parameters. */
68      private final ParameterDriversList estimatedMeasurementsParameters;
69  
70      /** Measurements. */
71      private final List<ObservedMeasurement<?>> measurements;
72  
73      /** Start columns for each estimated orbit. */
74      private final int[] orbitsStartColumns;
75  
76      /** End columns for each estimated orbit. */
77      private final int[] orbitsEndColumns;
78  
79      /** Map for propagation parameters columns. */
80      private final Map<String, Integer> propagationParameterColumns;
81  
82      /** Map for measurements parameters columns. */
83      private final Map<String, Integer> measurementParameterColumns;
84  
85      /** Last evaluations. */
86      private final Map<ObservedMeasurement<?>, EstimatedMeasurement<?>> evaluations;
87  
88      /** Observer to be notified at orbit changes. */
89      private final ModelObserver observer;
90  
91      /** Counter for the evaluations. */
92      private Incrementor evaluationsCounter;
93  
94      /** Counter for the iterations. */
95      private Incrementor iterationsCounter;
96  
97      /** Date of the first enabled measurement. */
98      private AbsoluteDate firstDate;
99  
100     /** Date of the last enabled measurement. */
101     private AbsoluteDate lastDate;
102 
103     /** Boolean indicating if the propagation will go forward or backward. */
104     private final boolean forwardPropagation;
105 
106     /** Model function value. */
107     private RealVector value;
108 
109     /** Mappers for extracting Jacobians from integrated states. */
110     private final AbstractJacobiansMapper[] mappers;
111 
112     /** Model function Jacobian. */
113     private RealMatrix jacobian;
114 
115     /**
116      * Constructor.
117      * @param propagatorBuilders builders to use for propagation
118      * @param measurements measurements
119      * @param estimatedMeasurementsParameters estimated measurements parameters
120      * @param mappers jacobian mappers
121      * @param observer observer to be notified at model calls
122      */
123     public AbstractBatchLSModel(final OrbitDeterminationPropagatorBuilder[] propagatorBuilders,
124                                 final List<ObservedMeasurement<?>> measurements,
125                                 final ParameterDriversList estimatedMeasurementsParameters,
126                                 final AbstractJacobiansMapper[] mappers,
127                                 final ModelObserver observer) {
128 
129         this.builders                        = propagatorBuilders.clone();
130         this.measurements                    = measurements;
131         this.estimatedMeasurementsParameters = estimatedMeasurementsParameters;
132         this.measurementParameterColumns     = new HashMap<>(estimatedMeasurementsParameters.getDrivers().size());
133         this.estimatedPropagationParameters  = new ParameterDriversList[builders.length];
134         this.evaluations                     = new IdentityHashMap<>(measurements.size());
135         this.observer                        = observer;
136         this.mappers                         = mappers.clone();
137 
138         // allocate vector and matrix
139         int rows = 0;
140         for (final ObservedMeasurement<?> measurement : measurements) {
141             rows += measurement.getDimension();
142         }
143 
144         this.orbitsStartColumns = new int[builders.length];
145         this.orbitsEndColumns   = new int[builders.length];
146         int columns = 0;
147         for (int i = 0; i < builders.length; ++i) {
148             this.orbitsStartColumns[i] = columns;
149             for (final ParameterDriver driver : builders[i].getOrbitalParametersDrivers().getDrivers()) {
150                 if (driver.isSelected()) {
151                     ++columns;
152                 }
153             }
154             this.orbitsEndColumns[i] = columns;
155         }
156 
157         // Gather all the propagation drivers names in a list
158         final List<String> estimatedPropagationParametersNames = new ArrayList<>();
159         for (int i = 0; i < builders.length; ++i) {
160             // The index i in array estimatedPropagationParameters (attribute of the class) is populated
161             // when the first call to getSelectedPropagationDriversForBuilder(i) is made
162             for (final DelegatingDriver delegating : getSelectedPropagationDriversForBuilder(i).getDrivers()) {
163                 final String driverName = delegating.getName();
164                 // Add the driver name if it has not been added yet
165                 if (!estimatedPropagationParametersNames.contains(driverName)) {
166                     estimatedPropagationParametersNames.add(driverName);
167                 }
168             }
169         }
170         // Populate the map of propagation drivers' columns and update the total number of columns
171         propagationParameterColumns = new HashMap<>(estimatedPropagationParametersNames.size());
172         for (final String driverName : estimatedPropagationParametersNames) {
173             propagationParameterColumns.put(driverName, columns);
174             ++columns;
175         }
176 
177         // Populate the map of measurement drivers' columns and update the total number of columns
178         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
179             measurementParameterColumns.put(parameter.getName(), columns);
180             ++columns;
181         }
182 
183         // Initialize point and value
184         value    = new ArrayRealVector(rows);
185         jacobian = MatrixUtils.createRealMatrix(rows, columns);
186 
187         // Decide whether the propagation will be done forward or backward.
188         // Minimize the duration between first measurement treated and orbit determination date
189         // Propagator builder number 0 holds the reference date for orbit determination
190         final AbsoluteDate refDate = builders[0].getInitialOrbitDate();
191 
192         // Sort the measurement list chronologically
193         measurements.sort(new ChronologicalComparator());
194         firstDate = measurements.get(0).getDate();
195         lastDate  = measurements.get(measurements.size() - 1).getDate();
196 
197         // Decide the direction of propagation
198         if (FastMath.abs(refDate.durationFrom(firstDate)) <= FastMath.abs(refDate.durationFrom(lastDate))) {
199             // Propagate forward from firstDate
200             forwardPropagation = true;
201         } else {
202             // Propagate backward from lastDate
203             forwardPropagation = false;
204         }
205     }
206 
207     /** Set the counter for evaluations.
208      * @param evaluationsCounter counter for evaluations
209      */
210     public void setEvaluationsCounter(final Incrementor evaluationsCounter) {
211         this.evaluationsCounter = evaluationsCounter;
212     }
213 
214     /** Set the counter for iterations.
215      * @param iterationsCounter counter for iterations
216      */
217     public void setIterationsCounter(final Incrementor iterationsCounter) {
218         this.iterationsCounter = iterationsCounter;
219     }
220 
221     /** Return the forward propagation flag.
222      * @return the forward propagation flag
223      */
224     public boolean isForwardPropagation() {
225         return forwardPropagation;
226     }
227 
228     /** Configure the propagator to compute derivatives.
229      * @param propagators {@link Propagator} to configure
230      * @return mapper for this propagator
231      */
232     protected abstract AbstractJacobiansMapper configureDerivatives(Propagator propagators);
233 
234     /** Configure the current estimated orbits.
235      * <p>
236      * For DSST orbit determination, short period derivatives are also calculated.
237      * </p>
238      * @param mapper Jacobian mapper
239      * @param propagator the orbit propagator
240      * @return the current estimated orbits
241      */
242     protected abstract Orbit configureOrbits(AbstractJacobiansMapper mapper, Propagator propagator);
243 
244     /** {@inheritDoc} */
245     @Override
246     public Pair<RealVector, RealMatrix> value(final RealVector point) {
247 
248         // Set up the propagators parallelizer
249         final Propagator[] propagators = createPropagators(point);
250         final Orbit[] orbits = new Orbit[propagators.length];
251         for (int i = 0; i < propagators.length; ++i) {
252             mappers[i] = configureDerivatives(propagators[i]);
253             orbits[i]  = configureOrbits(mappers[i], propagators[i]);
254         }
255         final PropagatorsParallelizer parallelizer =
256                         new PropagatorsParallelizer(Arrays.asList(propagators), configureMeasurements(point));
257 
258         // Reset value and Jacobian
259         evaluations.clear();
260         value.set(0.0);
261         for (int i = 0; i < jacobian.getRowDimension(); ++i) {
262             for (int j = 0; j < jacobian.getColumnDimension(); ++j) {
263                 jacobian.setEntry(i, j, 0.0);
264             }
265         }
266 
267         // Run the propagation, gathering residuals on the fly
268         if (isForwardPropagation()) {
269             // Propagate forward from firstDate
270             parallelizer.propagate(firstDate.shiftedBy(-1.0), lastDate.shiftedBy(+1.0));
271         } else {
272             // Propagate backward from lastDate
273             parallelizer.propagate(lastDate.shiftedBy(+1.0), firstDate.shiftedBy(-1.0));
274         }
275 
276         observer.modelCalled(orbits, evaluations);
277 
278         return new Pair<RealVector, RealMatrix>(value, jacobian);
279 
280     }
281 
282     /** Get the selected propagation drivers for a propagatorBuilder.
283      * @param iBuilder index of the builder in the builders' array
284      * @return the list of selected propagation drivers for propagatorBuilder of index iBuilder
285      */
286     public ParameterDriversList getSelectedPropagationDriversForBuilder(final int iBuilder) {
287 
288         // Lazy evaluation, create the list only if it hasn't been created yet
289         if (estimatedPropagationParameters[iBuilder] == null) {
290 
291             // Gather the drivers
292             final ParameterDriversList selectedPropagationDrivers = new ParameterDriversList();
293             for (final DelegatingDriver delegating : builders[iBuilder].getPropagationParametersDrivers().getDrivers()) {
294                 if (delegating.isSelected()) {
295                     for (final ParameterDriver driver : delegating.getRawDrivers()) {
296                         selectedPropagationDrivers.add(driver);
297                     }
298                 }
299             }
300 
301             // List of propagation drivers are sorted in the BatchLSEstimator class.
302             // Hence we need to sort this list so the parameters' indexes match
303             selectedPropagationDrivers.sort();
304 
305             // Add the list of selected propagation drivers to the array
306             estimatedPropagationParameters[iBuilder] = selectedPropagationDrivers;
307         }
308         return estimatedPropagationParameters[iBuilder];
309     }
310 
311     /** Create the propagators and parameters corresponding to an evaluation point.
312      * @param point evaluation point
313      * @return an array of new propagators
314      */
315     public Propagator[] createPropagators(final RealVector point) {
316 
317         final Propagator[] propagators = new Propagator[builders.length];
318 
319         // Set up the propagators
320         for (int i = 0; i < builders.length; ++i) {
321 
322             // Get the number of selected orbital drivers in the builder
323             final int nbOrb    = orbitsEndColumns[i] - orbitsStartColumns[i];
324 
325             // Get the list of selected propagation drivers in the builder and its size
326             final ParameterDriversList selectedPropagationDrivers = getSelectedPropagationDriversForBuilder(i);
327             final int nbParams = selectedPropagationDrivers.getNbParams();
328 
329             // Init the array of normalized parameters for the builder
330             final double[] propagatorArray = new double[nbOrb + nbParams];
331 
332             // Add the orbital drivers normalized values
333             for (int j = 0; j < nbOrb; ++j) {
334                 propagatorArray[j] = point.getEntry(orbitsStartColumns[i] + j);
335             }
336 
337             // Add the propagation drivers normalized values
338             for (int j = 0; j < nbParams; ++j) {
339                 propagatorArray[nbOrb + j] =
340                                 point.getEntry(propagationParameterColumns.get(selectedPropagationDrivers.getDrivers().get(j).getName()));
341             }
342 
343             // Build the propagator
344             propagators[i] = builders[i].buildPropagator(propagatorArray);
345         }
346 
347         return propagators;
348 
349     }
350 
351     /** Fetch a measurement that was evaluated during propagation.
352      * @param index index of the measurement first component
353      * @param evaluation measurement evaluation
354      */
355     public void fetchEvaluatedMeasurement(final int index, final EstimatedMeasurement<?> evaluation) {
356 
357         // States and observed measurement
358         final SpacecraftState[]      evaluationStates    = evaluation.getStates();
359         final ObservedMeasurement<?> observedMeasurement = evaluation.getObservedMeasurement();
360 
361         // compute weighted residuals
362         evaluations.put(observedMeasurement, evaluation);
363         if (evaluation.getStatus() == EstimatedMeasurement.Status.REJECTED) {
364             return;
365         }
366 
367         final double[] evaluated = evaluation.getEstimatedValue();
368         final double[] observed  = observedMeasurement.getObservedValue();
369         final double[] sigma     = observedMeasurement.getTheoreticalStandardDeviation();
370         final double[] weight    = evaluation.getObservedMeasurement().getBaseWeight();
371         for (int i = 0; i < evaluated.length; ++i) {
372             value.setEntry(index + i, weight[i] * (evaluated[i] - observed[i]) / sigma[i]);
373         }
374 
375         for (int k = 0; k < evaluationStates.length; ++k) {
376 
377             final int p = observedMeasurement.getSatellites().get(k).getPropagatorIndex();
378 
379             // partial derivatives of the current Cartesian coordinates with respect to current orbital state
380             final double[][] aCY = new double[6][6];
381             final Orbit currentOrbit = evaluationStates[k].getOrbit();
382             currentOrbit.getJacobianWrtParameters(builders[p].getPositionAngle(), aCY);
383             final RealMatrix dCdY = new Array2DRowRealMatrix(aCY, false);
384 
385             // Jacobian of the measurement with respect to current orbital state
386             final RealMatrix dMdC = new Array2DRowRealMatrix(evaluation.getStateDerivatives(k), false);
387             final RealMatrix dMdY = dMdC.multiply(dCdY);
388 
389             // compute derivatives used by analytical orbit determination methods
390             mappers[p].analyticalDerivatives(evaluationStates[k]);
391 
392             // Jacobian of the measurement with respect to initial orbital state
393             final double[][] aYY0 = new double[6][6];
394             mappers[p].getStateJacobian(evaluationStates[k], aYY0);
395             final RealMatrix dYdY0 = new Array2DRowRealMatrix(aYY0, false);
396             final RealMatrix dMdY0 = dMdY.multiply(dYdY0);
397             for (int i = 0; i < dMdY0.getRowDimension(); ++i) {
398                 int jOrb = orbitsStartColumns[p];
399                 for (int j = 0; j < dMdY0.getColumnDimension(); ++j) {
400                     final ParameterDriver driver = builders[p].getOrbitalParametersDrivers().getDrivers().get(j);
401                     if (driver.isSelected()) {
402                         jacobian.setEntry(index + i, jOrb++,
403                                           weight[i] * dMdY0.getEntry(i, j) / sigma[i] * driver.getScale());
404                     }
405                 }
406             }
407 
408             // Jacobian of the measurement with respect to propagation parameters
409             final ParameterDriversList selectedPropagationDrivers = getSelectedPropagationDriversForBuilder(p);
410             final int nbParams = selectedPropagationDrivers.getNbParams();
411             if ( nbParams > 0) {
412                 final double[][] aYPp  = new double[6][nbParams];
413                 mappers[p].getParametersJacobian(evaluationStates[k], aYPp);
414                 final RealMatrix dYdPp = new Array2DRowRealMatrix(aYPp, false);
415                 final RealMatrix dMdPp = dMdY.multiply(dYdPp);
416                 for (int i = 0; i < dMdPp.getRowDimension(); ++i) {
417                     for (int j = 0; j < nbParams; ++j) {
418                         final ParameterDriver delegating = selectedPropagationDrivers.getDrivers().get(j);
419                         jacobian.addToEntry(index + i, propagationParameterColumns.get(delegating.getName()),
420                                             weight[i] * dMdPp.getEntry(i, j) / sigma[i] * delegating.getScale());
421                     }
422                 }
423             }
424         }
425         // Jacobian of the measurement with respect to measurements parameters
426         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
427             if (driver.isSelected()) {
428                 final double[] aMPm = evaluation.getParameterDerivatives(driver);
429                 for (int i = 0; i < aMPm.length; ++i) {
430                     jacobian.setEntry(index + i, measurementParameterColumns.get(driver.getName()),
431                                       weight[i] * aMPm[i] / sigma[i] * driver.getScale());
432                 }
433             }
434         }
435 
436     }
437 
438     /** Configure the multi-satellites handler to handle measurements.
439      * @param point evaluation point
440      * @return multi-satellites handler to handle measurements
441      */
442     private MultiSatStepHandler configureMeasurements(final RealVector point) {
443 
444         // Set up the measurement parameters
445         int index = orbitsEndColumns[builders.length - 1] + propagationParameterColumns.size();
446         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
447             parameter.setNormalizedValue(point.getEntry(index++));
448         }
449 
450         // Set up measurements handler
451         final List<PreCompensation> precompensated = new ArrayList<>();
452         for (final ObservedMeasurement<?> measurement : measurements) {
453             if (measurement.isEnabled()) {
454                 precompensated.add(new PreCompensation(measurement, evaluations.get(measurement)));
455             }
456         }
457         precompensated.sort(new ChronologicalComparator());
458 
459         // Assign first and last date
460         firstDate = precompensated.get(0).getDate();
461         lastDate  = precompensated.get(precompensated.size() - 1).getDate();
462 
463         // Reverse the list in case of backward propagation
464         if (!forwardPropagation) {
465             Collections.reverse(precompensated);
466         }
467 
468         return new MeasurementHandler(this, precompensated);
469 
470     }
471 
472     /** Get the iterations count.
473      * @return iterations count
474      */
475     public int getIterationsCount() {
476         return iterationsCounter.getCount();
477     }
478 
479     /** Get the evaluations count.
480      * @return evaluations count
481      */
482     public int getEvaluationsCount() {
483         return evaluationsCounter.getCount();
484     }
485 
486 }