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.forces.gravity;
18  
19  import java.util.List;
20  import java.util.stream.Collectors;
21  
22  import org.hipparchus.Field;
23  import org.hipparchus.analysis.differentiation.DerivativeStructure;
24  import org.hipparchus.analysis.differentiation.Gradient;
25  import org.hipparchus.analysis.differentiation.GradientField;
26  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
27  import org.hipparchus.geometry.euclidean.threed.Vector3D;
28  import org.hipparchus.ode.AbstractIntegrator;
29  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
30  import org.hipparchus.util.Binary64;
31  import org.hipparchus.util.Binary64Field;
32  import org.hipparchus.util.FastMath;
33  import org.junit.jupiter.api.Assertions;
34  import org.junit.jupiter.api.BeforeEach;
35  import org.junit.jupiter.api.Test;
36  import org.orekit.Utils;
37  import org.orekit.attitudes.AttitudeProvider;
38  import org.orekit.attitudes.LofOffset;
39  import org.orekit.bodies.CelestialBodyFactory;
40  import org.orekit.forces.AbstractLegacyForceModelTest;
41  import org.orekit.forces.ForceModel;
42  import org.orekit.forces.gravity.potential.GravityFieldFactory;
43  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
44  import org.orekit.frames.Frame;
45  import org.orekit.frames.FramesFactory;
46  import org.orekit.frames.LOFType;
47  import org.orekit.orbits.KeplerianOrbit;
48  import org.orekit.orbits.Orbit;
49  import org.orekit.orbits.OrbitType;
50  import org.orekit.orbits.PositionAngleType;
51  import org.orekit.propagation.FieldSpacecraftState;
52  import org.orekit.propagation.SpacecraftState;
53  import org.orekit.propagation.ToleranceProvider;
54  import org.orekit.propagation.events.DateDetector;
55  import org.orekit.propagation.events.EventDetector;
56  import org.orekit.propagation.events.EventDetectorsProvider;
57  import org.orekit.propagation.events.FieldDateDetector;
58  import org.orekit.propagation.events.FieldEventDetector;
59  import org.orekit.propagation.numerical.NumericalPropagator;
60  import org.orekit.time.AbsoluteDate;
61  import org.orekit.time.FieldAbsoluteDate;
62  import org.orekit.time.TimeScale;
63  import org.orekit.time.TimeScalesFactory;
64  import org.orekit.time.TimeStamped;
65  import org.orekit.time.UT1Scale;
66  import org.orekit.utils.Constants;
67  import org.orekit.utils.IERSConventions;
68  import org.orekit.utils.ParameterDriver;
69  
70  
71  public class SolidTidesTest extends AbstractLegacyForceModelTest {
72  
73      private static final AttitudeProvider DEFAULT_LAW = Utils.defaultLaw();
74  
75      @Override
76      protected FieldVector3D<DerivativeStructure> accelerationDerivatives(final ForceModel forceModel,
77                                                                           final FieldSpacecraftState<DerivativeStructure> state) {
78          try {
79              java.lang.reflect.Field attractionModelField = SolidTides.class.getDeclaredField("attractionModel");
80              attractionModelField.setAccessible(true);
81              ForceModel attractionModel = (ForceModel) attractionModelField.get(forceModel);
82              Field<DerivativeStructure> field = state.getDate().getField();
83              return attractionModel.acceleration(state, attractionModel.getParameters(field, state.getDate()));
84  
85          } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
86              return null;
87          }
88      }
89  
90      @Override
91      protected FieldVector3D<Gradient> accelerationDerivativesGradient(final ForceModel forceModel,
92                                                                        final FieldSpacecraftState<Gradient> state) {
93          try {
94              final FieldVector3D<Gradient> position = state.getPVCoordinates().getPosition();
95              java.lang.reflect.Field attractionModelField = SolidTides.class.getDeclaredField("attractionModel");
96              attractionModelField.setAccessible(true);
97              ForceModel attractionModel = (ForceModel) attractionModelField.get(forceModel);
98              final int freeParameters = position.getX().getFreeParameters();
99              Field<Gradient> field = GradientField.getField(freeParameters);
100             return attractionModel.acceleration(state, attractionModel.getParameters(field, state.getDate()));
101 
102         } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
103             return null;
104         }
105     }
106 
107     @Test
108     void testDefaultInterpolation() {
109 
110         IERSConventions conventions = IERSConventions.IERS_2010;
111         Frame eme2000 = FramesFactory.getEME2000();
112         Frame itrf    = FramesFactory.getITRF(conventions, true);
113         TimeScale utc = TimeScalesFactory.getUTC();
114         UT1Scale  ut1 = TimeScalesFactory.getUT1(conventions, true);
115         NormalizedSphericalHarmonicsProvider gravityField =
116                 GravityFieldFactory.getNormalizedProvider(5, 5);
117 
118         // initialization
119         AbsoluteDate date = new AbsoluteDate(1970, 07, 01, 13, 59, 27.816, utc);
120         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
121                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
122                                          0, PositionAngleType.MEAN, eme2000, date,
123                                          gravityField.getMu());
124 
125         AbsoluteDate target = date.shiftedBy(7 * Constants.JULIAN_DAY);
126         ForceModel hf = new HolmesFeatherstoneAttractionModel(itrf, gravityField);
127         SpacecraftState raw = propagate(orbit, target, hf,
128                                         new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
129                                                        gravityField.getTideSystem(), true, Double.NaN, -1,
130                                                        conventions, ut1,
131                                                        CelestialBodyFactory.getSun(),
132                                                        CelestialBodyFactory.getMoon()));
133         SpacecraftState interpolated = propagate(orbit, target, hf,
134                                                  new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
135                                                                 gravityField.getTideSystem(),
136                                                                 conventions, ut1,
137                                                                 CelestialBodyFactory.getSun(),
138                                                                 CelestialBodyFactory.getMoon()));
139         Assertions.assertEquals(0.0,
140                             Vector3D.distance(raw.getPosition(),
141                                               interpolated.getPosition()),
142                             2.1e-5); // threshold would be 1.2e-3 for 30 days propagation
143 
144     }
145 
146     @Test
147     void testTideEffect1996() {
148         Frame eme2000 = FramesFactory.getEME2000();
149         TimeScale utc = TimeScalesFactory.getUTC();
150         AbsoluteDate date = new AbsoluteDate(2003, 07, 01, 13, 59, 27.816, utc);
151         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
152                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
153                                          0, PositionAngleType.MEAN, eme2000, date,
154                                          Constants.EIGEN5C_EARTH_MU);
155         doTestTideEffect(orbit, IERSConventions.IERS_1996, 44.09481, 0.00000);
156     }
157 
158     @Test
159     void testTideEffect2003WithinAnnualPoleRange() {
160         Frame eme2000 = FramesFactory.getEME2000();
161         TimeScale utc = TimeScalesFactory.getUTC();
162         AbsoluteDate date = new AbsoluteDate(1969, 07, 01, 13, 59, 27.816, utc);
163         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
164                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
165                                          0, PositionAngleType.MEAN, eme2000, date,
166                                          Constants.EIGEN5C_EARTH_MU);
167         doTestTideEffect(orbit, IERSConventions.IERS_2003, 73.14011, 0.87360);
168     }
169 
170     @Test
171     void testTideEffect2003AfterAnnualPoleRange() {
172         Frame eme2000 = FramesFactory.getEME2000();
173         TimeScale utc = TimeScalesFactory.getUTC();
174         AbsoluteDate date = new AbsoluteDate(2003, 07, 01, 13, 59, 27.816, utc);
175         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
176                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
177                                          0, PositionAngleType.MEAN, eme2000, date,
178                                          Constants.EIGEN5C_EARTH_MU);
179         doTestTideEffect(orbit, IERSConventions.IERS_2003, 44.24999, 0.61752);
180     }
181 
182     @Test
183     void testTideEffect2010BeforePoleModelChange() {
184         Frame eme2000 = FramesFactory.getEME2000();
185         TimeScale utc = TimeScalesFactory.getUTC();
186         AbsoluteDate date = new AbsoluteDate(2003, 07, 01, 13, 59, 27.816, utc);
187         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
188                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
189                                          0, PositionAngleType.MEAN, eme2000, date,
190                                          Constants.EIGEN5C_EARTH_MU);
191         doTestTideEffect(orbit, IERSConventions.IERS_2010, 44.25001, 0.70710);
192     }
193 
194     @Test
195     void testTideEffect2010AfterModelChange() {
196         Frame eme2000 = FramesFactory.getEME2000();
197         TimeScale utc = TimeScalesFactory.getUTC();
198         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
199         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
200                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
201                                          0, PositionAngleType.MEAN, eme2000, date,
202                                          Constants.EIGEN5C_EARTH_MU);
203         doTestTideEffect(orbit, IERSConventions.IERS_2010, 24.02815, 30.37047);
204     }
205 
206     @Test
207     void testStateJacobianVs80ImplementationNoPoleTide()
208         {
209         Frame eme2000 = FramesFactory.getEME2000();
210         TimeScale utc = TimeScalesFactory.getUTC();
211         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
212         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
213                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
214                                          0, PositionAngleType.MEAN, eme2000, date,
215                                          Constants.EIGEN5C_EARTH_MU);
216         Frame itrf    = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
217         UT1Scale  ut1 = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
218         NormalizedSphericalHarmonicsProvider gravityField =
219                         GravityFieldFactory.getNormalizedProvider(5, 5);
220 
221         ForceModel forceModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
222                                                gravityField.getTideSystem(), false,
223                                                SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
224                                                IERSConventions.IERS_2010, ut1,
225                                                CelestialBodyFactory.getSun(),
226                                                CelestialBodyFactory.getMoon());
227         Assertions.assertTrue(forceModel.dependsOnPositionOnly());
228 
229         checkStateJacobianVs80Implementation(new SpacecraftState(orbit), forceModel,
230                                              new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS),
231                                              2.0e-15, false);
232 
233     }
234 
235     @Test
236     void testStateJacobianVs80ImplementationGradientNoPoleTide()
237         {
238         Frame eme2000 = FramesFactory.getEME2000();
239         TimeScale utc = TimeScalesFactory.getUTC();
240         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
241         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
242                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
243                                          0, PositionAngleType.MEAN, eme2000, date,
244                                          Constants.EIGEN5C_EARTH_MU);
245         Frame itrf    = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
246         UT1Scale  ut1 = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
247         NormalizedSphericalHarmonicsProvider gravityField =
248                         GravityFieldFactory.getNormalizedProvider(5, 5);
249 
250         ForceModel forceModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
251                                                gravityField.getTideSystem(), false,
252                                                SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
253                                                IERSConventions.IERS_2010, ut1,
254                                                CelestialBodyFactory.getSun(),
255                                                CelestialBodyFactory.getMoon());
256         Assertions.assertTrue(forceModel.dependsOnPositionOnly());
257 
258         checkStateJacobianVs80ImplementationGradient(new SpacecraftState(orbit), forceModel,
259                                              new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS),
260                                              2.0e-15, false);
261 
262     }
263 
264     @Test
265     void testStateJacobianVs80ImplementationPoleTide()
266         {
267         Frame eme2000 = FramesFactory.getEME2000();
268         TimeScale utc = TimeScalesFactory.getUTC();
269         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
270         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
271                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
272                                          0, PositionAngleType.MEAN, eme2000, date,
273                                          Constants.EIGEN5C_EARTH_MU);
274         Frame itrf    = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
275         UT1Scale  ut1 = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
276         NormalizedSphericalHarmonicsProvider gravityField =
277                         GravityFieldFactory.getNormalizedProvider(5, 5);
278 
279         ForceModel forceModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
280                                                gravityField.getTideSystem(), true,
281                                                SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
282                                                IERSConventions.IERS_2010, ut1,
283                                                CelestialBodyFactory.getSun(),
284                                                CelestialBodyFactory.getMoon());
285 
286         checkStateJacobianVs80Implementation(new SpacecraftState(orbit), forceModel,
287                                              new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS),
288                                              2.0e-15, false);
289 
290     }
291 
292     @Test
293     void testStateJacobianVs80ImplementationGradientPoleTide()
294         {
295         Frame eme2000 = FramesFactory.getEME2000();
296         TimeScale utc = TimeScalesFactory.getUTC();
297         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
298         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
299                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
300                                          0, PositionAngleType.MEAN, eme2000, date,
301                                          Constants.EIGEN5C_EARTH_MU);
302         Frame itrf    = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
303         UT1Scale  ut1 = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
304         NormalizedSphericalHarmonicsProvider gravityField =
305                         GravityFieldFactory.getNormalizedProvider(5, 5);
306 
307         ForceModel forceModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
308                                                gravityField.getTideSystem(), true,
309                                                SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
310                                                IERSConventions.IERS_2010, ut1,
311                                                CelestialBodyFactory.getSun(),
312                                                CelestialBodyFactory.getMoon());
313 
314         checkStateJacobianVs80ImplementationGradient(new SpacecraftState(orbit), forceModel,
315                                              new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS),
316                                              2.0e-15, false);
317 
318     }
319 
320     @Test
321     void testStateJacobianVsFiniteDifferencesNoPoleTide()
322         {
323         Frame eme2000 = FramesFactory.getEME2000();
324         TimeScale utc = TimeScalesFactory.getUTC();
325         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
326         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
327                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
328                                          0, PositionAngleType.MEAN, eme2000, date,
329                                          Constants.EIGEN5C_EARTH_MU);
330         Frame itrf    = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
331         UT1Scale  ut1 = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
332         NormalizedSphericalHarmonicsProvider gravityField =
333                         GravityFieldFactory.getNormalizedProvider(5, 5);
334 
335         ForceModel forceModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
336                                                gravityField.getTideSystem(), false,
337                                                SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
338                                                IERSConventions.IERS_2010, ut1,
339                                                CelestialBodyFactory.getSun(),
340                                                CelestialBodyFactory.getMoon());
341 
342         checkStateJacobianVsFiniteDifferences(new SpacecraftState(orbit), forceModel, DEFAULT_LAW,
343                                               10.0, 2.0e-10, false);
344 
345     }
346 
347     @Test
348     void testStateJacobianVsFiniteDifferencesGradientNoPoleTide()
349         {
350         Frame eme2000 = FramesFactory.getEME2000();
351         TimeScale utc = TimeScalesFactory.getUTC();
352         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
353         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
354                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
355                                          0, PositionAngleType.MEAN, eme2000, date,
356                                          Constants.EIGEN5C_EARTH_MU);
357         Frame itrf    = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
358         UT1Scale  ut1 = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
359         NormalizedSphericalHarmonicsProvider gravityField =
360                         GravityFieldFactory.getNormalizedProvider(5, 5);
361 
362         ForceModel forceModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
363                                                gravityField.getTideSystem(), false,
364                                                SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
365                                                IERSConventions.IERS_2010, ut1,
366                                                CelestialBodyFactory.getSun(),
367                                                CelestialBodyFactory.getMoon());
368 
369         checkStateJacobianVsFiniteDifferencesGradient(new SpacecraftState(orbit), forceModel, DEFAULT_LAW,
370                                               10.0, 2.0e-10, false);
371 
372     }
373 
374     @Test
375     void testStateJacobianVsFiniteDifferencesPoleTide()
376         {
377         Frame eme2000 = FramesFactory.getEME2000();
378         TimeScale utc = TimeScalesFactory.getUTC();
379         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
380         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
381                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
382                                          0, PositionAngleType.MEAN, eme2000, date,
383                                          Constants.EIGEN5C_EARTH_MU);
384         Frame itrf    = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
385         UT1Scale  ut1 = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
386         NormalizedSphericalHarmonicsProvider gravityField =
387                         GravityFieldFactory.getNormalizedProvider(5, 5);
388 
389         ForceModel forceModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
390                                                gravityField.getTideSystem(), true,
391                                                SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
392                                                IERSConventions.IERS_2010, ut1,
393                                                CelestialBodyFactory.getSun(),
394                                                CelestialBodyFactory.getMoon());
395 
396         checkStateJacobianVsFiniteDifferences(new SpacecraftState(orbit), forceModel, DEFAULT_LAW,
397                                               10.0, 2.0e-10, false);
398 
399     }
400 
401     @Test
402     void testStateJacobianVsFiniteDifferencesGradientPoleTide()
403         {
404         Frame eme2000 = FramesFactory.getEME2000();
405         TimeScale utc = TimeScalesFactory.getUTC();
406         AbsoluteDate date = new AbsoluteDate(2964, 8, 12, 11, 30, 00.000, utc);
407         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, FastMath.toRadians(98.7),
408                                          FastMath.toRadians(93.0), FastMath.toRadians(15.0 * 22.5),
409                                          0, PositionAngleType.MEAN, eme2000, date,
410                                          Constants.EIGEN5C_EARTH_MU);
411         Frame itrf    = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
412         UT1Scale  ut1 = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
413         NormalizedSphericalHarmonicsProvider gravityField =
414                         GravityFieldFactory.getNormalizedProvider(5, 5);
415 
416         ForceModel forceModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
417                                                gravityField.getTideSystem(), true,
418                                                SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
419                                                IERSConventions.IERS_2010, ut1,
420                                                CelestialBodyFactory.getSun(),
421                                                CelestialBodyFactory.getMoon());
422 
423         checkStateJacobianVsFiniteDifferencesGradient(new SpacecraftState(orbit), forceModel, DEFAULT_LAW,
424                                               10.0, 2.0e-10, false);
425 
426     }
427     
428     /** Test added for <a href="https://gitlab.orekit.org/orekit/orekit/-/issues/1167">issue 1167</a>.
429      * <p>Mostly for code coverage, with the introduction of interface {@link EventDetectorsProvider}
430      */
431     @Test
432     void testGetEventDetectors() {
433         
434         // Given
435         // -----
436         
437         final IERSConventions conventions = IERSConventions.IERS_2010;
438         final Frame itrf = FramesFactory.getITRF(conventions, true);
439         final AbsoluteDate t0 = AbsoluteDate.ARBITRARY_EPOCH;
440 
441         final NormalizedSphericalHarmonicsProvider gravityField =
442                         GravityFieldFactory.getNormalizedProvider(5, 5);
443         final UT1Scale  ut1 = TimeScalesFactory.getUT1(conventions, true);
444         
445         // Create solid tides force model
446         final ForceModel solidTidesModel = new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
447                                                           gravityField.getTideSystem(), false,
448                                                           SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
449                                                           conventions, ut1,
450                                                           CelestialBodyFactory.getSun(),
451                                                           CelestialBodyFactory.getMoon());
452 
453         // When: Empty list
454         List<EventDetector>                detectors      = solidTidesModel.getEventDetectors().collect(Collectors.toList());
455         List<FieldEventDetector<Binary64>> fieldDetectors = solidTidesModel.getFieldEventDetectors(Binary64Field.getInstance()).collect(Collectors.toList());
456         
457         // Then
458         Assertions.assertTrue(detectors.isEmpty());
459         Assertions.assertTrue(fieldDetectors.isEmpty());
460         
461         // When: 1 span added to driver
462         final List<ParameterDriver> drivers = solidTidesModel.getParametersDrivers();
463         
464         for (final ParameterDriver driver : drivers) {
465             driver.addSpanAtDate(t0);
466         }
467         
468         detectors      = solidTidesModel.getEventDetectors().collect(Collectors.toList());
469         DateDetector dateDetector = (DateDetector) detectors.get(0);
470         List<TimeStamped> dates = dateDetector.getDates();
471         
472         fieldDetectors = solidTidesModel.getFieldEventDetectors(Binary64Field.getInstance()).collect(Collectors.toList());
473         FieldDateDetector<Binary64> fieldDateDetector = (FieldDateDetector<Binary64>) fieldDetectors.get(0);
474         FieldAbsoluteDate<Binary64> fieldDate = fieldDateDetector.getDate();
475         
476         // Then
477         Assertions.assertFalse(detectors.isEmpty());
478         Assertions.assertEquals(1, detectors.size());
479         Assertions.assertTrue(detectors.get(0) instanceof DateDetector);
480         
481         Assertions.assertEquals(1, dates.size());
482         Assertions.assertEquals(0., dates.get(0).durationFrom(t0), 0.);
483         
484         Assertions.assertFalse(fieldDetectors.isEmpty());
485         Assertions.assertEquals(1, fieldDetectors.size());
486         Assertions.assertTrue(fieldDetectors.get(0) instanceof FieldDateDetector);
487         Assertions.assertEquals(0., fieldDate.durationFrom(t0).getReal(), 0.);
488     }
489 
490     private void doTestTideEffect(Orbit orbit, IERSConventions conventions, double delta1, double delta2) {
491 
492         Frame itrf    = FramesFactory.getITRF(conventions, true);
493         UT1Scale  ut1 = TimeScalesFactory.getUT1(conventions, true);
494         NormalizedSphericalHarmonicsProvider gravityField =
495                 GravityFieldFactory.getNormalizedProvider(5, 5);
496 
497         // initialization
498 
499         AbsoluteDate target = orbit.getDate().shiftedBy(7 * Constants.JULIAN_DAY);
500         ForceModel hf = new HolmesFeatherstoneAttractionModel(itrf, gravityField);
501         SpacecraftState noTides              = propagate(orbit, target, hf);
502         SpacecraftState solidTidesNoPoleTide = propagate(orbit, target, hf,
503                                                          new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
504                                                                         gravityField.getTideSystem(), false,
505                                                                         SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
506                                                                         conventions, ut1,
507                                                                         CelestialBodyFactory.getSun(),
508                                                                         CelestialBodyFactory.getMoon()));
509         SpacecraftState solidTidesPoleTide = propagate(orbit, target, hf,
510                                                        new SolidTides(itrf, gravityField.getAe(), gravityField.getMu(),
511                                                                       gravityField.getTideSystem(), true,
512                                                                       SolidTides.DEFAULT_STEP, SolidTides.DEFAULT_POINTS,
513                                                                       conventions, ut1,
514                                                                       CelestialBodyFactory.getSun(),
515                                                                       CelestialBodyFactory.getMoon()));
516         Assertions.assertEquals(delta1,
517                             Vector3D.distance(noTides.getPosition(),
518                                               solidTidesNoPoleTide.getPosition()),
519                             0.01);
520         Assertions.assertEquals(delta2,
521                             Vector3D.distance(solidTidesNoPoleTide.getPosition(),
522                                               solidTidesPoleTide.getPosition()),
523                             0.01);
524 
525     }
526 
527     private SpacecraftState propagate(Orbit orbit, AbsoluteDate target, ForceModel... forceModels)
528         {
529         double[][] tolerances = ToleranceProvider.getDefaultToleranceProvider(10.).getTolerances(orbit, OrbitType.KEPLERIAN);
530         AbstractIntegrator integrator = new DormandPrince853Integrator(1.0e-3, 300, tolerances[0], tolerances[1]);
531         NumericalPropagator propagator = new NumericalPropagator(integrator);
532         for (ForceModel forceModel : forceModels) {
533             propagator.addForceModel(forceModel);
534         }
535         propagator.setInitialState(new SpacecraftState(orbit));
536         return propagator.propagate(target);
537     }
538 
539     @BeforeEach
540     public void setUp() {
541         Utils.setDataRoot("regular-data:potential/icgem-format");
542     }
543 
544 }