1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.propagation.numerical;
18
19 import org.hipparchus.CalculusFieldElement;
20 import org.hipparchus.Field;
21 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22 import org.hipparchus.geometry.euclidean.threed.Vector3D;
23 import org.hipparchus.linear.MatrixUtils;
24 import org.hipparchus.linear.RealMatrix;
25 import org.hipparchus.linear.RealVector;
26 import org.hipparchus.ode.ODEIntegrator;
27 import org.hipparchus.util.FastMath;
28 import org.junit.jupiter.api.Assertions;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.params.ParameterizedTest;
32 import org.junit.jupiter.params.provider.EnumSource;
33 import org.junit.jupiter.params.provider.ValueSource;
34 import org.orekit.Utils;
35 import org.orekit.attitudes.AttitudeProvider;
36 import org.orekit.attitudes.FrameAlignedProvider;
37 import org.orekit.attitudes.LofOffset;
38 import org.orekit.bodies.AnalyticalSolarPositionProvider;
39 import org.orekit.data.DataContext;
40 import org.orekit.forces.ForceModel;
41 import org.orekit.forces.gravity.J2OnlyPerturbation;
42 import org.orekit.forces.gravity.ThirdBodyAttraction;
43 import org.orekit.forces.gravity.potential.GravityFieldFactory;
44 import org.orekit.forces.gravity.potential.ICGEMFormatReader;
45 import org.orekit.forces.maneuvers.ConstantThrustManeuver;
46 import org.orekit.forces.maneuvers.Maneuver;
47 import org.orekit.forces.maneuvers.propulsion.BasicConstantThrustPropulsionModel;
48 import org.orekit.forces.maneuvers.propulsion.ProfileThrustPropulsionModel;
49 import org.orekit.forces.maneuvers.propulsion.PropulsionModel;
50 import org.orekit.forces.maneuvers.propulsion.SphericalConstantThrustPropulsionModel;
51 import org.orekit.forces.maneuvers.propulsion.ThrustVectorProvider;
52 import org.orekit.forces.maneuvers.trigger.DateBasedManeuverTriggers;
53 import org.orekit.forces.maneuvers.trigger.IntervalEventTrigger;
54 import org.orekit.forces.maneuvers.trigger.ManeuverTriggers;
55 import org.orekit.forces.radiation.CylindricallyShadowedLightFluxModel;
56 import org.orekit.forces.radiation.IsotropicRadiationSingleCoefficient;
57 import org.orekit.forces.radiation.LightFluxModel;
58 import org.orekit.forces.radiation.RadiationPressureModel;
59 import org.orekit.forces.radiation.RadiationSensitive;
60 import org.orekit.frames.FramesFactory;
61 import org.orekit.frames.LOFType;
62 import org.orekit.orbits.CartesianOrbit;
63 import org.orekit.orbits.EquinoctialOrbit;
64 import org.orekit.orbits.Orbit;
65 import org.orekit.orbits.OrbitType;
66 import org.orekit.orbits.PositionAngleType;
67 import org.orekit.propagation.MatricesHarvester;
68 import org.orekit.propagation.SpacecraftState;
69 import org.orekit.propagation.conversion.DormandPrince54IntegratorBuilder;
70 import org.orekit.propagation.events.ApsideDetector;
71 import org.orekit.propagation.events.EventDetectionSettings;
72 import org.orekit.propagation.events.EventDetector;
73 import org.orekit.propagation.events.FieldApsideDetector;
74 import org.orekit.propagation.events.FieldEventDetectionSettings;
75 import org.orekit.propagation.events.FieldEventDetector;
76 import org.orekit.propagation.events.ParameterDrivenDateIntervalDetector;
77 import org.orekit.propagation.events.handlers.ContinueOnEvent;
78 import org.orekit.propagation.events.handlers.FieldStopOnEvent;
79 import org.orekit.time.AbsoluteDate;
80 import org.orekit.time.FieldAbsoluteDate;
81 import org.orekit.utils.Constants;
82 import org.orekit.utils.PVCoordinates;
83 import org.orekit.utils.ParameterDriver;
84 import org.orekit.utils.TimeSpanMap;
85
86 import java.util.Arrays;
87 import java.util.Collections;
88 import java.util.List;
89 import java.util.stream.Stream;
90
91 import static org.junit.jupiter.api.Assertions.*;
92
93 class ExtendedStateTransitionMatrixGeneratorTest {
94
95 @BeforeEach
96 void setUp() {
97 Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
98 GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
99 }
100
101 @Test
102 void testManeuverTriggerDateParameter() {
103
104 final NumericalPropagator propagator = buildPropagator(OrbitType.CARTESIAN, null);
105 final LofOffset lofOffset = new LofOffset(propagator.getFrame(), LOFType.TNW);
106 propagator.setAttitudeProvider(lofOffset);
107 final String stmName = "stm";
108 final MatricesHarvester harvester = propagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(7), null);
109 final double duration = 100.;
110 final double timeOfFlight = duration * 3;
111 final AbsoluteDate epoch = propagator.getInitialState().getDate();
112 final AbsoluteDate targetDate = epoch.shiftedBy(timeOfFlight);
113 final AbsoluteDate medianDate = epoch.shiftedBy(duration * 2);
114 final BasicConstantThrustPropulsionModel propulsionModel = new BasicConstantThrustPropulsionModel(0.1, 10.,
115 Vector3D.MINUS_I, "man");
116 final AbsoluteDate startDate = medianDate.shiftedBy(-duration/2.);
117 final ConstantThrustManeuver maneuver = new ConstantThrustManeuver(startDate, duration, null, propulsionModel);
118 maneuver.getParameterDriver("man" + ParameterDrivenDateIntervalDetector.START_SUFFIX).setSelected(true);
119 propagator.addForceModel(maneuver);
120
121 final SpacecraftState state = propagator.propagate(targetDate);
122
123 final double dP = 1e-1;
124 final NumericalPropagator propagatorFiniteDifference1 = setupPropagator(propagator.getOrbitType(), lofOffset,
125 startDate, duration, propulsionModel, -dP/2.);
126 final SpacecraftState finiteDifferencesState1 = propagatorFiniteDifference1.propagate(targetDate);
127 final NumericalPropagator propagatorFiniteDifference2 = setupPropagator(propagator.getOrbitType(), lofOffset,
128 startDate, duration, propulsionModel, dP/2.);
129 final SpacecraftState finiteDifferencesState2 = propagatorFiniteDifference2.propagate(targetDate);
130 final PVCoordinates relativePV = new PVCoordinates(finiteDifferencesState1.getPVCoordinates(),
131 finiteDifferencesState2.getPVCoordinates());
132 final RealMatrix parameterJacobian = harvester.getParametersJacobian(state);
133 compareWithFiniteDifferences(relativePV, dP, parameterJacobian, 1e-3);
134 }
135
136 private static NumericalPropagator setupPropagator(final OrbitType orbitType, final AttitudeProvider attitudeProvider,
137 final AbsoluteDate startDate, final double duration,
138 final BasicConstantThrustPropulsionModel propulsionModel,
139 final double dt) {
140 final NumericalPropagator propagator = buildPropagator(orbitType, attitudeProvider);
141 final AbsoluteDate shiftedStartDate = startDate.shiftedBy(dt);
142 final ConstantThrustManeuver maneuver = new ConstantThrustManeuver(shiftedStartDate, duration - dt, null, propulsionModel);
143 propagator.addForceModel(maneuver);
144 return propagator;
145 }
146
147 @Test
148 void testManeuverPropulsionParameter() {
149
150 final NumericalPropagator propagator = buildPropagator(OrbitType.CARTESIAN);
151 final String stmName = "stm";
152 final MatricesHarvester harvester = propagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(7), null);
153 final double thrustMagnitude = 0.1;
154 final Maneuver maneuver = getPermanentManeuver(thrustMagnitude);
155 propagator.addForceModel(maneuver);
156 final double timeOfFlight = 1e3;
157 final AbsoluteDate epoch = propagator.getInitialState().getDate();
158 final AbsoluteDate targetDate = epoch.shiftedBy(timeOfFlight);
159
160 final SpacecraftState state = propagator.propagate(targetDate);
161
162 compareMassGradientWithFiniteDifferences(propagator.getInitialState(), maneuver, targetDate,
163 harvester.getStateTransitionMatrix(state));
164 final RealMatrix parameterJacobian = harvester.getParametersJacobian(state);
165 compareParameterJacobianWithFiniteDifferences(propagator, thrustMagnitude, targetDate, parameterJacobian);
166 }
167
168 private static void compareParameterJacobianWithFiniteDifferences(final NumericalPropagator propagator,
169 final double thrustMagnitude,
170 final AbsoluteDate targetDate,
171 final RealMatrix parameterJacobian) {
172 final NumericalPropagator propagatorFiniteDifference1 = buildPropagator(propagator.getOrbitType(),
173 propagator.getAttitudeProvider());
174 final double dP = 1e-5;
175 propagatorFiniteDifference1.addForceModel(getPermanentManeuver((thrustMagnitude - dP / 2.)));
176 final SpacecraftState finiteDifferencesState1 = propagatorFiniteDifference1.propagate(targetDate);
177 final NumericalPropagator propagatorFiniteDifference2 = buildPropagator(propagator.getOrbitType(),
178 propagator.getAttitudeProvider());
179 propagatorFiniteDifference2.addForceModel(getPermanentManeuver(thrustMagnitude + dP / 2.));
180 final SpacecraftState finiteDifferencesState2 = propagatorFiniteDifference2.propagate(targetDate);
181 final PVCoordinates relativePV = new PVCoordinates(finiteDifferencesState1.getPVCoordinates(),
182 finiteDifferencesState2.getPVCoordinates());
183 compareWithFiniteDifferences(relativePV, dP, parameterJacobian, 1e-3);
184 }
185
186 private static void compareWithFiniteDifferences(final PVCoordinates relativePV, final double dP,
187 final RealMatrix parameterJacobian, final double delta) {
188 assertEquals(relativePV.getPosition().getX() / dP, parameterJacobian.getEntry(0, 0), delta);
189 assertEquals(relativePV.getPosition().getY() / dP, parameterJacobian.getEntry(1, 0), delta);
190 assertEquals(relativePV.getPosition().getZ() / dP, parameterJacobian.getEntry(2, 0), delta);
191 assertEquals(relativePV.getVelocity().getX() / dP, parameterJacobian.getEntry(3, 0), delta * 1e-2);
192 assertEquals(relativePV.getVelocity().getY() / dP, parameterJacobian.getEntry(4, 0), delta * 1e-2);
193 assertEquals(relativePV.getVelocity().getZ() / dP, parameterJacobian.getEntry(5, 0), delta * 1e-2);
194 }
195
196 private static Maneuver getPermanentManeuver(final double thrustMagnitude) {
197 final Vector3D thrustVector = new Vector3D(3, 2).scalarMultiply(thrustMagnitude);
198 final double isp = 100.;
199 final String maneuverName = "man";
200 final SphericalConstantThrustPropulsionModel propulsionModel = new SphericalConstantThrustPropulsionModel(isp,
201 thrustVector, maneuverName);
202 propulsionModel.getParameterDriver(maneuverName + SphericalConstantThrustPropulsionModel.THRUST_MAGNITUDE).setSelected(true);
203 return new Maneuver(null, new TestManeuverTrigger(), propulsionModel);
204 }
205
206 private static class TestManeuverTrigger implements ManeuverTriggers {
207
208 @Override
209 public boolean isFiring(AbsoluteDate date, double[] parameters) {
210 return true;
211 }
212
213 @Override
214 public <T extends CalculusFieldElement<T>> boolean isFiring(FieldAbsoluteDate<T> date, T[] parameters) {
215 return true;
216 }
217
218 @Override
219 public Stream<EventDetector> getEventDetectors() {
220 return Stream.empty();
221 }
222
223 @Override
224 public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(Field<T> field) {
225 return Stream.empty();
226 }
227
228 @Override
229 public List<ParameterDriver> getParametersDrivers() {
230 return Collections.emptyList();
231 }
232 }
233
234 @Test
235 void testStm7x7vs6x6Column() {
236
237 final OrbitType orbitType = OrbitType.CARTESIAN;
238 final NumericalPropagator propagator = buildPropagator(orbitType);
239 final String stmName = "stm";
240 final MatricesHarvester harvester7x7 = propagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(7), null);
241 final double timeOfFlight = 1e5;
242 final AbsoluteDate epoch = propagator.getInitialState().getDate();
243 final AbsoluteDate targetDate = epoch.shiftedBy(timeOfFlight);
244 final ForceModel force = new ThirdBodyAttraction(new AnalyticalSolarPositionProvider(DataContext.getDefault()),
245 "sun", Constants.JPL_SSD_SUN_GM);
246 force.getParametersDrivers().get(0).setSelected(true);
247 propagator.addForceModel(force);
248
249 final SpacecraftState state = propagator.propagate(targetDate);
250 final RealMatrix actualJacobian = harvester7x7.getParametersJacobian(state);
251
252 final NumericalPropagator otherPropagator = buildPropagator(orbitType);
253 otherPropagator.addForceModel(force);
254 final MatricesHarvester harvester6x6 = otherPropagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(6), null);
255 final SpacecraftState otherState = otherPropagator.propagate(targetDate);
256 final RealMatrix expectedJacobian = harvester6x6.getParametersJacobian(otherState);
257 assertArrayEquals(expectedJacobian.getColumn(0), Arrays.copyOfRange(actualJacobian.getColumn(0), 0, 6));
258 }
259
260 @ParameterizedTest
261 @EnumSource(OrbitType.class)
262 void testStm7x7vs6x6J2(final OrbitType orbitType) {
263
264 final NumericalPropagator propagator = buildPropagator(orbitType);
265 final String stmName = "stm";
266 final MatricesHarvester harvester7x7 = propagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(7), null);
267 final double timeOfFlight = 1e5;
268 final AbsoluteDate epoch = propagator.getInitialState().getDate();
269 final AbsoluteDate targetDate = epoch.shiftedBy(timeOfFlight);
270 final ForceModel j2Perturbation = new J2OnlyPerturbation(GravityFieldFactory.getUnnormalizedProvider(2, 0),
271 FramesFactory.getGTOD(true));
272 propagator.addForceModel(j2Perturbation);
273
274 final SpacecraftState state = propagator.propagate(targetDate);
275 final RealMatrix actualStm = harvester7x7.getStateTransitionMatrix(state);
276
277 final NumericalPropagator otherPropagator = buildPropagator(orbitType);
278 otherPropagator.addForceModel(j2Perturbation);
279 final MatricesHarvester harvester6x6 = otherPropagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(6), null);
280 final SpacecraftState otherState = otherPropagator.propagate(targetDate);
281 final RealMatrix expectedStm = harvester6x6.getStateTransitionMatrix(otherState);
282 for (int i = 0; i < 6; i++) {
283 assertArrayEquals(expectedStm.getRow(i), Arrays.copyOfRange(actualStm.getRow(i), 0, 6));
284 }
285 }
286
287 @Test
288 void testParameterJacobian7x7vs6x6ProfileThrust() {
289
290 final NumericalPropagator propagator = buildPropagator(OrbitType.CARTESIAN);
291 final String stmName = "stm";
292 final MatricesHarvester harvester7x7 = propagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(7), null);
293 final double timeOfFlight = 1e3;
294 final AbsoluteDate epoch = propagator.getInitialState().getDate();
295 final AbsoluteDate targetDate = epoch.shiftedBy(timeOfFlight);
296 final double slopeThrust = 2e-2;
297 final ThrustVectorProvider thrustVectorProvider = getThrustVector(epoch, slopeThrust);
298 final ProfileThrustPropulsionModel propulsionModel = buildProfileModel(thrustVectorProvider);
299 final AbsoluteDate startDate = epoch.shiftedBy(5e2);
300 final double duration = 3e2;
301 final Maneuver maneuver = new Maneuver(null, buildDatedBasedTriggers(startDate, duration, 0.), propulsionModel);
302 final String parameterName = "triggers" + ParameterDrivenDateIntervalDetector.DURATION_SUFFIX;
303 maneuver.getParameterDriver(parameterName).setSelected(true);
304 propagator.addForceModel(maneuver);
305
306 final SpacecraftState state = propagator.propagate(targetDate);
307 final RealMatrix actualJacobian = harvester7x7.getParametersJacobian(state);
308
309 final NumericalPropagator otherPropagator = buildPropagator(propagator.getOrbitType(), propagator.getAttitudeProvider());
310 otherPropagator.addForceModel(new Maneuver(null, buildDatedBasedTriggers(startDate, duration, 0.), propulsionModel));
311 otherPropagator.getAllForceModels().get(0).getParameterDriver(parameterName).setSelected(true);
312 final MatricesHarvester harvester6x6 = otherPropagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(6), null);
313 final SpacecraftState otherState = otherPropagator.propagate(targetDate);
314 final RealMatrix expectedJacobian = harvester6x6.getParametersJacobian(otherState);
315 assertArrayEquals(expectedJacobian.getColumn(0), Arrays.copyOfRange(actualJacobian.getColumn(0), 0, 6), 0.1);
316 }
317
318 @Test
319 void testManeuverTriggerDateParameterWithProfileThrust() {
320
321 final NumericalPropagator propagator = buildPropagator(OrbitType.CARTESIAN, null);
322 final LofOffset lofOffset = new LofOffset(propagator.getFrame(), LOFType.TNW);
323 propagator.setAttitudeProvider(lofOffset);
324 final String stmName = "stm";
325 final MatricesHarvester harvester = propagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(7), null);
326 final double duration = 100.;
327 final double timeOfFlight = duration * 3;
328 final AbsoluteDate epoch = propagator.getInitialState().getDate();
329 final AbsoluteDate targetDate = epoch.shiftedBy(timeOfFlight);
330 final AbsoluteDate startDate = epoch.shiftedBy(duration/2.);
331 final double slopeThrust = 1e-3;
332 final ThrustVectorProvider thrustVectorProvider = getThrustVector(epoch, slopeThrust);
333 final ProfileThrustPropulsionModel propulsionModel = buildProfileModel(thrustVectorProvider);
334 final Maneuver maneuver = new Maneuver(null, buildDatedBasedTriggers(startDate, duration, 0.), propulsionModel);
335 maneuver.getParameterDriver("triggers" + ParameterDrivenDateIntervalDetector.STOP_SUFFIX).setSelected(true);
336 propagator.addForceModel(maneuver);
337
338 final SpacecraftState state = propagator.propagate(targetDate);
339
340 final double dT = 1e-1;
341 final NumericalPropagator propagatorFiniteDifference1 = buildPropagator(propagator.getOrbitType(), lofOffset);
342 final double shiftBackward = -dT /2.;
343 propagatorFiniteDifference1.addForceModel(new Maneuver(null, buildDatedBasedTriggers(startDate,
344 duration, shiftBackward), buildProfileModel(thrustVectorProvider)));
345 final SpacecraftState finiteDifferencesState1 = propagatorFiniteDifference1.propagate(targetDate);
346 final NumericalPropagator propagatorFiniteDifference2 = buildPropagator(propagator.getOrbitType(), lofOffset);
347 final double shiftForward = dT /2.;
348 propagatorFiniteDifference2.addForceModel(new Maneuver(null, buildDatedBasedTriggers(startDate,
349 duration, shiftForward), buildProfileModel(thrustVectorProvider)));
350 final SpacecraftState finiteDifferencesState2 = propagatorFiniteDifference2.propagate(targetDate);
351 final PVCoordinates relativePV = new PVCoordinates(finiteDifferencesState1.getPVCoordinates(),
352 finiteDifferencesState2.getPVCoordinates());
353 final RealMatrix parameterJacobian = harvester.getParametersJacobian(state);
354 compareWithFiniteDifferences(relativePV, dT, parameterJacobian, 1e-3);
355 }
356
357 private static DateBasedManeuverTriggers buildDatedBasedTriggers(final AbsoluteDate startDate,
358 final double duration, final double shiftEnd) {
359 return new DateBasedManeuverTriggers("triggers", startDate, duration + shiftEnd);
360 }
361
362 private static ProfileThrustPropulsionModel buildProfileModel(final ThrustVectorProvider thrustVectorProvider) {
363 final TimeSpanMap<ThrustVectorProvider> providerTimeSpanMap = new TimeSpanMap<>(null);
364 providerTimeSpanMap.addValidBetween(thrustVectorProvider, AbsoluteDate.PAST_INFINITY, AbsoluteDate.FUTURE_INFINITY);
365 return new ProfileThrustPropulsionModel(providerTimeSpanMap, 10.,"man");
366 }
367
368 private static ThrustVectorProvider getThrustVector(final AbsoluteDate referenceDate, final double slope) {
369 return new ThrustVectorProvider() {
370 @Override
371 public Vector3D getThrustVector(AbsoluteDate date, double mass) {
372 final double factor = slope * FastMath.abs(date.durationFrom(referenceDate));
373 return Vector3D.PLUS_I.scalarMultiply(factor);
374 }
375
376 @Override
377 public <T extends CalculusFieldElement<T>> FieldVector3D<T> getThrustVector(FieldAbsoluteDate<T> date, T mass) {
378 return new FieldVector3D<>(mass.getField(), getThrustVector(date.toAbsoluteDate(), mass.getReal()));
379 }
380 };
381 }
382
383 private static NumericalPropagator buildPropagator(final OrbitType orbitType) {
384 final NumericalPropagator propagator = buildPropagator(orbitType, null);
385 propagator.setAttitudeProvider(new FrameAlignedProvider(propagator.getFrame()));
386 return propagator;
387 }
388
389 private static NumericalPropagator buildPropagator(final OrbitType orbitType, final AttitudeProvider attitudeProvider) {
390 final Orbit orbit = new EquinoctialOrbit(7e6, 0.001, 0.001, 1., 2., 3., PositionAngleType.MEAN,
391 FramesFactory.getGCRF(), AbsoluteDate.ARBITRARY_EPOCH, Constants.EGM96_EARTH_MU);
392 final ODEIntegrator integrator = new DormandPrince54IntegratorBuilder(1e-3, 50., 1e-4).buildIntegrator(orbit, orbitType);
393 final NumericalPropagator propagator = new NumericalPropagator(integrator, attitudeProvider);
394 propagator.setOrbitType(orbitType);
395 propagator.setResetAtEnd(false);
396 propagator.setInitialState(new SpacecraftState(orbit));
397 return propagator;
398 }
399
400 private static void compareMassGradientWithFiniteDifferences(final SpacecraftState state,
401 final ForceModel forceModel,
402 final AbsoluteDate targetDate,
403 final RealMatrix stateTransitionMatrix) {
404 final double dM = 1.;
405 final NumericalPropagator propagator1 = buildPropagator(OrbitType.CARTESIAN);
406 propagator1.resetInitialState(state.withMass(state.getMass() - dM/2.));
407 propagator1.addForceModel(forceModel);
408 final SpacecraftState propagated1 = propagator1.propagate(targetDate);
409 final NumericalPropagator propagator2 = buildPropagator(OrbitType.CARTESIAN);
410 propagator2.resetInitialState(state.withMass(state.getMass() + dM/2.));
411 propagator2.addForceModel(forceModel);
412 final SpacecraftState propagated2 = propagator2.propagate(targetDate);
413 final PVCoordinates relativePV = new PVCoordinates(propagated1.getPVCoordinates(),
414 propagated2.getPVCoordinates());
415 assertEquals(relativePV.getPosition().getX() / dM, stateTransitionMatrix.getEntry(0, 6), 1e-3);
416 assertEquals(relativePV.getPosition().getY() / dM, stateTransitionMatrix.getEntry(1, 6), 1e-3);
417 assertEquals(relativePV.getPosition().getZ() / dM, stateTransitionMatrix.getEntry(2, 6), 1e-3);
418 assertEquals(relativePV.getVelocity().getX() / dM, stateTransitionMatrix.getEntry(3, 6), 1e-5);
419 assertEquals(relativePV.getVelocity().getY() / dM, stateTransitionMatrix.getEntry(4, 6), 1e-5);
420 assertEquals(relativePV.getVelocity().getZ() / dM, stateTransitionMatrix.getEntry(5, 6), 1e-5);
421 }
422
423 @ParameterizedTest
424 @EnumSource(value = OrbitType.class, names = {"CARTESIAN", "EQUINOCTIAL", "CIRCULAR"})
425 void testRadiationPressure(final OrbitType orbitType) {
426
427 final NumericalPropagator propagator = buildPropagator(orbitType);
428 final String stmName = "stm";
429 final MatricesHarvester harvester = propagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(7), null);
430 final CylindricallyShadowedLightFluxModel shadowModel = new CylindricallyShadowedLightFluxModel(new AnalyticalSolarPositionProvider(),
431 Constants.EGM96_EARTH_EQUATORIAL_RADIUS);
432 final double crossSection = 100.;
433 final double coefficientValue = 1.6;
434 final IsotropicRadiationSingleCoefficient singleCoefficient = new IsotropicRadiationSingleCoefficient(crossSection, coefficientValue);
435 singleCoefficient.getRadiationParametersDrivers()
436 .stream().filter(driver -> driver.getName().equals(RadiationSensitive.REFLECTION_COEFFICIENT)).findFirst()
437 .ifPresent(driver -> driver.setSelected(true));
438 final RadiationPressureModel model = new RadiationPressureModel(shadowModel, singleCoefficient);
439 propagator.addForceModel(model);
440 final double timeOfFlight = 1e4;
441 final AbsoluteDate epoch = propagator.getInitialState().getDate();
442 final AbsoluteDate targetDate = epoch.shiftedBy(timeOfFlight);
443
444 final SpacecraftState state = propagator.propagate(targetDate);
445 final RealMatrix jacobianMatrix = harvester.getParametersJacobian(state);
446 final double[][] conversionJacobian = new double[6][6];
447 state.getOrbit().getJacobianWrtParameters(propagator.getPositionAngleType(), conversionJacobian);
448 final RealMatrix matrix = MatrixUtils.createRealIdentityMatrix(jacobianMatrix.getRowDimension());
449 matrix.setSubMatrix(conversionJacobian, 0, 0);
450 final RealMatrix jacobianCartesian = matrix.multiply(jacobianMatrix);
451
452 compareRadiationCoefficientGradientWithFiniteDifferences(propagator.getOrbitType(), shadowModel, crossSection,
453 coefficientValue, targetDate, jacobianCartesian);
454 final RealMatrix stm = harvester.getStateTransitionMatrix(state);
455 for (int i = 0; i < 6; i++) {
456 Assertions.assertNotEquals(0., stm.getEntry(i, 6));
457 }
458 }
459
460 private static void compareRadiationCoefficientGradientWithFiniteDifferences(final OrbitType orbitType,
461 final LightFluxModel fluxModel,
462 final double crossSection, final double singleCoefficient,
463 final AbsoluteDate targetDate,
464 final RealMatrix jacobianMatrix) {
465 final double dP = 0.01;
466 final NumericalPropagator propagator1 = buildPropagator(orbitType);
467 propagator1.addForceModel(new RadiationPressureModel(fluxModel, new IsotropicRadiationSingleCoefficient(crossSection,
468 singleCoefficient - dP /2)));
469 final SpacecraftState propagated1 = propagator1.propagate(targetDate);
470 final NumericalPropagator propagator2 = buildPropagator(propagator1.getOrbitType(), propagator1.getAttitudeProvider());
471 propagator2.addForceModel(new RadiationPressureModel(fluxModel, new IsotropicRadiationSingleCoefficient(crossSection,
472 singleCoefficient + dP /2)));
473 final SpacecraftState propagated2 = propagator2.propagate(targetDate);
474 final PVCoordinates relativePV = new PVCoordinates(propagated1.getPVCoordinates(),
475 propagated2.getPVCoordinates());
476 final double toleranceDerivativePosition = 3e-2;
477 final double toleranceDerivativeVelocity = 5e-5;
478 assertEquals(relativePV.getPosition().getX() / dP, jacobianMatrix.getEntry(0, 0), toleranceDerivativePosition);
479 assertEquals(relativePV.getPosition().getY() / dP, jacobianMatrix.getEntry(1, 0), toleranceDerivativePosition);
480 assertEquals(relativePV.getPosition().getZ() / dP, jacobianMatrix.getEntry(2, 0), toleranceDerivativePosition);
481 assertEquals(relativePV.getVelocity().getX() / dP, jacobianMatrix.getEntry(3, 0), toleranceDerivativeVelocity);
482 assertEquals(relativePV.getVelocity().getY() / dP, jacobianMatrix.getEntry(4, 0), toleranceDerivativeVelocity);
483 assertEquals(relativePV.getVelocity().getZ() / dP, jacobianMatrix.getEntry(5, 0), toleranceDerivativeVelocity);
484 }
485
486 @ParameterizedTest
487 @ValueSource(doubles = {-2e4, 2e4})
488 void testManeuverApside(final double timeOfFlight) {
489
490 final NumericalPropagator propagator = buildPropagator(OrbitType.CARTESIAN);
491 final String stmName = "stm";
492 final MatricesHarvester harvester = propagator.setupMatricesComputation(stmName, MatrixUtils.createRealIdentityMatrix(7), null);
493 final double thrustMagnitude = 5e-3;
494 final Vector3D thrustDirection = Vector3D.PLUS_I;
495 final double isp = 10;
496 final PropulsionModel propulsionModel = new BasicConstantThrustPropulsionModel(thrustMagnitude, isp,
497 thrustDirection, "");
498 final EventDetectionSettings detectionSettings = EventDetectionSettings.getDefaultEventDetectionSettings();
499 final ManeuverTriggers triggers = new ApsideTriggers(detectionSettings);
500 final Maneuver maneuver = new Maneuver(null, triggers, propulsionModel);
501 propagator.addForceModel(maneuver);
502 final AbsoluteDate epoch = propagator.getInitialState().getDate();
503 final AbsoluteDate targetDate = epoch.shiftedBy(timeOfFlight);
504 final SpacecraftState initialState = propagator.getInitialState();
505
506 final SpacecraftState state = propagator.propagate(targetDate);
507 final RealMatrix stateTransitionMatrix = harvester.getStateTransitionMatrix(state);
508
509 final double dR = 1.;
510 final double dV = 1e-3;
511 checkStmColumnWithFiniteDifferences(0, dR, propagator, initialState, maneuver, targetDate, stateTransitionMatrix.getColumnVector(0));
512 checkStmColumnWithFiniteDifferences(1, dR, propagator, initialState, maneuver, targetDate, stateTransitionMatrix.getColumnVector(1));
513 checkStmColumnWithFiniteDifferences(2, dR, propagator, initialState, maneuver, targetDate, stateTransitionMatrix.getColumnVector(2));
514 checkStmColumnWithFiniteDifferences(3, dV, propagator, initialState, maneuver, targetDate, stateTransitionMatrix.getColumnVector(3));
515 checkStmColumnWithFiniteDifferences(4, dV, propagator, initialState, maneuver, targetDate, stateTransitionMatrix.getColumnVector(4));
516 checkStmColumnWithFiniteDifferences(5, dV, propagator, initialState, maneuver, targetDate, stateTransitionMatrix.getColumnVector(5));
517 }
518
519 private void checkStmColumnWithFiniteDifferences(final int index, final double dX, final NumericalPropagator propagator,
520 final SpacecraftState initialState, final Maneuver maneuver,
521 final AbsoluteDate targetDate, final RealVector stateTransitionMatrixColumn) {
522 final double[] cartesianVector = new double[6];
523 cartesianVector[index] = -dX/2.;
524 final NumericalPropagator propagator1 = buildPropagator(propagator.getOrbitType(), propagator.getAttitudeProvider());
525 propagator1.resetInitialState(modifyState(initialState, cartesianVector));
526 propagator1.addForceModel(maneuver);
527 final SpacecraftState propagated1 = propagator1.propagate(targetDate);
528 final NumericalPropagator propagator2 = buildPropagator(propagator.getOrbitType(), propagator.getAttitudeProvider());
529 cartesianVector[index] *= -1;
530 propagator2.resetInitialState(modifyState(initialState, cartesianVector));
531 propagator2.addForceModel(maneuver);
532 final SpacecraftState propagated2 = propagator2.propagate(targetDate);
533 final PVCoordinates relativePV = new PVCoordinates(propagated1.getPVCoordinates(), propagated2.getPVCoordinates());
534 final double toleranceP = 1e0;
535 final double toleranceV = 1e-3;
536 assertEquals(relativePV.getPosition().getX() / dX, stateTransitionMatrixColumn.getEntry(0), toleranceP);
537 assertEquals(relativePV.getPosition().getY() / dX, stateTransitionMatrixColumn.getEntry(1), toleranceP);
538 assertEquals(relativePV.getPosition().getZ() / dX, stateTransitionMatrixColumn.getEntry(2), toleranceP);
539 assertEquals(relativePV.getVelocity().getX() / dX, stateTransitionMatrixColumn.getEntry(3), toleranceV);
540 assertEquals(relativePV.getVelocity().getY() / dX, stateTransitionMatrixColumn.getEntry(4), toleranceV);
541 assertEquals(relativePV.getVelocity().getZ() / dX, stateTransitionMatrixColumn.getEntry(5), toleranceV);
542 }
543
544 private static SpacecraftState modifyState(final SpacecraftState baseState, final double[] cartesianShift) {
545 final PVCoordinates pvCoordinates = baseState.getPVCoordinates();
546 final PVCoordinates newPV = new PVCoordinates(pvCoordinates.getPosition().add(new Vector3D(Arrays.copyOfRange(cartesianShift, 0, 3))),
547 pvCoordinates.getVelocity().add(new Vector3D(Arrays.copyOfRange(cartesianShift, 3, 6))));
548 final CartesianOrbit orbit = new CartesianOrbit(newPV, baseState.getFrame(), baseState.getDate(),
549 baseState.getOrbit().getMu());
550 return new SpacecraftState(orbit, baseState.getAttitude()).withMass(baseState.getMass());
551 }
552
553 private static class ApsideTriggers extends IntervalEventTrigger<ApsideDetector> {
554
555 protected ApsideTriggers(EventDetectionSettings detectionSettings) {
556 super(new ApsideDetector(detectionSettings, new ContinueOnEvent()));
557 }
558
559 @Override
560 public List<ParameterDriver> getParametersDrivers() {
561 return Collections.emptyList();
562 }
563
564 private FieldApsideDetector buildFieldApsideDetector(Field field, EventDetector detector) {
565 return new FieldApsideDetector<>(new FieldEventDetectionSettings<>(field, detector.getDetectionSettings()),
566 new FieldStopOnEvent());
567 }
568
569 @Override
570 protected <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> D convertIntervalDetector(Field<S> field, ApsideDetector detector) {
571 return (D) buildFieldApsideDetector(field, detector);
572 }
573 }
574 }
575