1   /* Copyright 2002-2024 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.sequential;
18  
19  import org.hipparchus.exception.MathRuntimeException;
20  import org.hipparchus.filtering.kalman.ProcessEstimate;
21  import org.hipparchus.filtering.kalman.extended.ExtendedKalmanFilter;
22  import org.hipparchus.filtering.kalman.extended.NonLinearEvolution;
23  import org.hipparchus.filtering.kalman.extended.NonLinearProcess;
24  import org.hipparchus.linear.Array2DRowRealMatrix;
25  import org.hipparchus.linear.ArrayRealVector;
26  import org.hipparchus.linear.MatrixUtils;
27  import org.hipparchus.linear.QRDecomposition;
28  import org.hipparchus.linear.RealMatrix;
29  import org.hipparchus.linear.RealVector;
30  import org.hipparchus.util.FastMath;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.estimation.measurements.EstimatedMeasurement;
33  import org.orekit.estimation.measurements.ObservedMeasurement;
34  import org.orekit.orbits.Orbit;
35  import org.orekit.orbits.OrbitType;
36  import org.orekit.propagation.PropagationType;
37  import org.orekit.propagation.SpacecraftState;
38  import org.orekit.propagation.conversion.DSSTPropagatorBuilder;
39  import org.orekit.propagation.semianalytical.dsst.DSSTHarvester;
40  import org.orekit.propagation.semianalytical.dsst.DSSTPropagator;
41  import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
42  import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
43  import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
44  import org.orekit.time.AbsoluteDate;
45  import org.orekit.time.ChronologicalComparator;
46  import org.orekit.utils.ParameterDriver;
47  import org.orekit.utils.ParameterDriversList;
48  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
49  import org.orekit.utils.TimeSpanMap.Span;
50  
51  import java.util.ArrayList;
52  import java.util.Comparator;
53  import java.util.HashMap;
54  import java.util.List;
55  import java.util.Map;
56  
57  /** Process model to use with a {@link SemiAnalyticalKalmanEstimator}.
58   *
59   * @see "Folcik Z., Orbit Determination Using Modern Filters/Smoothers and Continuous Thrust Modeling,
60   *       Master of Science Thesis, Department of Aeronautics and Astronautics, MIT, June, 2008."
61   *
62   * @see "Cazabonne B., Bayard J., Journot M., and Cefola P. J., A Semi-analytical Approach for Orbit
63   *       Determination based on Extended Kalman Filter, AAS Paper 21-614, AAS/AIAA Astrodynamics
64   *       Specialist Conference, Big Sky, August 2021."
65   *
66   * @author Julie Bayard
67   * @author Bryan Cazabonne
68   * @author Maxime Journot
69   * @since 11.1
70   */
71  public  class SemiAnalyticalKalmanModel implements KalmanEstimation, NonLinearProcess<MeasurementDecorator>, SemiAnalyticalProcess {
72  
73      /** Builders for DSST propagator. */
74      private final DSSTPropagatorBuilder builder;
75  
76      /** Estimated orbital parameters. */
77      private final ParameterDriversList estimatedOrbitalParameters;
78  
79      /** Per-builder estimated propagation drivers. */
80      private final ParameterDriversList estimatedPropagationParameters;
81  
82      /** Estimated measurements parameters. */
83      private final ParameterDriversList estimatedMeasurementsParameters;
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      /** Scaling factors. */
92      private final double[] scale;
93  
94      /** Provider for covariance matrix. */
95      private final CovarianceMatrixProvider covarianceMatrixProvider;
96  
97      /** Process noise matrix provider for measurement parameters. */
98      private final CovarianceMatrixProvider measurementProcessNoiseMatrix;
99  
100     /** Harvester between two-dimensional Jacobian matrices and one-dimensional additional state arrays. */
101     private DSSTHarvester harvester;
102 
103     /** Propagators for the reference trajectories, up to current date. */
104     private DSSTPropagator dsstPropagator;
105 
106     /** Observer to retrieve current estimation info. */
107     private KalmanObserver observer;
108 
109     /** Current number of measurement. */
110     private int currentMeasurementNumber;
111 
112     /** Current date. */
113     private AbsoluteDate currentDate;
114 
115     /** Predicted mean element filter correction. */
116     private RealVector predictedFilterCorrection;
117 
118     /** Corrected mean element filter correction. */
119     private RealVector correctedFilterCorrection;
120 
121     /** Predicted measurement. */
122     private EstimatedMeasurement<?> predictedMeasurement;
123 
124     /** Corrected measurement. */
125     private EstimatedMeasurement<?> correctedMeasurement;
126 
127     /** Nominal mean spacecraft state. */
128     private SpacecraftState nominalMeanSpacecraftState;
129 
130     /** Previous nominal mean spacecraft state. */
131     private SpacecraftState previousNominalMeanSpacecraftState;
132 
133     /** Current corrected estimate. */
134     private ProcessEstimate correctedEstimate;
135 
136     /** Inverse of the orbital part of the state transition matrix. */
137     private RealMatrix phiS;
138 
139     /** Propagation parameters part of the state transition matrix. */
140     private RealMatrix psiS;
141 
142     /** Kalman process model constructor (package private).
143      * @param propagatorBuilder propagators builders used to evaluate the orbits.
144      * @param covarianceMatrixProvider provider for covariance matrix
145      * @param estimatedMeasurementParameters measurement parameters to estimate
146      * @param measurementProcessNoiseMatrix provider for measurement process noise matrix
147      */
148     protected SemiAnalyticalKalmanModel(final DSSTPropagatorBuilder propagatorBuilder,
149                                         final CovarianceMatrixProvider covarianceMatrixProvider,
150                                         final ParameterDriversList estimatedMeasurementParameters,
151                                         final CovarianceMatrixProvider measurementProcessNoiseMatrix) {
152 
153         this.builder                         = propagatorBuilder;
154         this.estimatedMeasurementsParameters = estimatedMeasurementParameters;
155         this.measurementParameterColumns     = new HashMap<>(estimatedMeasurementsParameters.getDrivers().size());
156         this.observer                        = null;
157         this.currentMeasurementNumber        = 0;
158         this.currentDate                     = propagatorBuilder.getInitialOrbitDate();
159         this.covarianceMatrixProvider        = covarianceMatrixProvider;
160         this.measurementProcessNoiseMatrix   = measurementProcessNoiseMatrix;
161 
162         // Number of estimated parameters
163         int columns = 0;
164 
165         // Set estimated orbital parameters
166         estimatedOrbitalParameters = new ParameterDriversList();
167         for (final ParameterDriver driver : builder.getOrbitalParametersDrivers().getDrivers()) {
168 
169             // Verify if the driver reference date has been set
170             if (driver.getReferenceDate() == null) {
171                 driver.setReferenceDate(currentDate);
172             }
173 
174             // Verify if the driver is selected
175             if (driver.isSelected()) {
176                 estimatedOrbitalParameters.add(driver);
177                 columns++;
178             }
179 
180         }
181 
182         // Set estimated propagation parameters
183         estimatedPropagationParameters = new ParameterDriversList();
184         final List<String> estimatedPropagationParametersNames = new ArrayList<>();
185         for (final ParameterDriver driver : builder.getPropagationParametersDrivers().getDrivers()) {
186 
187             // Verify if the driver reference date has been set
188             if (driver.getReferenceDate() == null) {
189                 driver.setReferenceDate(currentDate);
190             }
191 
192             // Verify if the driver is selected
193             if (driver.isSelected()) {
194                 estimatedPropagationParameters.add(driver);
195                 // Add the driver name if it has not been added yet
196                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
197 
198                     if (!estimatedPropagationParametersNames.contains(span.getData())) {
199                         estimatedPropagationParametersNames.add(span.getData());
200                     }
201                 }
202             }
203 
204         }
205         estimatedPropagationParametersNames.sort(Comparator.naturalOrder());
206 
207         // Populate the map of propagation drivers' columns and update the total number of columns
208         propagationParameterColumns = new HashMap<>(estimatedPropagationParametersNames.size());
209         for (final String driverName : estimatedPropagationParametersNames) {
210             propagationParameterColumns.put(driverName, columns);
211             ++columns;
212         }
213 
214         // Set the estimated measurement parameters
215         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
216             if (parameter.getReferenceDate() == null) {
217                 parameter.setReferenceDate(currentDate);
218             }
219             for (Span<String> span = parameter.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
220                 measurementParameterColumns.put(span.getData(), columns);
221                 ++columns;
222             }
223         }
224 
225         // Compute the scale factors
226         this.scale = new double[columns];
227         int index = 0;
228         for (final ParameterDriver driver : estimatedOrbitalParameters.getDrivers()) {
229             scale[index++] = driver.getScale();
230         }
231         for (final ParameterDriver driver : estimatedPropagationParameters.getDrivers()) {
232             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
233                 scale[index++] = driver.getScale();
234             }
235         }
236         for (final ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
237             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
238                 scale[index++] = driver.getScale();
239             }
240         }
241 
242         // Build the reference propagator and add its partial derivatives equations implementation
243         updateReferenceTrajectory(getEstimatedPropagator());
244         this.nominalMeanSpacecraftState = dsstPropagator.getInitialState();
245         this.previousNominalMeanSpacecraftState = nominalMeanSpacecraftState;
246 
247         // Initialize "field" short periodic terms
248         harvester.initializeFieldShortPeriodTerms(nominalMeanSpacecraftState);
249 
250         // Initialize the estimated normalized mean element filter correction (See Ref [1], Eq. 3.2a)
251         this.predictedFilterCorrection = MatrixUtils.createRealVector(columns);
252         this.correctedFilterCorrection = predictedFilterCorrection;
253 
254         // Initialize propagation parameters part of the state transition matrix (See Ref [1], Eq. 3.2c)
255         this.psiS = null;
256         if (estimatedPropagationParameters.getNbParams() != 0) {
257             this.psiS = MatrixUtils.createRealMatrix(getNumberSelectedOrbitalDriversValuesToEstimate(),
258                                                      getNumberSelectedPropagationDriversValuesToEstimate());
259         }
260 
261         // Initialize inverse of the orbital part of the state transition matrix (See Ref [1], Eq. 3.2d)
262         this.phiS = MatrixUtils.createRealIdentityMatrix(getNumberSelectedOrbitalDriversValuesToEstimate());
263 
264         // Number of estimated measurement parameters
265         final int nbMeas = getNumberSelectedMeasurementDriversValuesToEstimate();
266 
267         // Number of estimated dynamic parameters (orbital + propagation)
268         final int nbDyn  = getNumberSelectedOrbitalDriversValuesToEstimate() + getNumberSelectedPropagationDriversValuesToEstimate();
269 
270         // Covariance matrix
271         final RealMatrix noiseK = MatrixUtils.createRealMatrix(nbDyn + nbMeas, nbDyn + nbMeas);
272         final RealMatrix noiseP = covarianceMatrixProvider.getInitialCovarianceMatrix(nominalMeanSpacecraftState);
273         noiseK.setSubMatrix(noiseP.getData(), 0, 0);
274         if (measurementProcessNoiseMatrix != null) {
275             final RealMatrix noiseM = measurementProcessNoiseMatrix.getInitialCovarianceMatrix(nominalMeanSpacecraftState);
276             noiseK.setSubMatrix(noiseM.getData(), nbDyn, nbDyn);
277         }
278 
279         // Verify dimension
280         KalmanEstimatorUtil.checkDimension(noiseK.getRowDimension(),
281                                            builder.getOrbitalParametersDrivers(),
282                                            builder.getPropagationParametersDrivers(),
283                                            estimatedMeasurementsParameters);
284 
285         final RealMatrix correctedCovariance = KalmanEstimatorUtil.normalizeCovarianceMatrix(noiseK, scale);
286 
287         // Initialize corrected estimate
288         this.correctedEstimate = new ProcessEstimate(0.0, correctedFilterCorrection, correctedCovariance);
289 
290     }
291 
292     /** {@inheritDoc} */
293     @Override
294     public KalmanObserver getObserver() {
295         return observer;
296     }
297 
298     /** Set the observer.
299      * @param observer the observer
300      */
301     public void setObserver(final KalmanObserver observer) {
302         this.observer = observer;
303     }
304 
305     /** Get the current corrected estimate.
306      * @return current corrected estimate
307      */
308     public ProcessEstimate getEstimate() {
309         return correctedEstimate;
310     }
311 
312     /** Process a single measurement.
313      * <p>
314      * Update the filter with the new measurements.
315      * </p>
316      * @param observedMeasurements the list of measurements to process
317      * @param filter Extended Kalman Filter
318      * @return estimated propagator
319      */
320     public DSSTPropagator processMeasurements(final List<ObservedMeasurement<?>> observedMeasurements,
321                                               final ExtendedKalmanFilter<MeasurementDecorator> filter) {
322         try {
323 
324             // Sort the measurement
325             observedMeasurements.sort(new ChronologicalComparator());
326             final AbsoluteDate tStart             = observedMeasurements.get(0).getDate();
327             final AbsoluteDate tEnd               = observedMeasurements.get(observedMeasurements.size() - 1).getDate();
328             final double       overshootTimeRange = FastMath.nextAfter(tEnd.durationFrom(tStart),
329                                                     Double.POSITIVE_INFINITY);
330 
331             // Initialize step handler and set it to the propagator
332             final SemiAnalyticalMeasurementHandler stepHandler = new SemiAnalyticalMeasurementHandler(this, filter, observedMeasurements, builder.getInitialOrbitDate());
333             dsstPropagator.getMultiplexer().add(stepHandler);
334             dsstPropagator.propagate(tStart, tStart.shiftedBy(overshootTimeRange));
335 
336             // Return the last estimated propagator
337             return getEstimatedPropagator();
338 
339         } catch (MathRuntimeException mrte) {
340             throw new OrekitException(mrte);
341         }
342     }
343 
344     /** Get the propagator estimated with the values set in the propagator builder.
345      * @return propagator based on the current values in the builder
346      */
347     public DSSTPropagator getEstimatedPropagator() {
348         // Return propagator built with current instantiation of the propagator builder
349         return (DSSTPropagator) builder.buildPropagator();
350     }
351 
352     /** {@inheritDoc} */
353     @Override
354     public NonLinearEvolution getEvolution(final double previousTime, final RealVector previousState,
355                                            final MeasurementDecorator measurement) {
356 
357         // Set a reference date for all measurements parameters that lack one (including the not estimated ones)
358         final ObservedMeasurement<?> observedMeasurement = measurement.getObservedMeasurement();
359         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
360             if (driver.getReferenceDate() == null) {
361                 driver.setReferenceDate(builder.getInitialOrbitDate());
362             }
363         }
364 
365         // Increment measurement number
366         ++currentMeasurementNumber;
367 
368         // Update the current date
369         currentDate = measurement.getObservedMeasurement().getDate();
370 
371         // Normalized state transition matrix
372         final RealMatrix stm = getErrorStateTransitionMatrix();
373 
374         // Predict filter correction
375         predictedFilterCorrection = predictFilterCorrection(stm);
376 
377         // Short period term derivatives
378         analyticalDerivativeComputations(nominalMeanSpacecraftState);
379 
380         // Calculate the predicted osculating elements
381         final double[] osculating = computeOsculatingElements(predictedFilterCorrection);
382         final Orbit osculatingOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(osculating, null, builder.getPositionAngleType(),
383                                                                             currentDate, nominalMeanSpacecraftState.getMu(),
384                                                                             nominalMeanSpacecraftState.getFrame());
385 
386         // Compute the predicted measurements  (See Ref [1], Eq. 3.8)
387         predictedMeasurement = observedMeasurement.estimate(currentMeasurementNumber,
388                                                             currentMeasurementNumber,
389                                                             new SpacecraftState[] {
390                                                                 new SpacecraftState(osculatingOrbit,
391                                                                                     nominalMeanSpacecraftState.getAttitude(),
392                                                                                     nominalMeanSpacecraftState.getMass(),
393                                                                                     nominalMeanSpacecraftState.getAdditionalStatesValues(),
394                                                                                     nominalMeanSpacecraftState.getAdditionalStatesDerivatives())
395                                                             });
396 
397         // Normalized measurement matrix
398         final RealMatrix measurementMatrix = getMeasurementMatrix();
399 
400         // Number of estimated measurement parameters
401         final int nbMeas = getNumberSelectedMeasurementDriversValuesToEstimate();
402 
403         // Number of estimated dynamic parameters (orbital + propagation)
404         final int nbDyn  = getNumberSelectedOrbitalDriversValuesToEstimate() + getNumberSelectedPropagationDriversValuesToEstimate();
405 
406         // Covariance matrix
407         final RealMatrix noiseK = MatrixUtils.createRealMatrix(nbDyn + nbMeas, nbDyn + nbMeas);
408         final RealMatrix noiseP = covarianceMatrixProvider.getProcessNoiseMatrix(previousNominalMeanSpacecraftState, nominalMeanSpacecraftState);
409         noiseK.setSubMatrix(noiseP.getData(), 0, 0);
410         if (measurementProcessNoiseMatrix != null) {
411             final RealMatrix noiseM = measurementProcessNoiseMatrix.getProcessNoiseMatrix(previousNominalMeanSpacecraftState, nominalMeanSpacecraftState);
412             noiseK.setSubMatrix(noiseM.getData(), nbDyn, nbDyn);
413         }
414 
415         // Verify dimension
416         KalmanEstimatorUtil.checkDimension(noiseK.getRowDimension(),
417                                            builder.getOrbitalParametersDrivers(),
418                                            builder.getPropagationParametersDrivers(),
419                                            estimatedMeasurementsParameters);
420 
421         final RealMatrix normalizedProcessNoise = KalmanEstimatorUtil.normalizeCovarianceMatrix(noiseK, scale);
422 
423         // Return
424         return new NonLinearEvolution(measurement.getTime(), predictedFilterCorrection, stm,
425                                       normalizedProcessNoise, measurementMatrix);
426     }
427 
428     /** {@inheritDoc} */
429     @Override
430     public RealVector getInnovation(final MeasurementDecorator measurement, final NonLinearEvolution evolution,
431                                     final RealMatrix innovationCovarianceMatrix) {
432 
433         // Apply the dynamic outlier filter, if it exists
434         KalmanEstimatorUtil.applyDynamicOutlierFilter(predictedMeasurement, innovationCovarianceMatrix);
435         // Compute the innovation vector
436         return KalmanEstimatorUtil.computeInnovationVector(predictedMeasurement, predictedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
437     }
438 
439     /** {@inheritDoc} */
440     @Override
441     public void finalizeEstimation(final ObservedMeasurement<?> observedMeasurement,
442                                    final ProcessEstimate estimate) {
443         // Update the process estimate
444         correctedEstimate = estimate;
445         // Corrected filter correction
446         correctedFilterCorrection = estimate.getState();
447         // Update the previous nominal mean spacecraft state
448         previousNominalMeanSpacecraftState = nominalMeanSpacecraftState;
449         // Calculate the corrected osculating elements
450         final double[] osculating = computeOsculatingElements(correctedFilterCorrection);
451         final Orbit osculatingOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(osculating, null, builder.getPositionAngleType(),
452                                                                             currentDate, nominalMeanSpacecraftState.getMu(),
453                                                                             nominalMeanSpacecraftState.getFrame());
454 
455         // Compute the corrected measurements
456         correctedMeasurement = observedMeasurement.estimate(currentMeasurementNumber,
457                                                             currentMeasurementNumber,
458                                                             new SpacecraftState[] {
459                                                                 new SpacecraftState(osculatingOrbit,
460                                                                                     nominalMeanSpacecraftState.getAttitude(),
461                                                                                     nominalMeanSpacecraftState.getMass(),
462                                                                                     nominalMeanSpacecraftState.getAdditionalStatesValues(),
463                                                                                     nominalMeanSpacecraftState.getAdditionalStatesDerivatives())
464                                                             });
465         // Call the observer if the user add one
466         if (observer != null) {
467             observer.evaluationPerformed(this);
468         }
469     }
470 
471     /** {@inheritDoc} */
472     @Override
473     public void finalizeOperationsObservationGrid() {
474         // Update parameters
475         updateParameters();
476     }
477 
478     /** {@inheritDoc} */
479     @Override
480     public ParameterDriversList getEstimatedOrbitalParameters() {
481         return estimatedOrbitalParameters;
482     }
483 
484     /** {@inheritDoc} */
485     @Override
486     public ParameterDriversList getEstimatedPropagationParameters() {
487         return estimatedPropagationParameters;
488     }
489 
490     /** {@inheritDoc} */
491     @Override
492     public ParameterDriversList getEstimatedMeasurementsParameters() {
493         return estimatedMeasurementsParameters;
494     }
495 
496     /** {@inheritDoc} */
497     @Override
498     public SpacecraftState[] getPredictedSpacecraftStates() {
499         return new SpacecraftState[] {nominalMeanSpacecraftState};
500     }
501 
502     /** {@inheritDoc} */
503     @Override
504     public SpacecraftState[] getCorrectedSpacecraftStates() {
505         return new SpacecraftState[] {getEstimatedPropagator().getInitialState()};
506     }
507 
508     /** {@inheritDoc} */
509     @Override
510     public RealVector getPhysicalEstimatedState() {
511         // Method {@link ParameterDriver#getValue()} is used to get
512         // the physical values of the state.
513         // The scales'array is used to get the size of the state vector
514         final RealVector physicalEstimatedState = new ArrayRealVector(scale.length);
515         int i = 0;
516         for (final DelegatingDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
517             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
518                 physicalEstimatedState.setEntry(i++, span.getData());
519             }
520         }
521         for (final DelegatingDriver driver : getEstimatedPropagationParameters().getDrivers()) {
522             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
523                 physicalEstimatedState.setEntry(i++, span.getData());
524             }
525         }
526         for (final DelegatingDriver driver : getEstimatedMeasurementsParameters().getDrivers()) {
527             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
528                 physicalEstimatedState.setEntry(i++, span.getData());
529             }
530         }
531 
532         return physicalEstimatedState;
533     }
534 
535     /** {@inheritDoc} */
536     @Override
537     public RealMatrix getPhysicalEstimatedCovarianceMatrix() {
538         // Un-normalize the estimated covariance matrix (P) from Hipparchus and return it.
539         // The covariance P is an mxm matrix where m = nbOrb + nbPropag + nbMeas
540         // For each element [i,j] of P the corresponding normalized value is:
541         // Pn[i,j] = P[i,j] / (scale[i]*scale[j])
542         // Consequently: P[i,j] = Pn[i,j] * scale[i] * scale[j]
543         return KalmanEstimatorUtil.unnormalizeCovarianceMatrix(correctedEstimate.getCovariance(), scale);
544     }
545 
546     /** {@inheritDoc} */
547     @Override
548     public RealMatrix getPhysicalStateTransitionMatrix() {
549         //  Un-normalize the state transition matrix (φ) from Hipparchus and return it.
550         // φ is an mxm matrix where m = nbOrb + nbPropag + nbMeas
551         // For each element [i,j] of normalized φ (φn), the corresponding physical value is:
552         // φ[i,j] = φn[i,j] * scale[i] / scale[j]
553         return correctedEstimate.getStateTransitionMatrix() == null ?
554                 null : KalmanEstimatorUtil.unnormalizeStateTransitionMatrix(correctedEstimate.getStateTransitionMatrix(), scale);
555     }
556 
557     /** {@inheritDoc} */
558     @Override
559     public RealMatrix getPhysicalMeasurementJacobian() {
560         // Un-normalize the measurement matrix (H) from Hipparchus and return it.
561         // H is an nxm matrix where:
562         //  - m = nbOrb + nbPropag + nbMeas is the number of estimated parameters
563         //  - n is the size of the measurement being processed by the filter
564         // For each element [i,j] of normalized H (Hn) the corresponding physical value is:
565         // H[i,j] = Hn[i,j] * σ[i] / scale[j]
566         return correctedEstimate.getMeasurementJacobian() == null ?
567                 null : KalmanEstimatorUtil.unnormalizeMeasurementJacobian(correctedEstimate.getMeasurementJacobian(),
568                                                                           scale,
569                                                                           correctedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
570     }
571 
572     /** {@inheritDoc} */
573     @Override
574     public RealMatrix getPhysicalInnovationCovarianceMatrix() {
575         // Un-normalize the innovation covariance matrix (S) from Hipparchus and return it.
576         // S is an nxn matrix where n is the size of the measurement being processed by the filter
577         // For each element [i,j] of normalized S (Sn) the corresponding physical value is:
578         // S[i,j] = Sn[i,j] * σ[i] * σ[j]
579         return correctedEstimate.getInnovationCovariance() == null ?
580                 null : KalmanEstimatorUtil.unnormalizeInnovationCovarianceMatrix(correctedEstimate.getInnovationCovariance(),
581                                                                                  predictedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
582     }
583 
584     /** {@inheritDoc} */
585     @Override
586     public RealMatrix getPhysicalKalmanGain() {
587         // Un-normalize the Kalman gain (K) from Hipparchus and return it.
588         // K is an mxn matrix where:
589         //  - m = nbOrb + nbPropag + nbMeas is the number of estimated parameters
590         //  - n is the size of the measurement being processed by the filter
591         // For each element [i,j] of normalized K (Kn) the corresponding physical value is:
592         // K[i,j] = Kn[i,j] * scale[i] / σ[j]
593         return correctedEstimate.getKalmanGain() == null ?
594                 null : KalmanEstimatorUtil.unnormalizeKalmanGainMatrix(correctedEstimate.getKalmanGain(),
595                                                                        scale,
596                                                                        correctedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
597     }
598 
599     /** {@inheritDoc} */
600     @Override
601     public int getCurrentMeasurementNumber() {
602         return currentMeasurementNumber;
603     }
604 
605     /** {@inheritDoc} */
606     @Override
607     public AbsoluteDate getCurrentDate() {
608         return currentDate;
609     }
610 
611     /** {@inheritDoc} */
612     @Override
613     public EstimatedMeasurement<?> getPredictedMeasurement() {
614         return predictedMeasurement;
615     }
616 
617     /** {@inheritDoc} */
618     @Override
619     public EstimatedMeasurement<?> getCorrectedMeasurement() {
620         return correctedMeasurement;
621     }
622 
623     /** {@inheritDoc} */
624     @Override
625     public void updateNominalSpacecraftState(final SpacecraftState nominal) {
626         this.nominalMeanSpacecraftState = nominal;
627         // Update the builder with the nominal mean elements orbit
628         builder.resetOrbit(nominal.getOrbit(), PropagationType.MEAN);
629     }
630 
631     /** Update the reference trajectories using the propagator as input.
632      * @param propagator The new propagator to use
633      */
634     public void updateReferenceTrajectory(final DSSTPropagator propagator) {
635 
636         dsstPropagator = propagator;
637 
638         // Equation name
639         final String equationName = SemiAnalyticalKalmanEstimator.class.getName() + "-derivatives-";
640 
641         // Mean state
642         final SpacecraftState meanState = dsstPropagator.initialIsOsculating() ?
643                        DSSTPropagator.computeMeanState(dsstPropagator.getInitialState(), dsstPropagator.getAttitudeProvider(), dsstPropagator.getAllForceModels()) :
644                        dsstPropagator.getInitialState();
645 
646         // Update the jacobian harvester
647         dsstPropagator.setInitialState(meanState, PropagationType.MEAN);
648         harvester = dsstPropagator.setupMatricesComputation(equationName, null, null);
649 
650     }
651 
652     /** {@inheritDoc} */
653     @Override
654     public void updateShortPeriods(final SpacecraftState state) {
655         // Loop on DSST force models
656         for (final DSSTForceModel model : builder.getAllForceModels()) {
657             model.updateShortPeriodTerms(model.getParametersAllValues(), state);
658         }
659         harvester.updateFieldShortPeriodTerms(state);
660     }
661 
662     /** {@inheritDoc} */
663     @Override
664     public void initializeShortPeriodicTerms(final SpacecraftState meanState) {
665         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<>();
666         // initialize ForceModels in OSCULATING mode even if propagation is MEAN
667         final PropagationType type = PropagationType.OSCULATING;
668         for (final DSSTForceModel force :  builder.getAllForceModels()) {
669             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(new AuxiliaryElements(meanState.getOrbit(), 1), type, force.getParameters(meanState.getDate())));
670         }
671         dsstPropagator.setShortPeriodTerms(shortPeriodTerms);
672         // also need to initialize the Field terms in the same mode
673         harvester.initializeFieldShortPeriodTerms(meanState, type);
674     }
675 
676     /** Get the normalized state transition matrix (STM) from previous point to current point.
677      * The STM contains the partial derivatives of current state with respect to previous state.
678      * The  STM is an mxm matrix where m is the size of the state vector.
679      * m = nbOrb + nbPropag + nbMeas
680      * @return the normalized error state transition matrix
681      */
682     private RealMatrix getErrorStateTransitionMatrix() {
683 
684         /* The state transition matrix is obtained as follows, with:
685          *  - Phi(k, k-1) : Transitional orbital matrix
686          *  - Psi(k, k-1) : Transitional propagation parameters matrix
687          *
688          *       |             |             |   .    |
689          *       | Phi(k, k-1) | Psi(k, k-1) | ..0..  |
690          *       |             |             |   .    |
691          *       |-------------|-------------|--------|
692          *       |      .      |    1 0 0    |   .    |
693          * STM = |    ..0..    |    0 1 0    | ..0..  |
694          *       |      .      |    0 0 1    |   .    |
695          *       |-------------|-------------|--------|
696          *       |      .      |      .      | 1 0 0..|
697          *       |    ..0..    |    ..0..    | 0 1 0..|
698          *       |      .      |      .      | 0 0 1..|
699          */
700 
701         // Initialize to the proper size identity matrix
702         final RealMatrix stm = MatrixUtils.createRealIdentityMatrix(correctedEstimate.getState().getDimension());
703 
704         // Derivatives of the state vector with respect to initial state vector
705         final int nbOrb = getNumberSelectedOrbitalDriversValuesToEstimate();
706         final RealMatrix dYdY0 = harvester.getB2(nominalMeanSpacecraftState);
707 
708         // Calculate transitional orbital matrix (See Ref [1], Eq. 3.4a)
709         final RealMatrix phi = dYdY0.multiply(phiS);
710 
711         // Fill the state transition matrix with the orbital drivers
712         final List<DelegatingDriver> drivers = builder.getOrbitalParametersDrivers().getDrivers();
713         for (int i = 0; i < nbOrb; ++i) {
714             if (drivers.get(i).isSelected()) {
715                 int jOrb = 0;
716                 for (int j = 0; j < nbOrb; ++j) {
717                     if (drivers.get(j).isSelected()) {
718                         stm.setEntry(i, jOrb++, phi.getEntry(i, j));
719                     }
720                 }
721             }
722         }
723 
724         // Update PhiS
725         phiS = new QRDecomposition(dYdY0).getSolver().getInverse();
726 
727         // Derivatives of the state vector with respect to propagation parameters
728         if (psiS != null) {
729 
730             final int nbProp = getNumberSelectedPropagationDriversValuesToEstimate();
731             final RealMatrix dYdPp = harvester.getB3(nominalMeanSpacecraftState);
732 
733             // Calculate transitional parameters matrix (See Ref [1], Eq. 3.4b)
734             final RealMatrix psi = dYdPp.subtract(phi.multiply(psiS));
735 
736             // Fill 1st row, 2nd column (dY/dPp)
737             for (int i = 0; i < nbOrb; ++i) {
738                 for (int j = 0; j < nbProp; ++j) {
739                     stm.setEntry(i, j + nbOrb, psi.getEntry(i, j));
740                 }
741             }
742 
743             // Update PsiS
744             psiS = dYdPp;
745 
746         }
747 
748         // Normalization of the STM
749         // normalized(STM)ij = STMij*Sj/Si
750         for (int i = 0; i < scale.length; i++) {
751             for (int j = 0; j < scale.length; j++ ) {
752                 stm.setEntry(i, j, stm.getEntry(i, j) * scale[j] / scale[i]);
753             }
754         }
755 
756         // Return the error state transition matrix
757         return stm;
758 
759     }
760 
761     /** Get the normalized measurement matrix H.
762      * H contains the partial derivatives of the measurement with respect to the state.
763      * H is an nxm matrix where n is the size of the measurement vector and m the size of the state vector.
764      * @return the normalized measurement matrix H
765      */
766     private RealMatrix getMeasurementMatrix() {
767 
768         // Observed measurement characteristics
769         final SpacecraftState        evaluationState     = predictedMeasurement.getStates()[0];
770         final ObservedMeasurement<?> observedMeasurement = predictedMeasurement.getObservedMeasurement();
771         final double[] sigma  = predictedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation();
772 
773         // Initialize measurement matrix H: nxm
774         // n: Number of measurements in current measurement
775         // m: State vector size
776         final RealMatrix measurementMatrix = MatrixUtils.
777                 createRealMatrix(observedMeasurement.getDimension(),
778                                  correctedEstimate.getState().getDimension());
779 
780         // Predicted orbit
781         final Orbit predictedOrbit = evaluationState.getOrbit();
782 
783         // Measurement matrix's columns related to orbital and propagation parameters
784         // ----------------------------------------------------------
785 
786         // Partial derivatives of the current Cartesian coordinates with respect to current orbital state
787         final int nbOrb  = getNumberSelectedOrbitalDrivers();
788         final int nbProp = getNumberSelectedPropagationDrivers();
789         final double[][] aCY = new double[nbOrb][nbOrb];
790         predictedOrbit.getJacobianWrtParameters(builder.getPositionAngleType(), aCY);
791         final RealMatrix dCdY = new Array2DRowRealMatrix(aCY, false);
792 
793         // Jacobian of the measurement with respect to current Cartesian coordinates
794         final RealMatrix dMdC = new Array2DRowRealMatrix(predictedMeasurement.getStateDerivatives(0), false);
795 
796         // Jacobian of the measurement with respect to current orbital state
797         RealMatrix dMdY = dMdC.multiply(dCdY);
798 
799         // Compute factor dShortPeriod_dMeanState = I+B1 | B4
800         final RealMatrix IpB1B4 = MatrixUtils.createRealMatrix(nbOrb, nbOrb + nbProp);
801 
802         // B1
803         final RealMatrix B1 = harvester.getB1();
804 
805         // I + B1
806         final RealMatrix I = MatrixUtils.createRealIdentityMatrix(nbOrb);
807         final RealMatrix IpB1 = I.add(B1);
808         IpB1B4.setSubMatrix(IpB1.getData(), 0, 0);
809 
810         // If there are not propagation parameters, B4 is null
811         if (psiS != null) {
812             final RealMatrix B4 = harvester.getB4();
813             IpB1B4.setSubMatrix(B4.getData(), 0, nbOrb);
814         }
815 
816         // Ref [1], Eq. 3.10
817         dMdY = dMdY.multiply(IpB1B4);
818 
819         for (int i = 0; i < dMdY.getRowDimension(); i++) {
820             for (int j = 0; j < nbOrb; j++) {
821                 final double driverScale = builder.getOrbitalParametersDrivers().getDrivers().get(j).getScale();
822                 measurementMatrix.setEntry(i, j, dMdY.getEntry(i, j) / sigma[i] * driverScale);
823             }
824 
825             int col = 0;
826             for (int j = 0; j < nbProp; j++) {
827                 final double driverScale = estimatedPropagationParameters.getDrivers().get(j).getScale();
828                 for (Span<Double> span = estimatedPropagationParameters.getDrivers().get(j).getValueSpanMap().getFirstSpan();
829                                   span != null; span = span.next()) {
830 
831                     measurementMatrix.setEntry(i, col + nbOrb,
832                                                dMdY.getEntry(i, col + nbOrb) / sigma[i] * driverScale);
833                     col++;
834                 }
835             }
836         }
837 
838         // Normalized measurement matrix's columns related to measurement parameters
839         // --------------------------------------------------------------
840 
841         // Jacobian of the measurement with respect to measurement parameters
842         // Gather the measurement parameters linked to current measurement
843         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
844             if (driver.isSelected()) {
845                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
846                     // Derivatives of current measurement w/r to selected measurement parameter
847                     final double[] aMPm = predictedMeasurement.getParameterDerivatives(driver, span.getStart());
848 
849                     // Check that the measurement parameter is managed by the filter
850                     if (measurementParameterColumns.get(span.getData()) != null) {
851                         // Column of the driver in the measurement matrix
852                         final int driverColumn = measurementParameterColumns.get(span.getData());
853 
854                         // Fill the corresponding indexes of the measurement matrix
855                         for (int i = 0; i < aMPm.length; ++i) {
856                             measurementMatrix.setEntry(i, driverColumn, aMPm[i] / sigma[i] * driver.getScale());
857                         }
858                     }
859                 }
860             }
861         }
862 
863         return measurementMatrix;
864     }
865 
866     /** Predict the filter correction for the new observation.
867      * @param stm normalized state transition matrix
868      * @return the predicted filter correction for the new observation
869      */
870     private RealVector predictFilterCorrection(final RealMatrix stm) {
871         // Ref [1], Eq. 3.5a and 3.5b
872         return stm.operate(correctedFilterCorrection);
873     }
874 
875     /** Compute the predicted osculating elements.
876      * @param filterCorrection kalman filter correction
877      * @return the predicted osculating element
878      */
879     private double[] computeOsculatingElements(final RealVector filterCorrection) {
880 
881         // Number of estimated orbital parameters
882         final int nbOrb = getNumberSelectedOrbitalDrivers();
883 
884         // B1
885         final RealMatrix B1 = harvester.getB1();
886 
887         // Short periodic terms
888         final double[] shortPeriodTerms = dsstPropagator.getShortPeriodTermsValue(nominalMeanSpacecraftState);
889 
890         // Physical filter correction
891         final RealVector physicalFilterCorrection = MatrixUtils.createRealVector(nbOrb);
892         for (int index = 0; index < nbOrb; index++) {
893             physicalFilterCorrection.addToEntry(index, filterCorrection.getEntry(index) * scale[index]);
894         }
895 
896         // B1 * physicalCorrection
897         final RealVector B1Correction = B1.operate(physicalFilterCorrection);
898 
899         // Nominal mean elements
900         final double[] nominalMeanElements = new double[nbOrb];
901         OrbitType.EQUINOCTIAL.mapOrbitToArray(nominalMeanSpacecraftState.getOrbit(), builder.getPositionAngleType(), nominalMeanElements, null);
902 
903         // Ref [1] Eq. 3.6
904         final double[] osculatingElements = new double[nbOrb];
905         for (int i = 0; i < nbOrb; i++) {
906             osculatingElements[i] = nominalMeanElements[i] +
907                                     physicalFilterCorrection.getEntry(i) +
908                                     shortPeriodTerms[i] +
909                                     B1Correction.getEntry(i);
910         }
911 
912         // Return
913         return osculatingElements;
914 
915     }
916 
917     /** Analytical computation of derivatives.
918      * This method allow to compute analytical derivatives.
919      * @param state mean state used to calculate short period perturbations
920      */
921     private void analyticalDerivativeComputations(final SpacecraftState state) {
922         harvester.setReferenceState(state);
923     }
924 
925     /** Get the number of estimated orbital parameters.
926      * @return the number of estimated orbital parameters
927      */
928     private int getNumberSelectedOrbitalDrivers() {
929         return estimatedOrbitalParameters.getNbParams();
930     }
931 
932     /** Get the number of estimated propagation parameters.
933      * @return the number of estimated propagation parameters
934      */
935     private int getNumberSelectedPropagationDrivers() {
936         return estimatedPropagationParameters.getNbParams();
937     }
938 
939     /** Get the number of estimated orbital parameters values (some parameter
940      * driver may have several values to estimate for different time range
941      * {@link ParameterDriver}.
942      * @return the number of estimated values for , orbital parameters
943      */
944     private int getNumberSelectedOrbitalDriversValuesToEstimate() {
945         int nbOrbitalValuesToEstimate = 0;
946         for (final ParameterDriver driver : estimatedOrbitalParameters.getDrivers()) {
947             nbOrbitalValuesToEstimate += driver.getNbOfValues();
948         }
949         return nbOrbitalValuesToEstimate;
950     }
951 
952     /** Get the number of estimated propagation parameters values (some parameter
953      * driver may have several values to estimate for different time range
954      * {@link ParameterDriver}.
955      * @return the number of estimated values for propagation parameters
956      */
957     private int getNumberSelectedPropagationDriversValuesToEstimate() {
958         int nbPropagationValuesToEstimate = 0;
959         for (final ParameterDriver driver : estimatedPropagationParameters.getDrivers()) {
960             nbPropagationValuesToEstimate += driver.getNbOfValues();
961         }
962         return nbPropagationValuesToEstimate;
963     }
964 
965     /** Get the number of estimated measurement parameters values (some parameter
966      * driver may have several values to estimate for different time range
967      * {@link ParameterDriver}.
968      * @return the number of estimated values for measurement parameters
969      */
970     private int getNumberSelectedMeasurementDriversValuesToEstimate() {
971         int nbMeasurementValuesToEstimate = 0;
972         for (final ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
973             nbMeasurementValuesToEstimate += driver.getNbOfValues();
974         }
975         return nbMeasurementValuesToEstimate;
976     }
977 
978     /** Update the estimated parameters after the correction phase of the filter.
979      * The min/max allowed values are handled by the parameter themselves.
980      */
981     private void updateParameters() {
982         final RealVector correctedState = correctedEstimate.getState();
983         int i = 0;
984         // Orbital parameters
985         for (final DelegatingDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
986             // let the parameter handle min/max clipping
987             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
988                 driver.setNormalizedValue(driver.getNormalizedValue(span.getStart()) + correctedState.getEntry(i++), span.getStart());
989             }
990         }
991 
992         // Propagation parameters
993         for (final DelegatingDriver driver : getEstimatedPropagationParameters().getDrivers()) {
994             // let the parameter handle min/max clipping
995             // If the parameter driver contains only 1 value to estimate over the all time range
996             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
997                 driver.setNormalizedValue(driver.getNormalizedValue(span.getStart()) + correctedState.getEntry(i++), span.getStart());
998             }
999         }
1000 
1001         // Measurements parameters
1002         for (final DelegatingDriver driver : getEstimatedMeasurementsParameters().getDrivers()) {
1003             // let the parameter handle min/max clipping
1004             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
1005                 driver.setNormalizedValue(driver.getNormalizedValue(span.getStart()) + correctedState.getEntry(i++), span.getStart());
1006             }
1007         }
1008     }
1009 
1010 }