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