1   /* Copyright 2002-2025 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.io.IOException;
20  import java.text.ParseException;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.List;
24  
25  import org.hipparchus.CalculusFieldElement;
26  import org.hipparchus.Field;
27  import org.hipparchus.analysis.differentiation.Gradient;
28  import org.hipparchus.util.Binary64Field;
29  import org.hipparchus.util.FastMath;
30  import org.hipparchus.util.MathArrays;
31  import org.junit.jupiter.api.Assertions;
32  import org.junit.jupiter.api.BeforeEach;
33  import org.junit.jupiter.api.Test;
34  import org.orekit.Utils;
35  import org.orekit.attitudes.Attitude;
36  import org.orekit.bodies.CelestialBodyFactory;
37  import org.orekit.bodies.OneAxisEllipsoid;
38  import org.orekit.forces.gravity.potential.GravityFieldFactory;
39  import org.orekit.forces.gravity.potential.ICGEMFormatReader;
40  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
41  import org.orekit.frames.Frame;
42  import org.orekit.frames.FramesFactory;
43  import org.orekit.orbits.EquinoctialOrbit;
44  import org.orekit.orbits.FieldEquinoctialOrbit;
45  import org.orekit.orbits.FieldKeplerianOrbit;
46  import org.orekit.orbits.FieldOrbit;
47  import org.orekit.orbits.Orbit;
48  import org.orekit.orbits.OrbitType;
49  import org.orekit.orbits.PositionAngleType;
50  import org.orekit.propagation.FieldSpacecraftState;
51  import org.orekit.propagation.PropagationType;
52  import org.orekit.propagation.SpacecraftState;
53  import org.orekit.propagation.ToleranceProvider;
54  import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
55  import org.orekit.propagation.semianalytical.dsst.forces.DSSTNewtonianAttraction;
56  import org.orekit.propagation.semianalytical.dsst.forces.DSSTTesseral;
57  import org.orekit.propagation.semianalytical.dsst.forces.FieldShortPeriodTerms;
58  import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
59  import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
60  import org.orekit.propagation.semianalytical.dsst.utilities.FieldAuxiliaryElements;
61  import org.orekit.time.AbsoluteDate;
62  import org.orekit.time.DateComponents;
63  import org.orekit.time.FieldAbsoluteDate;
64  import org.orekit.time.TimeComponents;
65  import org.orekit.time.TimeScalesFactory;
66  import org.orekit.utils.Constants;
67  import org.orekit.utils.ParameterDriver;
68  import org.orekit.utils.ParameterDriversList;
69  
70  public class FieldDSSTTesseralTest {
71  
72      @Test
73      public void testGetMeanElementRate(){
74          doTestGetMeanElementRate(Binary64Field.getInstance());
75      }
76  
77      private <T extends CalculusFieldElement<T>> void doTestGetMeanElementRate(final Field<T> field) {
78  
79          final T zero = field.getZero();
80          // Central Body geopotential 4x4
81          final UnnormalizedSphericalHarmonicsProvider provider =
82                  GravityFieldFactory.getUnnormalizedProvider(4, 4);
83  
84          final Frame frame = FramesFactory.getEME2000();
85          final Frame earthFrame = CelestialBodyFactory.getEarth().getBodyOrientedFrame();
86          final FieldAbsoluteDate<T> initDate = new FieldAbsoluteDate<>(field, 2007, 04, 16, 0, 46, 42.400, TimeScalesFactory.getUTC());
87  
88          // a  = 26559890 m
89          // ey = 0.0041543085910249414
90          // ex = 2.719455286199036E-4
91          // hy = 0.3960084733107685
92          // hx = -0.3412974060023717
93          // lM = 8.566537840341699 rad
94          final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(zero.add(2.655989E7),
95                                                                  zero.add(2.719455286199036E-4),
96                                                                  zero.add(0.0041543085910249414),
97                                                                  zero.add(-0.3412974060023717),
98                                                                  zero.add(0.3960084733107685),
99                                                                  zero.add(8.566537840341699),
100                                                                 PositionAngleType.TRUE,
101                                                                 frame,
102                                                                 initDate,
103                                                                 zero.add(3.986004415E14));
104 
105         final T mass = zero.add(1000.0);
106         final FieldSpacecraftState<T> state = new FieldSpacecraftState<>(orbit, mass);
107 
108         final DSSTForceModel tesseral = new DSSTTesseral(earthFrame,
109                                                          Constants.WGS84_EARTH_ANGULAR_VELOCITY, provider,
110                                                          4, 4, 4, 8, 4, 4, 2);
111 
112         final FieldAuxiliaryElements<T> auxiliaryElements = new FieldAuxiliaryElements<>(state.getOrbit(), 1);
113 
114         // Force model parameters
115         final T[] parameters = tesseral.getParameters(field, state.getDate());
116         // Initialize force model
117         tesseral.initializeShortPeriodTerms(auxiliaryElements,
118                             PropagationType.MEAN, parameters);
119 
120         final T[] elements = MathArrays.buildArray(field, 7);
121         Arrays.fill(elements, zero);
122 
123         final T[] daidt = tesseral.getMeanElementRate(state, auxiliaryElements, parameters);
124         for (int i = 0; i < daidt.length; i++) {
125             elements[i] = daidt[i];
126         }
127 
128         Assertions.assertEquals(7.12557687065243E-05 , elements[0].getReal(), 6.0e-19);
129         Assertions.assertEquals(-1.11351345747909E-11, elements[1].getReal(), 2.0e-26);
130         Assertions.assertEquals(2.302319084099072E-11, elements[2].getReal(), 1.5e-26);
131         Assertions.assertEquals(2.499448456499174E-12, elements[3].getReal(), 1.0e-27);
132         Assertions.assertEquals(1.38138527141734E-13 , elements[4].getReal(), 3.0e-27);
133         Assertions.assertEquals(5.81588304559558E-12 , elements[5].getReal(), 1.0e-26);
134 
135     }
136 
137     @Test
138     public void testShortPeriodTerms() {
139         doTestShortPeriodTerms(Binary64Field.getInstance());
140     }
141 
142     @SuppressWarnings("unchecked")
143     private <T extends CalculusFieldElement<T>> void doTestShortPeriodTerms(final Field<T> field) {
144 
145         final T zero = field.getZero();
146         Utils.setDataRoot("regular-data:potential/icgem-format");
147         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("^eigen-6s-truncated$", false));
148         UnnormalizedSphericalHarmonicsProvider nshp = GravityFieldFactory.getUnnormalizedProvider(8, 8);
149         FieldOrbit<T> orbit = new FieldKeplerianOrbit<>(zero.add(13378000),
150                                                         zero.add(0.05),
151                                                         zero,
152                                                         zero,
153                                                         zero.add(FastMath.PI),
154                                                         zero,
155                                                         PositionAngleType.MEAN,
156                                                         FramesFactory.getTOD(false),
157                                                         new FieldAbsoluteDate<>(field, 2003, 5, 6, TimeScalesFactory.getUTC()),
158                                                         zero.add(nshp.getMu()));
159 
160         OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
161                                                       Constants.WGS84_EARTH_FLATTENING,
162                                                       FramesFactory.getGTOD(false));
163 
164         // Force model
165         final DSSTForceModel force = new DSSTTesseral(earth.getBodyFrame(),
166                                                       Constants.WGS84_EARTH_ANGULAR_VELOCITY,
167                                                       nshp, 8, 8, 4, 12, 8, 8, 4);
168 
169         // Initial state
170         final FieldSpacecraftState<T> meanState = new FieldSpacecraftState<>(orbit, zero.add(45.0));
171 
172         //Create the auxiliary object
173         final FieldAuxiliaryElements<T> aux = new FieldAuxiliaryElements<>(orbit, 1);
174 
175         final List<FieldShortPeriodTerms<T>> shortPeriodTerms = new ArrayList<FieldShortPeriodTerms<T>>();
176 
177         force.registerAttitudeProvider(null);
178         shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters(field, orbit.getDate())));
179         force.updateShortPeriodTerms(force.getParametersAllValues(field), meanState);
180         
181         T[] y = MathArrays.buildArray(field, 6);
182         Arrays.fill(y, zero);
183         for (final FieldShortPeriodTerms<T> spt : shortPeriodTerms) {
184             final T[] shortPeriodic = spt.value(meanState.getOrbit());
185             for (int i = 0; i < shortPeriodic.length; i++) {
186                 y[i] = y[i].add(shortPeriodic[i]);
187             }
188         }
189 
190         Assertions.assertEquals(5.192409957353241    ,      y[0].getReal(), 1.e-15);
191         Assertions.assertEquals(9.660364749662038E-7 ,   y[1].getReal(), 1.e-22);
192         Assertions.assertEquals(1.5420089871620561E-6,   y[2].getReal(), 1.e-21);
193         Assertions.assertEquals(-4.99441460131262E-8 , y[3].getReal(), 1.e-22);
194         Assertions.assertEquals(-4.500974242661193E-8,  y[4].getReal(), 1.e-22);
195         Assertions.assertEquals(-2.785213556107623E-7,  y[5].getReal(), 1.e-21);
196     }
197 
198     @Test
199     public void testIssue625() {
200         doTestIssue625(Binary64Field.getInstance());
201     }
202 
203     private <T extends CalculusFieldElement<T>> void doTestIssue625(final Field<T> field) {
204 
205         final T zero = field.getZero();
206         // Central Body geopotential 4x4
207         final UnnormalizedSphericalHarmonicsProvider provider =
208                 GravityFieldFactory.getUnnormalizedProvider(4, 4);
209 
210         final Frame frame = FramesFactory.getEME2000();
211         final Frame earthFrame = CelestialBodyFactory.getEarth().getBodyOrientedFrame();
212         final FieldAbsoluteDate<T> initDate = new FieldAbsoluteDate<>(field, 2007, 04, 16, 0, 46, 42.400, TimeScalesFactory.getUTC());
213 
214         // a  = 26559890 m
215         // ey = 0.0041543085910249414
216         // ex = 2.719455286199036E-4
217         // hy = 0.3960084733107685
218         // hx = -0.3412974060023717
219         // lM = 8.566537840341699 rad
220         final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(zero.add(2.655989E7),
221                                                                 zero.add(2.719455286199036E-4),
222                                                                 zero.add(0.0041543085910249414),
223                                                                 zero.add(-0.3412974060023717),
224                                                                 zero.add(0.3960084733107685),
225                                                                 zero.add(8.566537840341699),
226                                                                 PositionAngleType.TRUE,
227                                                                 frame,
228                                                                 initDate,
229                                                                 zero.add(3.986004415E14));
230 
231         final FieldSpacecraftState<T> state = new FieldSpacecraftState<>(orbit, zero.add(1000.0));
232 
233         final FieldAuxiliaryElements<T> auxiliaryElements = new FieldAuxiliaryElements<>(state.getOrbit(), 1);
234 
235         // Tesseral force model
236         final DSSTForceModel tesseral = new DSSTTesseral(earthFrame,
237                                                          Constants.WGS84_EARTH_ANGULAR_VELOCITY, provider,
238                                                          4, 4, 4, 8, 4, 4, 2);
239         tesseral.initializeShortPeriodTerms(auxiliaryElements, PropagationType.MEAN, tesseral.getParameters(field, state.getDate()));
240 
241         // Tesseral force model with default constructor
242         final DSSTForceModel tesseralDefault = new DSSTTesseral(earthFrame,
243                                                              Constants.WGS84_EARTH_ANGULAR_VELOCITY, provider);
244         tesseralDefault.initializeShortPeriodTerms(auxiliaryElements, PropagationType.MEAN, tesseralDefault.getParameters(field, state.getDate()));
245 
246         // Compute mean element rate for the tesseral force model
247         final T[] elements = tesseral.getMeanElementRate(state, auxiliaryElements, tesseral.getParameters(field, state.getDate()));
248 
249         // Compute mean element rate for the "default" tesseral force model
250         final T[] elementsDefault = tesseralDefault.getMeanElementRate(state, auxiliaryElements, tesseralDefault.getParameters(field, state.getDate()));
251 
252         // Verify
253         for (int i = 0; i < 6; i++) {
254             Assertions.assertEquals(elements[i].getReal(), elementsDefault[i].getReal(), Double.MIN_VALUE);
255         }
256 
257     }
258 
259     @Test
260     public void testIssue736() {
261         doTestIssue736(Binary64Field.getInstance());
262     }
263 
264     private <T extends CalculusFieldElement<T>> void doTestIssue736(final Field<T> field) {
265 
266         // Central Body geopotential 4x4
267         final UnnormalizedSphericalHarmonicsProvider provider =
268                 GravityFieldFactory.getUnnormalizedProvider(4, 4);
269 
270         // Frames and epoch
271         final Frame frame = FramesFactory.getEME2000();
272         final Frame earthFrame = CelestialBodyFactory.getEarth().getBodyOrientedFrame();
273         final FieldAbsoluteDate<T> initDate = new FieldAbsoluteDate<>(field, 2007, 04, 16, 0, 46, 42.400, TimeScalesFactory.getUTC());
274 
275         // Orbit
276         final T zero = field.getZero();
277         final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(zero.add(2.655989E7), zero.add(2.719455286199036E-4),
278                                                                 zero.add(0.0041543085910249414), zero.add(-0.3412974060023717),
279                                                                 zero.add(0.3960084733107685), zero.add(8.566537840341699),
280                                                                 PositionAngleType.TRUE, frame, initDate, zero.add(3.986004415E14));
281 
282         // Force model
283         final DSSTForceModel tesseral = new DSSTTesseral(earthFrame, Constants.WGS84_EARTH_ANGULAR_VELOCITY, provider);
284         final T[] parameters = tesseral.getParameters(field, orbit.getDate());
285 
286         // Initialize force model
287         tesseral.initializeShortPeriodTerms(new FieldAuxiliaryElements<>(orbit, 1), PropagationType.MEAN, parameters);
288 
289         // Eccentricity shift
290         final FieldOrbit<T> shfitedOrbit = new FieldEquinoctialOrbit<>(zero.add(2.655989E7), zero.add(0.02),
291                         zero.add(0.0041543085910249414), zero.add(-0.3412974060023717),
292                         zero.add(0.3960084733107685), zero.add(8.566537840341699),
293                         PositionAngleType.TRUE, frame, initDate, zero.add(3.986004415E14));
294 
295         final T[] elements = tesseral.getMeanElementRate(new FieldSpacecraftState<>(shfitedOrbit), new FieldAuxiliaryElements<>(shfitedOrbit, 1), parameters);
296 
297         // The purpose of this test is not to verify a specific value.
298         // Its purpose is to verify that a NullPointerException does not
299         // occur when calculating initial values of Hansen Coefficients
300         for (int i = 0; i < elements.length; i++) {
301             Assertions.assertTrue(elements[i].getReal() != 0);
302         }
303 
304     }
305 
306     @Test
307     @SuppressWarnings("unchecked")
308     public void testShortPeriodTermsStateDerivatives() {
309 
310         // Initial spacecraft state
311         final AbsoluteDate initDate = new AbsoluteDate(new DateComponents(2003, 05, 21), new TimeComponents(1, 0, 0.),
312                                                        TimeScalesFactory.getUTC());
313 
314         final Orbit orbit = new EquinoctialOrbit(42164000,
315                                                  10e-3,
316                                                  10e-3,
317                                                  FastMath.tan(0.001745329) * FastMath.cos(2 * FastMath.PI / 3),
318                                                  FastMath.tan(0.001745329) * FastMath.sin(2 * FastMath.PI / 3), 0.1,
319                                                  PositionAngleType.TRUE,
320                                                  FramesFactory.getEME2000(),
321                                                  initDate,
322                                                  3.986004415E14);
323 
324         final OrbitType orbitType = OrbitType.EQUINOCTIAL;
325 
326         final SpacecraftState meanState = new SpacecraftState(orbit);
327 
328         // Force model
329         final UnnormalizedSphericalHarmonicsProvider provider =
330                         GravityFieldFactory.getUnnormalizedProvider(4, 4);
331         final Frame earthFrame = CelestialBodyFactory.getEarth().getBodyOrientedFrame();
332         final DSSTForceModel tesseral =
333                         new DSSTTesseral(earthFrame,
334                                          Constants.WGS84_EARTH_ANGULAR_VELOCITY, provider,
335                                          4, 4, 4, 8, 4, 4, 2);
336 
337         // Converter for derivatives
338         final DSSTGradientConverter converter = new DSSTGradientConverter(meanState, Utils.defaultLaw());
339 
340         // Field parameters
341         final FieldSpacecraftState<Gradient> dsState = converter.getState(tesseral);
342         
343         final FieldAuxiliaryElements<Gradient> fieldAuxiliaryElements = new FieldAuxiliaryElements<>(dsState.getOrbit(), 1);
344 
345         // Zero
346         final Gradient zero = dsState.getDate().getField().getZero();
347 
348         // Compute state Jacobian using directly the method
349         final List<FieldShortPeriodTerms<Gradient>> shortPeriodTerms = new ArrayList<FieldShortPeriodTerms<Gradient>>();
350         shortPeriodTerms.addAll(tesseral.initializeShortPeriodTerms(fieldAuxiliaryElements, PropagationType.OSCULATING,
351                                 converter.getParametersAtStateDate(dsState, tesseral)));
352         tesseral.updateShortPeriodTerms(converter.getParameters(dsState, tesseral), dsState);
353         final Gradient[] shortPeriod = new Gradient[6];
354         Arrays.fill(shortPeriod, zero);
355         for (final FieldShortPeriodTerms<Gradient> spt : shortPeriodTerms) {
356             final Gradient[] spVariation = spt.value(dsState.getOrbit());
357             for (int i = 0; i < spVariation .length; i++) {
358                 shortPeriod[i] = shortPeriod[i].add(spVariation[i]);
359             }
360         }
361 
362         final double[][] shortPeriodJacobian = new double[6][6];
363 
364         final double[] derivativesASP  = shortPeriod[0].getGradient();
365         final double[] derivativesExSP = shortPeriod[1].getGradient();
366         final double[] derivativesEySP = shortPeriod[2].getGradient();
367         final double[] derivativesHxSP = shortPeriod[3].getGradient();
368         final double[] derivativesHySP = shortPeriod[4].getGradient();
369         final double[] derivativesLSP  = shortPeriod[5].getGradient();
370 
371         // Update Jacobian with respect to state
372         addToRow(derivativesASP,  0, shortPeriodJacobian);
373         addToRow(derivativesExSP, 1, shortPeriodJacobian);
374         addToRow(derivativesEySP, 2, shortPeriodJacobian);
375         addToRow(derivativesHxSP, 3, shortPeriodJacobian);
376         addToRow(derivativesHySP, 4, shortPeriodJacobian);
377         addToRow(derivativesLSP,  5, shortPeriodJacobian);
378 
379         // Compute reference state Jacobian using finite differences
380         double[][] shortPeriodJacobianRef = new double[6][6];
381         double dP = 0.001;
382         double[] steps = ToleranceProvider.getDefaultToleranceProvider(1000000 * dP).getTolerances(orbit, orbitType)[0];
383         for (int i = 0; i < 6; i++) {
384 
385             SpacecraftState stateM4 = shiftState(meanState, orbitType, -4 * steps[i], i);
386             double[]  shortPeriodM4 = computeShortPeriodTerms(stateM4, tesseral);
387 
388             SpacecraftState stateM3 = shiftState(meanState, orbitType, -3 * steps[i], i);
389             double[]  shortPeriodM3 = computeShortPeriodTerms(stateM3, tesseral);
390 
391             SpacecraftState stateM2 = shiftState(meanState, orbitType, -2 * steps[i], i);
392             double[]  shortPeriodM2 = computeShortPeriodTerms(stateM2, tesseral);
393 
394             SpacecraftState stateM1 = shiftState(meanState, orbitType, -1 * steps[i], i);
395             double[]  shortPeriodM1 = computeShortPeriodTerms(stateM1, tesseral);
396 
397             SpacecraftState stateP1 = shiftState(meanState, orbitType, 1 * steps[i], i);
398             double[]  shortPeriodP1 = computeShortPeriodTerms(stateP1, tesseral);
399 
400             SpacecraftState stateP2 = shiftState(meanState, orbitType, 2 * steps[i], i);
401             double[]  shortPeriodP2 = computeShortPeriodTerms(stateP2, tesseral);
402 
403             SpacecraftState stateP3 = shiftState(meanState, orbitType, 3 * steps[i], i);
404             double[]  shortPeriodP3 = computeShortPeriodTerms(stateP3, tesseral);
405 
406             SpacecraftState stateP4 = shiftState(meanState, orbitType, 4 * steps[i], i);
407             double[]  shortPeriodP4 = computeShortPeriodTerms(stateP4, tesseral);
408 
409             fillJacobianColumn(shortPeriodJacobianRef, i, orbitType, steps[i],
410                                shortPeriodM4, shortPeriodM3, shortPeriodM2, shortPeriodM1,
411                                shortPeriodP1, shortPeriodP2, shortPeriodP3, shortPeriodP4);
412 
413         }
414 
415         for (int m = 0; m < 6; ++m) {
416             for (int n = 0; n < 6; ++n) {
417                 double error = FastMath.abs((shortPeriodJacobian[m][n] - shortPeriodJacobianRef[m][n]) / shortPeriodJacobianRef[m][n]);
418                 Assertions.assertEquals(0, error, 1.52e-09);
419             }
420         }
421 
422     }
423 
424     @Test
425     @SuppressWarnings("unchecked")
426     public void testShortPeriodTermsMuParametersDerivatives() {
427 
428         // Initial spacecraft state
429         final AbsoluteDate initDate = new AbsoluteDate(new DateComponents(2003, 05, 21), new TimeComponents(1, 0, 0.),
430                                                        TimeScalesFactory.getUTC());
431 
432         final Orbit orbit = new EquinoctialOrbit(42164000,
433                                                  10e-3,
434                                                  10e-3,
435                                                  FastMath.tan(0.001745329) * FastMath.cos(2 * FastMath.PI / 3),
436                                                  FastMath.tan(0.001745329) * FastMath.sin(2 * FastMath.PI / 3), 0.1,
437                                                  PositionAngleType.TRUE,
438                                                  FramesFactory.getEME2000(),
439                                                  initDate,
440                                                  3.986004415E14);
441 
442         final OrbitType orbitType = OrbitType.EQUINOCTIAL;
443 
444         final SpacecraftState meanState = new SpacecraftState(orbit);
445 
446         // Force model
447         final UnnormalizedSphericalHarmonicsProvider provider =
448                         GravityFieldFactory.getUnnormalizedProvider(4, 4);
449         final Frame earthFrame = CelestialBodyFactory.getEarth().getBodyOrientedFrame();
450         final DSSTForceModel tesseral =
451                         new DSSTTesseral(earthFrame,
452                                          Constants.WGS84_EARTH_ANGULAR_VELOCITY, provider,
453                                          4, 4, 4, 8, 4, 4, 2);
454 
455         for (final ParameterDriver driver : tesseral.getParametersDrivers()) {
456             driver.setValue(driver.getReferenceValue());
457             driver.setSelected(driver.getName().equals(DSSTNewtonianAttraction.CENTRAL_ATTRACTION_COEFFICIENT));
458         }
459 
460         // Converter for derivatives
461         final DSSTGradientConverter converter = new DSSTGradientConverter(meanState, Utils.defaultLaw());
462 
463         // Field parameters
464         final FieldSpacecraftState<Gradient> dsState = converter.getState(tesseral);
465       
466         final FieldAuxiliaryElements<Gradient> fieldAuxiliaryElements = new FieldAuxiliaryElements<>(dsState.getOrbit(), 1);
467 
468         // Zero
469         final Gradient zero = dsState.getDate().getField().getZero();
470 
471         // Compute Jacobian using directly the method
472         final List<FieldShortPeriodTerms<Gradient>> shortPeriodTerms = new ArrayList<FieldShortPeriodTerms<Gradient>>();
473         shortPeriodTerms.addAll(tesseral.initializeShortPeriodTerms(fieldAuxiliaryElements, PropagationType.OSCULATING,
474                                 converter.getParametersAtStateDate(dsState, tesseral)));
475         tesseral.updateShortPeriodTerms(converter.getParameters(dsState, tesseral), dsState);
476         final Gradient[] shortPeriod = new Gradient[6];
477         Arrays.fill(shortPeriod, zero);
478         for (final FieldShortPeriodTerms<Gradient> spt : shortPeriodTerms) {
479             final Gradient[] spVariation = spt.value(dsState.getOrbit());
480             for (int i = 0; i < spVariation .length; i++) {
481                 shortPeriod[i] = shortPeriod[i].add(spVariation[i]);
482             }
483         }
484 
485         final double[][] shortPeriodJacobian = new double[6][1];
486 
487         final double[] derivativesASP  = shortPeriod[0].getGradient();
488         final double[] derivativesExSP = shortPeriod[1].getGradient();
489         final double[] derivativesEySP = shortPeriod[2].getGradient();
490         final double[] derivativesHxSP = shortPeriod[3].getGradient();
491         final double[] derivativesHySP = shortPeriod[4].getGradient();
492         final double[] derivativesLSP  = shortPeriod[5].getGradient();
493 
494         int index = converter.getFreeStateParameters();
495         for (ParameterDriver driver : tesseral.getParametersDrivers()) {
496             if (driver.isSelected()) {
497                 shortPeriodJacobian[0][0] += derivativesASP[index];
498                 shortPeriodJacobian[1][0] += derivativesExSP[index];
499                 shortPeriodJacobian[2][0] += derivativesEySP[index];
500                 shortPeriodJacobian[3][0] += derivativesHxSP[index];
501                 shortPeriodJacobian[4][0] += derivativesHySP[index];
502                 shortPeriodJacobian[5][0] += derivativesLSP[index];
503                 ++index;
504             }
505         }
506 
507         // Compute reference Jacobian using finite differences
508         double[][] shortPeriodJacobianRef = new double[6][1];
509         ParameterDriversList bound = new ParameterDriversList();
510         for (final ParameterDriver driver : tesseral.getParametersDrivers()) {
511             if (driver.getName().equals(DSSTNewtonianAttraction.CENTRAL_ATTRACTION_COEFFICIENT)) {
512                 driver.setSelected(true);
513                 bound.add(driver);
514             } else {
515                 driver.setSelected(false);
516             }
517         }
518 
519         ParameterDriver selected = bound.getDrivers().get(0);
520         double p0 = selected.getReferenceValue();
521         double h  = selected.getScale();
522 
523         selected.setValue(p0 - 4 * h);
524         final double[] shortPeriodM4 = computeShortPeriodTerms(meanState, tesseral);
525   
526         selected.setValue(p0 - 3 * h);
527         final double[] shortPeriodM3 = computeShortPeriodTerms(meanState, tesseral);
528       
529         selected.setValue(p0 - 2 * h);
530         final double[] shortPeriodM2 = computeShortPeriodTerms(meanState, tesseral);
531       
532         selected.setValue(p0 - 1 * h);
533         final double[] shortPeriodM1 = computeShortPeriodTerms(meanState, tesseral);
534       
535         selected.setValue(p0 + 1 * h);
536         final double[] shortPeriodP1 = computeShortPeriodTerms(meanState, tesseral);
537       
538         selected.setValue(p0 + 2 * h);
539         final double[] shortPeriodP2 = computeShortPeriodTerms(meanState, tesseral);
540       
541         selected.setValue(p0 + 3 * h);
542         final double[] shortPeriodP3 = computeShortPeriodTerms(meanState, tesseral);
543       
544         selected.setValue(p0 + 4 * h);
545         final double[] shortPeriodP4 = computeShortPeriodTerms(meanState, tesseral);
546 
547         fillJacobianColumn(shortPeriodJacobianRef, 0, orbitType, h,
548                            shortPeriodM4, shortPeriodM3, shortPeriodM2, shortPeriodM1,
549                            shortPeriodP1, shortPeriodP2, shortPeriodP3, shortPeriodP4);
550 
551         for (int i = 0; i < 6; ++i) {
552             Assertions.assertEquals(shortPeriodJacobianRef[i][0],
553                                 shortPeriodJacobian[i][0],
554                                 FastMath.abs(shortPeriodJacobianRef[i][0] * 2e-10));
555         }
556 
557     }
558 
559     private double[] computeShortPeriodTerms(SpacecraftState state,
560                                              DSSTForceModel force) {
561 
562         AuxiliaryElements auxiliaryElements = new AuxiliaryElements(state.getOrbit(), 1);
563 
564         List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
565         shortPeriodTerms.addAll(force.initializeShortPeriodTerms(auxiliaryElements, PropagationType.OSCULATING, force.getParameters(state.getDate())));
566         force.updateShortPeriodTerms(force.getParametersAllValues(), state);
567         
568         double[] shortPeriod = new double[6];
569         for (ShortPeriodTerms spt : shortPeriodTerms) {
570             double[] spVariation = spt.value(state.getOrbit());
571             for (int i = 0; i < spVariation.length; i++) {
572                 shortPeriod[i] += spVariation[i];
573             }
574         }
575 
576         return shortPeriod;
577 
578     }
579 
580     private void fillJacobianColumn(double[][] jacobian, int column,
581                                     OrbitType orbitType, double h,
582                                     double[] M4h, double[] M3h,
583                                     double[] M2h, double[] M1h,
584                                     double[] P1h, double[] P2h,
585                                     double[] P3h, double[] P4h) {
586         for (int i = 0; i < jacobian.length; ++i) {
587             jacobian[i][column] = ( -3 * (P4h[i] - M4h[i]) +
588                                     32 * (P3h[i] - M3h[i]) -
589                                    168 * (P2h[i] - M2h[i]) +
590                                    672 * (P1h[i] - M1h[i])) / (840 * h);
591         }
592     }
593 
594     private SpacecraftState shiftState(SpacecraftState state, OrbitType orbitType,
595                                        double delta, int column) {
596 
597         double[][] array = stateToArray(state, orbitType);
598         array[0][column] += delta;
599 
600         return arrayToState(array, orbitType, state.getFrame(), state.getDate(),
601                             state.getOrbit().getMu(), state.getAttitude());
602 
603     }
604 
605     private double[][] stateToArray(SpacecraftState state, OrbitType orbitType) {
606           double[][] array = new double[2][6];
607 
608           orbitType.mapOrbitToArray(state.getOrbit(), PositionAngleType.MEAN, array[0], array[1]);
609           return array;
610       }
611 
612     private SpacecraftState arrayToState(double[][] array, OrbitType orbitType,
613                                            Frame frame, AbsoluteDate date, double mu,
614                                            Attitude attitude) {
615           EquinoctialOrbit orbit = (EquinoctialOrbit) orbitType.mapArrayToOrbit(array[0], array[1], PositionAngleType.MEAN, date, mu, frame);
616           return new SpacecraftState(orbit, attitude);
617     }
618 
619     /** Fill Jacobians rows.
620      * @param derivatives derivatives of a component
621      * @param index component index (0 for a, 1 for ex, 2 for ey, 3 for hx, 4 for hy, 5 for l)
622      * @param jacobian Jacobian of short period terms with respect to state
623      */
624     private void addToRow(final double[] derivatives, final int index,
625                           final double[][] jacobian) {
626 
627         for (int i = 0; i < 6; i++) {
628             jacobian[index][i] += derivatives[i];
629         }
630 
631     }
632 
633     @BeforeEach
634     public void setUp() throws IOException, ParseException {
635         Utils.setDataRoot("regular-data:potential/shm-format");
636     }
637 
638 }