1   /* Copyright 2002-2022 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.propagation.analytical;
18  
19  import java.util.Arrays;
20  import java.util.List;
21  
22  import org.hipparchus.analysis.differentiation.Gradient;
23  import org.hipparchus.linear.MatrixUtils;
24  import org.hipparchus.linear.RealMatrix;
25  import org.orekit.orbits.FieldOrbit;
26  import org.orekit.propagation.AbstractMatricesHarvester;
27  import org.orekit.propagation.AdditionalStateProvider;
28  import org.orekit.propagation.FieldSpacecraftState;
29  import org.orekit.propagation.SpacecraftState;
30  import org.orekit.time.AbsoluteDate;
31  import org.orekit.time.FieldAbsoluteDate;
32  import org.orekit.utils.DoubleArrayDictionary;
33  import org.orekit.utils.FieldPVCoordinates;
34  import org.orekit.utils.ParameterDriver;
35  
36  /**
37   * Base class harvester between two-dimensional Jacobian
38   * matrices and analytical orbit propagator.
39   * @author Thomas Paulet
40   * @author Bryan Cazabonne
41   * @since 11.1
42   */
43  public abstract class AbstractAnalyticalMatricesHarvester extends AbstractMatricesHarvester implements AdditionalStateProvider {
44  
45      /** Columns names for parameters. */
46      private List<String> columnsNames;
47  
48      /** Epoch of the last computed state transition matrix. */
49      private AbsoluteDate epoch;
50  
51      /** Analytical derivatives that apply to State Transition Matrix. */
52      private final double[][] analyticalDerivativesStm;
53  
54      /** Analytical derivatives that apply to Jacobians columns. */
55      private final DoubleArrayDictionary analyticalDerivativesJacobianColumns;
56  
57      /** Propagator bound to this harvester. */
58      private final AbstractAnalyticalPropagator propagator;
59  
60      /** Simple constructor.
61       * <p>
62       * The arguments for initial matrices <em>must</em> be compatible with the
63       * {@link org.orekit.orbits.OrbitType orbit type}
64       * and {@link org.orekit.orbits.PositionAngle position angle} that will be used by propagator
65       * </p>
66       * @param propagator propagator bound to this harvester
67       * @param stmName State Transition Matrix state name
68       * @param initialStm initial State Transition Matrix ∂Y/∂Y₀,
69       * if null (which is the most frequent case), assumed to be 6x6 identity
70       * @param initialJacobianColumns initial columns of the Jacobians matrix with respect to parameters,
71       * if null or if some selected parameters are missing from the dictionary, the corresponding
72       * initial column is assumed to be 0
73       */
74      protected AbstractAnalyticalMatricesHarvester(final AbstractAnalyticalPropagator propagator, final String stmName,
75                                                    final RealMatrix initialStm, final DoubleArrayDictionary initialJacobianColumns) {
76          super(stmName, initialStm, initialJacobianColumns);
77          this.propagator                           = propagator;
78          this.epoch                                = propagator.getInitialState().getDate();
79          this.columnsNames                         = null;
80          this.analyticalDerivativesStm             = getInitialStateTransitionMatrix().getData();
81          this.analyticalDerivativesJacobianColumns = new DoubleArrayDictionary();
82      }
83  
84      /** {@inheritDoc} */
85      @Override
86      public List<String> getJacobiansColumnsNames() {
87          return columnsNames == null ? propagator.getJacobiansColumnsNames() : columnsNames;
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public void freezeColumnsNames() {
93          columnsNames = getJacobiansColumnsNames();
94      }
95  
96      /** {@inheritDoc} */
97      @Override
98      public String getName() {
99          return getStmName();
100     }
101 
102     /** {@inheritDoc} */
103     @Override
104     public double[] getAdditionalState(final SpacecraftState state) {
105         // Update the partial derivatives if needed
106         updateDerivativesIfNeeded(state);
107         // Return the state transition matrix in an array
108         return toArray(analyticalDerivativesStm);
109     }
110 
111     /** {@inheritDoc} */
112     @Override
113     public RealMatrix getStateTransitionMatrix(final SpacecraftState state) {
114         // Check if additional state is defined
115         if (!state.hasAdditionalState(getName())) {
116             return null;
117         }
118         // Return the state transition matrix
119         return toRealMatrix(state.getAdditionalState(getName()));
120     }
121 
122     /** {@inheritDoc} */
123     @Override
124     public RealMatrix getParametersJacobian(final SpacecraftState state) {
125         // Update the partial derivatives if needed
126         updateDerivativesIfNeeded(state);
127 
128         // Estimated parameters
129         final List<String> names = getJacobiansColumnsNames();
130         if (names == null || names.isEmpty()) {
131             return null;
132         }
133 
134         // Initialize Jacobian
135         final RealMatrix dYdP = MatrixUtils.createRealMatrix(STATE_DIMENSION, names.size());
136 
137         // Add the derivatives
138         for (int j = 0; j < names.size(); ++j) {
139             final double[] column = analyticalDerivativesJacobianColumns.get(names.get(j));
140             if (column != null) {
141                 for (int i = 0; i < STATE_DIMENSION; i++) {
142                     dYdP.addToEntry(i, j, column[i]);
143                 }
144             }
145         }
146 
147         // Return
148         return dYdP;
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     public void setReferenceState(final SpacecraftState reference) {
154 
155         // reset derivatives to zero
156         for (final double[] row : analyticalDerivativesStm) {
157             Arrays.fill(row, 0.0);
158         }
159         analyticalDerivativesJacobianColumns.clear();
160 
161         final AbstractAnalyticalGradientConverter converter           = getGradientConverter();
162         final FieldSpacecraftState<Gradient> gState                   = converter.getState();
163         final Gradient[] gParameters                                  = converter.getParameters(gState);
164         final FieldAbstractAnalyticalPropagator<Gradient> gPropagator = converter.getPropagator(gState, gParameters);
165 
166         // Compute Jacobian
167         final AbsoluteDate target               = reference.getDate();
168         final FieldAbsoluteDate<Gradient> start = gPropagator.getInitialState().getDate();
169         final double dt                         = target.durationFrom(start.toAbsoluteDate());
170         final FieldOrbit<Gradient> gOrbit       = gPropagator.propagateOrbit(start.shiftedBy(dt), gParameters);
171         final FieldPVCoordinates<Gradient> gPv  = gOrbit.getPVCoordinates();
172 
173         final double[] derivativesX   = gPv.getPosition().getX().getGradient();
174         final double[] derivativesY   = gPv.getPosition().getY().getGradient();
175         final double[] derivativesZ   = gPv.getPosition().getZ().getGradient();
176         final double[] derivativesVx  = gPv.getVelocity().getX().getGradient();
177         final double[] derivativesVy  = gPv.getVelocity().getY().getGradient();
178         final double[] derivativesVz  = gPv.getVelocity().getZ().getGradient();
179 
180         // Update Jacobian with respect to state
181         addToRow(derivativesX,  0);
182         addToRow(derivativesY,  1);
183         addToRow(derivativesZ,  2);
184         addToRow(derivativesVx, 3);
185         addToRow(derivativesVy, 4);
186         addToRow(derivativesVz, 5);
187 
188         // Partial derivatives of the state with respect to propagation parameters
189         int paramsIndex = converter.getFreeStateParameters();
190         for (ParameterDriver driver : converter.getParametersDrivers()) {
191             if (driver.isSelected()) {
192 
193                 // get the partials derivatives for this driver
194                 DoubleArrayDictionary.Entry entry = analyticalDerivativesJacobianColumns.getEntry(driver.getName());
195                 if (entry == null) {
196                     // create an entry filled with zeroes
197                     analyticalDerivativesJacobianColumns.put(driver.getName(), new double[STATE_DIMENSION]);
198                     entry = analyticalDerivativesJacobianColumns.getEntry(driver.getName());
199                 }
200 
201                 // add the contribution of the current force model
202                 entry.increment(new double[] {
203                     derivativesX[paramsIndex], derivativesY[paramsIndex], derivativesZ[paramsIndex],
204                     derivativesVx[paramsIndex], derivativesVy[paramsIndex], derivativesVz[paramsIndex]
205                 });
206                 ++paramsIndex;
207 
208             }
209         }
210 
211         // Update the epoch of the last computed partial derivatives
212         epoch = target;
213 
214     }
215 
216     /** Update the partial derivatives (if needed).
217      * @param state current spacecraft state
218      */
219     private void updateDerivativesIfNeeded(final SpacecraftState state) {
220         if (!state.getDate().isEqualTo(epoch)) {
221             setReferenceState(state);
222         }
223     }
224 
225     /** Fill State Transition Matrix rows.
226      * @param derivatives derivatives of a component
227      * @param index component index
228      */
229     private void addToRow(final double[] derivatives, final int index) {
230         for (int i = 0; i < 6; i++) {
231             analyticalDerivativesStm[index][i] += derivatives[i];
232         }
233     }
234 
235     /** Convert an array to a matrix (6x6 dimension).
236      * @param array input array
237      * @return the corresponding matrix
238      */
239     private RealMatrix toRealMatrix(final double[] array) {
240         final RealMatrix matrix = MatrixUtils.createRealMatrix(STATE_DIMENSION, STATE_DIMENSION);
241         int index = 0;
242         for (int i = 0; i < STATE_DIMENSION; ++i) {
243             for (int j = 0; j < STATE_DIMENSION; ++j) {
244                 matrix.setEntry(i, j, array[index++]);
245             }
246         }
247         return matrix;
248     }
249 
250     /** Set the STM data into an array.
251      * @param matrix STM matrix
252      * @return an array containing the STM data
253      */
254     private double[] toArray(final double[][] matrix) {
255         final double[] array = new double[STATE_DIMENSION * STATE_DIMENSION];
256         int index = 0;
257         for (int i = 0; i < STATE_DIMENSION; ++i) {
258             final double[] row = matrix[i];
259             for (int j = 0; j < STATE_DIMENSION; ++j) {
260                 array[index++] = row[j];
261             }
262         }
263         return array;
264     }
265 
266     /**
267      * Get the gradient converter related to the analytical orbit propagator.
268      * @return the gradient converter
269      */
270     public abstract AbstractAnalyticalGradientConverter getGradientConverter();
271 
272 }