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