1 /* Copyright 2002-2021 CS GROUP
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.propagation.semianalytical.dsst;
18
19 import java.util.IdentityHashMap;
20 import java.util.Map;
21
22 import org.hipparchus.analysis.differentiation.Gradient;
23 import org.orekit.errors.OrekitException;
24 import org.orekit.errors.OrekitMessages;
25 import org.orekit.propagation.FieldSpacecraftState;
26 import org.orekit.propagation.PropagationType;
27 import org.orekit.propagation.SpacecraftState;
28 import org.orekit.propagation.integration.AdditionalEquations;
29 import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
30 import org.orekit.propagation.semianalytical.dsst.utilities.FieldAuxiliaryElements;
31 import org.orekit.utils.ParameterDriver;
32 import org.orekit.utils.ParameterDriversList;
33
34 /** Set of {@link AdditionalEquations additional equations} computing the partial derivatives
35 * of the state (orbit) with respect to initial state and force models parameters.
36 * <p>
37 * This set of equations are automatically added to a {@link DSSTPropagator DSST propagator}
38 * in order to compute partial derivatives of the orbit along with the orbit itself. This is
39 * useful for example in orbit determination applications.
40 * </p>
41 * <p>
42 * The partial derivatives with respect to initial state are dimension 6 (orbit only).
43 * </p>
44 * <p>
45 * The partial derivatives with respect to force models parameters has a dimension
46 * equal to the number of selected parameters. Parameters selection is implemented at
47 * {@link DSSTForceModel DSST force models} level. Users must retrieve a {@link ParameterDriver
48 * parameter driver} by looping on all drivers using {@link DSSTForceModel#getParametersDrivers()}
49 * and then select it by calling {@link ParameterDriver#setSelected(boolean) setSelected(true)}.
50 * </p>
51 * @author Bryan Cazabonne
52 * @since 10.0
53 */
54 public class DSSTPartialDerivativesEquations implements AdditionalEquations {
55
56 /** Retrograde factor I.
57 * <p>
58 * DSST model needs equinoctial orbit as internal representation.
59 * Classical equinoctial elements have discontinuities when inclination
60 * is close to zero. In this representation, I = +1. <br>
61 * To avoid this discontinuity, another representation exists and equinoctial
62 * elements can be expressed in a different way, called "retrograde" orbit.
63 * This implies I = -1. <br>
64 * As Orekit doesn't implement the retrograde orbit, I is always set to +1.
65 * But for the sake of consistency with the theory, the retrograde factor
66 * has been kept in the formulas.
67 * </p>
68 */
69 private static final int I = 1;
70
71 /** Propagator computing state evolution. */
72 private final DSSTPropagator propagator;
73
74 /** Selected parameters for Jacobian computation. */
75 private ParameterDriversList selected;
76
77 /** Parameters map. */
78 private Map<ParameterDriver, Integer> map;
79
80 /** Name. */
81 private final String name;
82
83 /** Flag for Jacobian matrices initialization. */
84 private boolean initialized;
85
86 /** Type of the orbit used for the propagation.*/
87 private PropagationType propagationType;
88
89 /** Simple constructor.
90 * <p>
91 * Upon construction, this set of equations is <em>automatically</em> added to
92 * the propagator by calling its {@link
93 * DSSTPropagator#addAdditionalEquations(AdditionalEquations)} method. So
94 * there is no need to call this method explicitly for these equations.
95 * </p>
96 * @param name name of the partial derivatives equations
97 * @param propagator the propagator that will handle the orbit propagation
98 * @param propagationType type of the orbit used for the propagation (mean or osculating)
99 */
100 public DSSTPartialDerivativesEquations(final String name,
101 final DSSTPropagator propagator,
102 final PropagationType propagationType) {
103 this.name = name;
104 this.selected = null;
105 this.map = null;
106 this.propagator = propagator;
107 this.initialized = false;
108 this.propagationType = propagationType;
109 propagator.addAdditionalEquations(this);
110 }
111
112 /** {@inheritDoc} */
113 public String getName() {
114 return name;
115 }
116
117 /** Freeze the selected parameters from the force models.
118 */
119 private void freezeParametersSelection() {
120 if (selected == null) {
121
122 // first pass: gather all parameters, binding similar names together
123 selected = new ParameterDriversList();
124 for (final DSSTForceModel provider : propagator.getAllForceModels()) {
125 for (final ParameterDriver driver : provider.getParametersDrivers()) {
126 selected.add(driver);
127 }
128 }
129
130 // second pass: now that shared parameter names are bound together,
131 // their selections status have been synchronized, we can filter them
132 selected.filter(true);
133
134 // third pass: sort parameters lexicographically
135 selected.sort();
136
137 // fourth pass: set up a map between parameters drivers and matrices columns
138 map = new IdentityHashMap<ParameterDriver, Integer>();
139 int parameterIndex = 0;
140 for (final ParameterDriver selectedDriver : selected.getDrivers()) {
141 for (final DSSTForceModel provider : propagator.getAllForceModels()) {
142 for (final ParameterDriver driver : provider.getParametersDrivers()) {
143 if (driver.getName().equals(selectedDriver.getName())) {
144 map.put(driver, parameterIndex);
145 }
146 }
147 }
148 ++parameterIndex;
149 }
150
151 }
152 }
153
154 /** Set the initial value of the Jacobian with respect to state and parameter.
155 * <p>
156 * This method is equivalent to call {@link #setInitialJacobians(SpacecraftState,
157 * double[][], double[][])} with dYdY0 set to the identity matrix and dYdP set
158 * to a zero matrix.
159 * </p>
160 * <p>
161 * The force models parameters for which partial derivatives are desired,
162 * <em>must</em> have been {@link ParameterDriver#setSelected(boolean) selected}
163 * before this method is called, so proper matrices dimensions are used.
164 * </p>
165 * @param s0 initial state
166 * @return state with initial Jacobians added
167 */
168 public SpacecraftState setInitialJacobians(final SpacecraftState s0) {
169 freezeParametersSelection();
170 final int stateDimension = 6;
171 final double[][] dYdY0 = new double[stateDimension][stateDimension];
172 final double[][] dYdP = new double[stateDimension][selected.getNbParams()];
173 for (int i = 0; i < stateDimension; ++i) {
174 dYdY0[i][i] = 1.0;
175 }
176 return setInitialJacobians(s0, dYdY0, dYdP);
177 }
178
179 /** Set the initial value of the Jacobian with respect to state and parameter.
180 * <p>
181 * The returned state must be added to the propagator (it is not done
182 * automatically, as the user may need to add more states to it).
183 * </p>
184 * <p>
185 * The force models parameters for which partial derivatives are desired,
186 * <em>must</em> have been {@link ParameterDriver#setSelected(boolean) selected}
187 * before this method is called, and the {@code dY1dP} matrix dimension <em>must</em>
188 * be consistent with the selection.
189 * </p>
190 * @param s1 current state
191 * @param dY1dY0 Jacobian of current state at time t₁ with respect
192 * to state at some previous time t₀ (must be 6x6)
193 * @param dY1dP Jacobian of current state at time t₁ with respect
194 * to parameters (may be null if no parameters are selected)
195 * @return state with initial Jacobians added
196 */
197 public SpacecraftState setInitialJacobians(final SpacecraftState s1,
198 final double[][] dY1dY0, final double[][] dY1dP) {
199
200 freezeParametersSelection();
201
202 // Check dimensions
203 final int stateDim = dY1dY0.length;
204 if (stateDim != 6 || stateDim != dY1dY0[0].length) {
205 throw new OrekitException(OrekitMessages.STATE_JACOBIAN_NOT_6X6,
206 stateDim, dY1dY0[0].length);
207 }
208 if (dY1dP != null && stateDim != dY1dP.length) {
209 throw new OrekitException(OrekitMessages.STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH,
210 stateDim, dY1dP.length);
211 }
212 if (dY1dP == null && selected.getNbParams() != 0 ||
213 dY1dP != null && selected.getNbParams() != dY1dP[0].length) {
214 throw new OrekitException(new OrekitException(OrekitMessages.INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH,
215 dY1dP == null ? 0 : dY1dP[0].length, selected.getNbParams()));
216 }
217
218 // store the matrices as a single dimension array
219 initialized = true;
220 final DSSTJacobiansMapper mapper = getMapper();
221 final double[] p = new double[mapper.getAdditionalStateDimension()];
222 mapper.setInitialJacobians(s1, dY1dY0, dY1dP, p);
223
224 // set value in propagator
225 return s1.addAdditionalState(name, p);
226
227 }
228
229 /** Get a mapper between two-dimensional Jacobians and one-dimensional additional state.
230 * @return a mapper between two-dimensional Jacobians and one-dimensional additional state,
231 * with the same name as the instance
232 * @see #setInitialJacobians(SpacecraftState)
233 * @see #setInitialJacobians(SpacecraftState, double[][], double[][])
234 */
235 public DSSTJacobiansMapper getMapper() {
236 if (!initialized) {
237 throw new OrekitException(OrekitMessages.STATE_JACOBIAN_NOT_INITIALIZED);
238 }
239 return new DSSTJacobiansMapper(name, selected, propagator, map, propagationType);
240 }
241
242 /** {@inheritDoc} */
243 public double[] computeDerivatives(final SpacecraftState s, final double[] pDot) {
244
245 // initialize Jacobians to zero
246 final int paramDim = selected.getNbParams();
247 final int dim = 6;
248 final double[][] dMeanElementRatedParam = new double[dim][paramDim];
249 final double[][] dMeanElementRatedElement = new double[dim][dim];
250 final DSSTGradientConverter converter = new DSSTGradientConverter(s, propagator.getAttitudeProvider());
251
252 // Compute Jacobian
253 for (final DSSTForceModel forceModel : propagator.getAllForceModels()) {
254
255 final FieldSpacecraftState<Gradient> dsState = converter.getState(forceModel);
256 final Gradient[] parameters = converter.getParameters(dsState, forceModel);
257 final FieldAuxiliaryElements<Gradient> auxiliaryElements = new FieldAuxiliaryElements<>(dsState.getOrbit(), I);
258
259 // "field" initialization of the force model if it was not done before
260 forceModel.initializeShortPeriodTerms(auxiliaryElements, propagationType, parameters);
261 final Gradient[] meanElementRate = forceModel.getMeanElementRate(dsState, auxiliaryElements, parameters);
262 final double[] derivativesA = meanElementRate[0].getGradient();
263 final double[] derivativesEx = meanElementRate[1].getGradient();
264 final double[] derivativesEy = meanElementRate[2].getGradient();
265 final double[] derivativesHx = meanElementRate[3].getGradient();
266 final double[] derivativesHy = meanElementRate[4].getGradient();
267 final double[] derivativesL = meanElementRate[5].getGradient();
268
269 // update Jacobian with respect to state
270 addToRow(derivativesA, 0, dMeanElementRatedElement);
271 addToRow(derivativesEx, 1, dMeanElementRatedElement);
272 addToRow(derivativesEy, 2, dMeanElementRatedElement);
273 addToRow(derivativesHx, 3, dMeanElementRatedElement);
274 addToRow(derivativesHy, 4, dMeanElementRatedElement);
275 addToRow(derivativesL, 5, dMeanElementRatedElement);
276
277 int index = converter.getFreeStateParameters();
278 for (ParameterDriver driver : forceModel.getParametersDrivers()) {
279 if (driver.isSelected()) {
280 final int parameterIndex = map.get(driver);
281 dMeanElementRatedParam[0][parameterIndex] += derivativesA[index];
282 dMeanElementRatedParam[1][parameterIndex] += derivativesEx[index];
283 dMeanElementRatedParam[2][parameterIndex] += derivativesEy[index];
284 dMeanElementRatedParam[3][parameterIndex] += derivativesHx[index];
285 dMeanElementRatedParam[4][parameterIndex] += derivativesHy[index];
286 dMeanElementRatedParam[5][parameterIndex] += derivativesL[index];
287 ++index;
288 }
289 }
290
291 }
292
293 // The variational equations of the complete state Jacobian matrix have the following form:
294
295 // [ Adot ] = [ dMeanElementRatedElement ] * [ A ]
296
297 // The A matrix and its derivative (Adot) are 6 * 6 matrices
298
299 // The following loops compute these expression taking care of the mapping of the
300 // A matrix into the single dimension array p and of the mapping of the
301 // Adot matrix into the single dimension array pDot.
302
303 final double[] p = s.getAdditionalState(getName());
304
305 for (int i = 0; i < dim; i++) {
306 final double[] dMeanElementRatedElementi = dMeanElementRatedElement[i];
307 for (int j = 0; j < dim; j++) {
308 pDot[j + dim * i] =
309 dMeanElementRatedElementi[0] * p[j] + dMeanElementRatedElementi[1] * p[j + dim] + dMeanElementRatedElementi[2] * p[j + 2 * dim] +
310 dMeanElementRatedElementi[3] * p[j + 3 * dim] + dMeanElementRatedElementi[4] * p[j + 4 * dim] + dMeanElementRatedElementi[5] * p[j + 5 * dim];
311 }
312 }
313
314 final int columnTop = dim * dim;
315 for (int k = 0; k < paramDim; k++) {
316 // the variational equations of the parameters Jacobian matrix are computed
317 // one column at a time, they have the following form:
318
319 // [ Bdot ] = [ dMeanElementRatedElement ] * [ B ] + [ dMeanElementRatedParam ]
320
321 // The B sub-columns and its derivative (Bdot) are 6 elements columns.
322
323 // The following loops compute this expression taking care of the mapping of the
324 // B columns into the single dimension array p and of the mapping of the
325 // Bdot columns into the single dimension array pDot.
326
327 for (int i = 0; i < dim; ++i) {
328 final double[] dMeanElementRatedElementi = dMeanElementRatedElement[i];
329 pDot[columnTop + (i + dim * k)] =
330 dMeanElementRatedParam[i][k] +
331 dMeanElementRatedElementi[0] * p[columnTop + k] + dMeanElementRatedElementi[1] * p[columnTop + k + paramDim] + dMeanElementRatedElementi[2] * p[columnTop + k + 2 * paramDim] +
332 dMeanElementRatedElementi[3] * p[columnTop + k + 3 * paramDim] + dMeanElementRatedElementi[4] * p[columnTop + k + 4 * paramDim] + dMeanElementRatedElementi[5] * p[columnTop + k + 5 * paramDim];
333 }
334 }
335
336 // these equations have no effect on the main state itself
337 return null;
338
339 }
340
341 /** Fill Jacobians rows.
342 * @param derivatives derivatives of a component
343 * @param index component index (0 for a, 1 for ex, 2 for ey, 3 for hx, 4 for hy, 5 for l)
344 * @param dMeanElementRatedElement Jacobian of mean elements rate with respect to mean elements
345 */
346 private void addToRow(final double[] derivatives, final int index,
347 final double[][] dMeanElementRatedElement) {
348
349 for (int i = 0; i < 6; i++) {
350 dMeanElementRatedElement[index][i] += derivatives[i];
351 }
352
353 }
354
355 }