1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.control.indirect.shooting;
18
19 import org.hipparchus.CalculusFieldElement;
20 import org.hipparchus.Field;
21 import org.hipparchus.analysis.differentiation.Gradient;
22 import org.hipparchus.analysis.differentiation.GradientField;
23 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24 import org.hipparchus.geometry.euclidean.threed.Vector3D;
25 import org.hipparchus.ode.ODEIntegrator;
26 import org.junit.jupiter.api.Assertions;
27 import org.junit.jupiter.api.BeforeEach;
28 import org.junit.jupiter.api.Test;
29 import org.junit.jupiter.params.ParameterizedTest;
30 import org.junit.jupiter.params.provider.ValueSource;
31 import org.mockito.Mockito;
32 import org.orekit.Utils;
33 import org.orekit.control.indirect.adjoint.CartesianAdjointDerivativesProvider;
34 import org.orekit.control.indirect.adjoint.CartesianAdjointEquationTerm;
35 import org.orekit.control.indirect.adjoint.CartesianAdjointJ2Term;
36 import org.orekit.control.indirect.adjoint.CartesianAdjointKeplerianTerm;
37 import org.orekit.control.indirect.adjoint.FieldCartesianAdjointDerivativesProvider;
38 import org.orekit.control.indirect.adjoint.cost.CartesianCost;
39 import org.orekit.control.indirect.adjoint.cost.FieldCartesianCost;
40 import org.orekit.control.indirect.adjoint.cost.FieldUnboundedCartesianEnergy;
41 import org.orekit.control.indirect.adjoint.cost.FieldUnboundedCartesianEnergyNeglectingMass;
42 import org.orekit.control.indirect.adjoint.cost.UnboundedCartesianEnergy;
43 import org.orekit.control.indirect.adjoint.cost.UnboundedCartesianEnergyNeglectingMass;
44 import org.orekit.control.indirect.shooting.boundary.CartesianBoundaryConditionChecker;
45 import org.orekit.control.indirect.shooting.boundary.FixedTimeBoundaryOrbits;
46 import org.orekit.control.indirect.shooting.boundary.FixedTimeCartesianBoundaryStates;
47 import org.orekit.control.indirect.shooting.boundary.NormBasedCartesianConditionChecker;
48 import org.orekit.control.indirect.shooting.propagation.*;
49 import org.orekit.forces.ForceModel;
50 import org.orekit.forces.gravity.J2OnlyPerturbation;
51 import org.orekit.forces.gravity.NewtonianAttraction;
52 import org.orekit.frames.Frame;
53 import org.orekit.frames.FramesFactory;
54 import org.orekit.orbits.*;
55 import org.orekit.propagation.CartesianToleranceProvider;
56 import org.orekit.propagation.FieldSpacecraftState;
57 import org.orekit.propagation.SpacecraftState;
58 import org.orekit.propagation.ToleranceProvider;
59 import org.orekit.propagation.analytical.KeplerianPropagator;
60 import org.orekit.propagation.events.EventDetectionSettings;
61 import org.orekit.propagation.events.FieldEventDetectionSettings;
62 import org.orekit.propagation.numerical.NumericalPropagator;
63 import org.orekit.time.AbsoluteDate;
64 import org.orekit.time.DateTimeComponents;
65 import org.orekit.time.FieldAbsoluteDate;
66 import org.orekit.time.TimeScalesFactory;
67 import org.orekit.utils.*;
68
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.List;
72
73 class NewtonFixedBoundaryCartesianSingleShootingTest {
74
75 private static final double THRESHOLD_LU_DECOMPOSITION = 1e-11;
76 private static final String ADJOINT_NAME = "adjoint";
77
78 @BeforeEach
79 public void setUp() {
80 Utils.setDataRoot("regular-data");
81 }
82
83 @Test
84 void testUpdateShootingVariablesZeroDefects() {
85
86 final double[] originalAdjoint = new double[] { 1, 2, 3, 4, 5, 6 };
87 final GradientField field = GradientField.getField(6);
88 final TimeStampedPVCoordinates targetPV = new TimeStampedPVCoordinates(AbsoluteDate.ARBITRARY_EPOCH,
89 new PVCoordinates(new Vector3D(10., 1., 0.1), new Vector3D(0.001, 0.1, -0.0001)));
90 final TimeStampedFieldPVCoordinates<Gradient> fieldPVCoordinates = new TimeStampedFieldPVCoordinates<>(field,
91 targetPV);
92 final FieldCartesianOrbit<Gradient> fieldOrbit = new FieldCartesianOrbit<>(
93 new FieldPVCoordinates<>(fieldPVCoordinates.getPosition().add(getFieldVector3D(field, 0, 1, 2)),
94 fieldPVCoordinates.getVelocity().add(getFieldVector3D(field, 3, 4, 5))),
95 FramesFactory.getGCRF(), new FieldAbsoluteDate<>(field, targetPV.getDate()), field.getOne());
96 final FieldSpacecraftState<Gradient> fieldState = new FieldSpacecraftState<>(fieldOrbit);
97 final NewtonFixedBoundaryCartesianSingleShooting shooting = Mockito.mock(NewtonFixedBoundaryCartesianSingleShooting.class);
98 final double one = 1;
99 Mockito.when(shooting.getScalePositionDefects()).thenReturn(one);
100 Mockito.when(shooting.getScaleVelocityDefects()).thenReturn(one);
101 Mockito.when(shooting.updateShootingVariables(originalAdjoint, fieldState)).thenCallRealMethod();
102 Mockito.when(shooting.getTerminalCartesianState()).thenReturn(targetPV);
103 Mockito.when(shooting.getScales()).thenReturn(new double[] {1, 1, 1, 1, 1, 1});
104
105 final double[] adjoint = shooting.updateShootingVariables(originalAdjoint, fieldState);
106
107 Assertions.assertArrayEquals(originalAdjoint, adjoint);
108 }
109
110 private static FieldVector3D<Gradient> getFieldVector3D(final GradientField field, final int i1,
111 final int i2, final int i3) {
112 final int parametersNumber = field.getOne().getFreeParameters();
113 return new FieldVector3D<>(Gradient.variable(parametersNumber, i1, 0),
114 Gradient.variable(parametersNumber, i2, 0), Gradient.variable(parametersNumber, i3, 0));
115 }
116
117 @ParameterizedTest
118 @ValueSource(doubles = {5e5, 8e5, 1.5e6})
119 void testSolveOrbitVersusAbsolutePV(final double approximateAltitude) {
120
121 final double tolerancePosition = 1e0;
122 final double toleranceVelocity = 1e-4;
123 final CartesianBoundaryConditionChecker conditionChecker = new NormBasedCartesianConditionChecker(10,
124 tolerancePosition, toleranceVelocity);
125 final Orbit initialOrbit = createSomeInitialOrbit(approximateAltitude);
126 final double timeOfFlight = 1e4;
127 final Orbit terminalOrbit = createTerminalBoundary(initialOrbit, timeOfFlight);
128 final FixedTimeBoundaryOrbits boundaryOrbits = new FixedTimeBoundaryOrbits(initialOrbit, terminalOrbit);
129 final ShootingPropagationSettings propagationSettings = createShootingSettings(initialOrbit, 0., Double.POSITIVE_INFINITY,
130 ShootingIntegrationSettingsFactory.getClassicalRungeKuttaIntegratorSettings(60.));
131 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
132 boundaryOrbits, conditionChecker);
133 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
134 shooting.setStepFactor(1.);
135 shooting.setScalePositionDefects(1.);
136 shooting.setScaleVelocityDefects(1.);
137 final double mass = 1e3;
138 final double[] guess = new double[6];
139
140 final ShootingBoundaryOutput output = shooting.solve(mass, guess);
141
142 Assertions.assertTrue(output.isConverged());
143 Assertions.assertNotEquals(0, output.getIterationCount());
144 final PVCoordinates expectedPV = terminalOrbit.getPVCoordinates();
145 final PVCoordinates actualPV = output.getTerminalState().getPVCoordinates();
146 Assertions.assertEquals(0., expectedPV.getPosition().subtract(actualPV.getPosition()).getNorm(),
147 tolerancePosition);
148 Assertions.assertEquals(0., expectedPV.getVelocity().subtract(actualPV.getVelocity()).getNorm(),
149 toleranceVelocity);
150 compareToAbsolutePV(mass, guess, propagationSettings, boundaryOrbits, conditionChecker, 0.,
151 output);
152 }
153
154 private static ShootingPropagationSettings createShootingSettings(final Orbit initialOrbit, final double massFlowRate,
155 final double maximumThrustMagnitude,
156 final ShootingIntegrationSettings integrationSettings) {
157 final NewtonianAttraction newtonianAttraction = new NewtonianAttraction(initialOrbit.getMu());
158 final Frame j2Frame = initialOrbit.getFrame();
159 final J2OnlyPerturbation j2OnlyPerturbation = new J2OnlyPerturbation(initialOrbit.getMu(),
160 Constants.EGM96_EARTH_EQUATORIAL_RADIUS, -Constants.EGM96_EARTH_C20, j2Frame);
161 final List<ForceModel> forceModelList = new ArrayList<>();
162 forceModelList.add(newtonianAttraction);
163 forceModelList.add(j2OnlyPerturbation);
164 final CartesianAdjointKeplerianTerm keplerianTerm = new CartesianAdjointKeplerianTerm(initialOrbit.getMu());
165 final CartesianAdjointJ2Term j2Term = new CartesianAdjointJ2Term(j2OnlyPerturbation.getMu(), j2OnlyPerturbation.getrEq(),
166 j2OnlyPerturbation.getJ2(initialOrbit.getDate()), j2OnlyPerturbation.getFrame());
167 return new ShootingPropagationSettings(forceModelList, getAdjointDynamicsProvider(massFlowRate,
168 maximumThrustMagnitude, keplerianTerm, j2Term), integrationSettings);
169 }
170
171 private static Orbit createSomeInitialOrbit(final double approximateAltitude) {
172 return new KeplerianOrbit(Constants.EGM96_EARTH_EQUATORIAL_RADIUS + approximateAltitude, 1e-4, 1., 2., 3., 4., PositionAngleType.ECCENTRIC,
173 FramesFactory.getGCRF(), AbsoluteDate.ARBITRARY_EPOCH, Constants.EGM96_EARTH_MU);
174 }
175
176 private static Orbit createTerminalBoundary(final Orbit initialOrbit, final double timeOfFlight) {
177 final KeplerianPropagator propagator = new KeplerianPropagator(initialOrbit);
178 final AbsoluteDate epoch = initialOrbit.getDate();
179 return propagator.propagate(epoch.shiftedBy(timeOfFlight)).getOrbit();
180 }
181
182 private void compareToAbsolutePV(final double mass, final double[] guess,
183 final ShootingPropagationSettings propagationSettings,
184 final FixedTimeBoundaryOrbits boundaryOrbits,
185 final CartesianBoundaryConditionChecker conditionChecker,
186 final double toleranceMassAdjoint,
187 final ShootingBoundaryOutput output) {
188 final Orbit initialOrbit = boundaryOrbits.getInitialOrbit();
189 final Orbit terminalOrbit = boundaryOrbits.getTerminalOrbit();
190 final FixedTimeCartesianBoundaryStates boundaryStates = new FixedTimeCartesianBoundaryStates(convertToAbsolutePVCoordinates(initialOrbit),
191 convertToAbsolutePVCoordinates(terminalOrbit));
192 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
193 boundaryStates, conditionChecker);
194 shooting.setStepFactor(1.);
195 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
196 shooting.setToleranceMassAdjoint(toleranceMassAdjoint);
197 final ShootingBoundaryOutput otherOutput = shooting.solve(mass, guess);
198 Assertions.assertEquals(otherOutput.getIterationCount(), output.getIterationCount());
199 Assertions.assertEquals(otherOutput.getTerminalState().getPosition(), otherOutput.getTerminalState().getPosition());
200 final String adjointName = propagationSettings.getAdjointDynamicsProvider().getAdjointName();
201 Assertions.assertArrayEquals(output.getInitialState().getAdditionalState(adjointName),
202 otherOutput.getInitialState().getAdditionalState(adjointName));
203 }
204
205 private static AbsolutePVCoordinates convertToAbsolutePVCoordinates(final Orbit orbit) {
206 return new AbsolutePVCoordinates(orbit.getFrame(), orbit.getDate(), orbit.getPVCoordinates());
207 }
208
209 @Test
210 void testSolveSequential() {
211
212 final double tolerancePosition = 1e-0;
213 final double toleranceVelocity = 1e-4;
214 final CartesianBoundaryConditionChecker conditionChecker = new NormBasedCartesianConditionChecker(10,
215 tolerancePosition, toleranceVelocity);
216 final Orbit initialOrbit = createSomeInitialOrbit(1e6);
217 final double timeOfFlight = 1e4;
218 final Orbit terminalOrbit = createTerminalBoundary(initialOrbit, timeOfFlight);
219 final FixedTimeBoundaryOrbits boundaryOrbits = new FixedTimeBoundaryOrbits(initialOrbit, terminalOrbit);
220 final double flowRateFactor = 1e-3;
221 final ShootingPropagationSettings propagationSettings = createShootingSettings(initialOrbit, flowRateFactor, Double.POSITIVE_INFINITY,
222 ShootingIntegrationSettingsFactory.getDormandPrince54IntegratorSettings(1e-1, 1e2,
223 ToleranceProvider.of(CartesianToleranceProvider.of(1e-3, 1e-6, CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE))));
224 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
225 boundaryOrbits, conditionChecker);
226 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
227 shooting.setStepFactor(1.);
228 final double toleranceMassAdjoint = 1e-10;
229 shooting.setToleranceMassAdjoint(toleranceMassAdjoint);
230 final double mass = 1e3;
231 final double[] guess = new double[]{-2.305656141544546E-6, -6.050107349447073E-6, -4.484389270662034E-6,
232 -9.635757291472267E-4, -0.0026076008704216066, 8.621848929368622E-5, 0.};
233
234 final ShootingBoundaryOutput output = shooting.solve(mass, guess);
235
236 final double thrustBound = 1e5;
237 final ShootingPropagationSettings propagationSettingsBoundedEnergy = createShootingSettings(initialOrbit,
238 flowRateFactor, thrustBound, propagationSettings.getIntegrationSettings());
239 final NewtonFixedBoundaryCartesianSingleShooting shootingBoundedEnergy = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettingsBoundedEnergy,
240 boundaryOrbits, conditionChecker);
241 shootingBoundedEnergy.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
242 final double[] unboundedEnergyAdjoint = output.getInitialState().getAdditionalState(ADJOINT_NAME);
243 double[] guessBoundedEnergy = unboundedEnergyAdjoint.clone();
244 final ShootingBoundaryOutput outputBoundedEnergy = shootingBoundedEnergy.solve(mass, guessBoundedEnergy);
245 Assertions.assertTrue(outputBoundedEnergy.isConverged());
246 Assertions.assertEquals(0, outputBoundedEnergy.getIterationCount());
247 }
248
249 @Test
250 void testSolveRegression() {
251
252 final double massFlowRateFactor = 2e-6;
253 final Orbit initialOrbit = createSomeInitialOrbit(1e6);
254 final double timeOfFlight = initialOrbit.getKeplerianPeriod() * 5;
255 final Orbit terminalOrbit = createTerminalBoundary(initialOrbit, timeOfFlight);
256 final NewtonFixedBoundaryCartesianSingleShooting shooting = getShootingMethod(massFlowRateFactor,
257 new FixedTimeBoundaryOrbits(initialOrbit, terminalOrbit),
258 ShootingIntegrationSettingsFactory.getClassicalRungeKuttaIntegratorSettings(100.));
259 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
260 shooting.setStepFactor(1.);
261 final double toleranceMassAdjoint = 1e-10;
262 shooting.setToleranceMassAdjoint(toleranceMassAdjoint);
263 final double mass = 1.;
264 final double[] guess = {-1.3440754763650783E-6, -6.346307866897998E-6, -4.25736594074492E-6,
265 -4.54324936872417E-4, -0.0020329894350755227, -8.358161689612435E-4, 0.};
266
267 final ShootingBoundaryOutput output = shooting.solve(mass, guess);
268
269 Assertions.assertTrue(output.isConverged());
270 final double[] initialAdjoint = output.getInitialState().getAdditionalState(shooting.getPropagationSettings()
271 .getAdjointDynamicsProvider().getAdjointName());
272 final double[] expectedAdjoint = new double[] {-1.3432883741256684E-6, -6.343244627959342E-6, -4.2552646864846415E-6,
273 -4.540374638007354E-4, -0.002031906384904598, -8.355018662664441E-4, -1.0320210230861449};
274 final double tolerance = 1e-8;
275 for (int i = 0; i < expectedAdjoint.length; i++) {
276 Assertions.assertEquals(expectedAdjoint[i], initialAdjoint[i], tolerance);
277 }
278 Assertions.assertNotEquals(1., output.getTerminalState().getMass());
279 }
280
281 private static NewtonFixedBoundaryCartesianSingleShooting getShootingMethod(final double massFlowRateFactor,
282 final FixedTimeBoundaryOrbits fixedTimeBoundaryOrbits,
283 final ShootingIntegrationSettings integrationSettings) {
284 final double tolerancePosition = 1e-0;
285 final double toleranceVelocity = 1e-4;
286 final CartesianBoundaryConditionChecker conditionChecker = new NormBasedCartesianConditionChecker(10,
287 tolerancePosition, toleranceVelocity);
288 final FixedTimeBoundaryOrbits boundaryOrbits = new FixedTimeBoundaryOrbits(fixedTimeBoundaryOrbits.getInitialOrbit(),
289 fixedTimeBoundaryOrbits.getTerminalOrbit());
290 final ShootingPropagationSettings propagationSettings = createShootingSettings(fixedTimeBoundaryOrbits.getInitialOrbit(),
291 massFlowRateFactor, Double.POSITIVE_INFINITY, integrationSettings);
292 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
293 boundaryOrbits, conditionChecker);
294 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
295 shooting.setStepFactor(1.);
296 shooting.setScalePositionDefects(1e3);
297 shooting.setScaleVelocityDefects(1.);
298 return shooting;
299 }
300
301 @Test
302 void testSolveForwardBackward() {
303
304 final Orbit initialOrbit = createGeoInitialOrbit();
305 final double timeOfFlight = initialOrbit.getKeplerianPeriod() * 3;
306 final Orbit terminalOrbit = createTerminalBoundary(initialOrbit, timeOfFlight);
307 final ShootingIntegrationSettings integrationSettings = ShootingIntegrationSettingsFactory.getClassicalRungeKuttaIntegratorSettings(100.);
308 final NewtonFixedBoundaryCartesianSingleShooting shooting = getShootingMethod(0.,
309 new FixedTimeBoundaryOrbits(initialOrbit, terminalOrbit), integrationSettings);
310 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
311 shooting.setStepFactor(1.);
312 final double toleranceMassAdjoint = 1e-10;
313 final double initialMass = 3e3;
314 shooting.setToleranceMassAdjoint(toleranceMassAdjoint);
315 final double[] guess = new double[] {-1.429146468892837E-10, 4.5022870335769276E-11, -1.318194179536703E-12,
316 -1.098381235039422E-6, -1.6798876678906052E-6, -3.207856651454041E-9};
317
318 final ShootingBoundaryOutput forwardOutput = shooting.solve(initialMass, guess);
319
320 final SpacecraftState terminalState = forwardOutput.getTerminalState();
321 final String adjointName = ADJOINT_NAME;
322 final double[] terminalAdjointForward = terminalState.getAdditionalState(adjointName);
323 final NewtonFixedBoundaryCartesianSingleShooting backwardShooting = getShootingMethod(0.,
324 new FixedTimeBoundaryOrbits(terminalOrbit, initialOrbit), integrationSettings);
325 backwardShooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
326 final ShootingBoundaryOutput backwardOutput = backwardShooting.solve(terminalState.getMass(), terminalAdjointForward);
327 Assertions.assertTrue(backwardOutput.isConverged());
328 Assertions.assertEquals(0, backwardOutput.getIterationCount());
329 final double[] initialAdjointForward = forwardOutput.getInitialState().getAdditionalState(adjointName);
330 final double[] terminalAdjointBackward = backwardOutput.getTerminalState().getAdditionalState(adjointName);
331 for (int i = 0; i < initialAdjointForward.length; i++) {
332 Assertions.assertEquals(initialAdjointForward[i], terminalAdjointBackward[i], 1e-13);
333 }
334 }
335
336 private static Orbit createGeoInitialOrbit() {
337 return new KeplerianOrbit(Constants.EGM96_EARTH_EQUATORIAL_RADIUS + 35000e3, 1e-5, 0.001, 2., 3., 4., PositionAngleType.ECCENTRIC,
338 FramesFactory.getGCRF(), AbsoluteDate.ARBITRARY_EPOCH, Constants.EGM96_EARTH_MU);
339 }
340
341 @ParameterizedTest
342 @ValueSource(doubles = {1, 10, 100, 1000})
343 void testSolveScales(final double scale) {
344
345 final double tolerancePosition = 1e1;
346 final double toleranceVelocity = 1e-3;
347 final CartesianBoundaryConditionChecker conditionChecker = new NormBasedCartesianConditionChecker(10,
348 tolerancePosition, toleranceVelocity);
349 final FixedTimeBoundaryOrbits boundaryOrbits = createBoundaryForKeplerianSettings();
350 final ShootingIntegrationSettings integrationSettings = ShootingIntegrationSettingsFactory.getDormandPrince54IntegratorSettings(1e-2, 2e2,
351 ToleranceProvider.of(CartesianToleranceProvider.of(1e-3, 1e-6, CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE)));
352 final ShootingPropagationSettings propagationSettings = createKeplerianShootingSettings(boundaryOrbits.getInitialOrbit(),
353 0, integrationSettings);
354
355 final ShootingBoundaryOutput output = getShootingBoundaryOutput(propagationSettings, boundaryOrbits, conditionChecker, scale);
356
357 final ShootingBoundaryOutput expectedOutput = getShootingBoundaryOutput(propagationSettings, boundaryOrbits, conditionChecker, 1);
358 Assertions.assertEquals(expectedOutput.getIterationCount(), output.getIterationCount());
359 Assertions.assertArrayEquals(expectedOutput.getInitialState().getAdditionalState(ADJOINT_NAME),
360 output.getInitialState().getAdditionalState(ADJOINT_NAME), 1e-20);
361 }
362
363 private static ShootingBoundaryOutput getShootingBoundaryOutput(final ShootingPropagationSettings propagationSettings,
364 final FixedTimeBoundaryOrbits boundaryOrbits,
365 final CartesianBoundaryConditionChecker conditionChecker,
366 final double scale) {
367 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
368 boundaryOrbits, conditionChecker);
369 shooting.setStepFactor(1.);
370 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
371 shooting.setStepFactor(1.);
372 final double toleranceMassAdjoint = 1e-8;
373 shooting.setToleranceMassAdjoint(toleranceMassAdjoint);
374 final double mass = 1e3;
375 final double[] guess = new double[6];
376 final double[] scales = guess.clone();
377 Arrays.fill(scales, scale);
378 return shooting.solve(mass, guess, scales);
379 }
380
381 @ParameterizedTest
382 @ValueSource(doubles = {1e-4, 1e-3, 1e-2})
383 void testSolveUnboundedCartesianEnergy(final double flowRateFactor) {
384
385 final double tolerancePosition = 1e1;
386 final double toleranceVelocity = 1e-3;
387 final CartesianBoundaryConditionChecker conditionChecker = new NormBasedCartesianConditionChecker(10,
388 tolerancePosition, toleranceVelocity);
389 final FixedTimeBoundaryOrbits boundaryOrbits = createBoundaryForKeplerianSettings();
390 final ShootingIntegrationSettings integrationSettings = ShootingIntegrationSettingsFactory.getDormandPrince54IntegratorSettings(1e-2, 2e2,
391 ToleranceProvider.of(CartesianToleranceProvider.of(1e-3, 1e-6, CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE)));
392 final ShootingPropagationSettings propagationSettings = createKeplerianShootingSettings(boundaryOrbits.getInitialOrbit(),
393 flowRateFactor, integrationSettings);
394 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
395 boundaryOrbits, conditionChecker);
396 shooting.setStepFactor(1);
397 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
398 final double toleranceMassAdjoint = 1e-8;
399 shooting.setToleranceMassAdjoint(toleranceMassAdjoint);
400 final double mass = 1e3;
401 final double[] guess = guessWithoutMass(propagationSettings.getAdjointDynamicsProvider().getAdjointName(), mass, integrationSettings, boundaryOrbits,
402 conditionChecker);
403
404 final ShootingBoundaryOutput output = shooting.solve(mass, guess);
405
406 Assertions.assertTrue(output.isConverged());
407 }
408
409 private static FixedTimeBoundaryOrbits createBoundaryForKeplerianSettings() {
410 final Orbit initialOrbit = createSomeInitialOrbit(2e6);
411 final PVCoordinates templatePV = initialOrbit.getPVCoordinates();
412 final Orbit modifiedOrbit = new CartesianOrbit(new PVCoordinates(templatePV.getPosition().add(new Vector3D(1e3, 2e3, 3e3)), templatePV.getVelocity()),
413 initialOrbit.getFrame(), initialOrbit.getDate(), initialOrbit.getMu());
414 final double timeOfFlight = 1e5;
415 final Orbit terminalOrbit = createTerminalBoundary(modifiedOrbit, timeOfFlight);
416 return new FixedTimeBoundaryOrbits(modifiedOrbit, terminalOrbit);
417 }
418
419 private static ShootingPropagationSettings createKeplerianShootingSettings(final Orbit initialOrbit,
420 final double massFlowRateFactor,
421 final ShootingIntegrationSettings integrationSettings) {
422 final NewtonianAttraction newtonianAttraction = new NewtonianAttraction(initialOrbit.getMu());
423 final List<ForceModel> forceModelList = new ArrayList<>();
424 forceModelList.add(newtonianAttraction);
425 final CartesianAdjointKeplerianTerm keplerianTerm = new CartesianAdjointKeplerianTerm(initialOrbit.getMu());
426 final AdjointDynamicsProvider adjointDynamicsProvider = getAdjointDynamicsProvider(massFlowRateFactor, Double.POSITIVE_INFINITY,
427 keplerianTerm);
428 return new ShootingPropagationSettings(forceModelList, adjointDynamicsProvider, integrationSettings);
429 }
430
431 private static double[] guessWithoutMass(final String adjointName, final double mass,
432 final ShootingIntegrationSettings integrationSettings,
433 final FixedTimeBoundaryOrbits boundaryOrbits,
434 final CartesianBoundaryConditionChecker conditionChecker) {
435 final ShootingPropagationSettings propagationSettings = createKeplerianShootingSettings(boundaryOrbits.getInitialOrbit(),
436 0., integrationSettings);
437 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
438 boundaryOrbits, conditionChecker);
439 shooting.setStepFactor(1.);
440 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
441 final ShootingBoundaryOutput output = shooting.solve(mass, new double[6]);
442 final double squaredMass = mass * mass;
443 final double[] adjoint = output.getInitialState().getAdditionalState(adjointName);
444 final double[] adjointWithMass = new double[7];
445 for (int i = 0; i < adjoint.length; i++) {
446 adjointWithMass[i] = adjoint[i] * squaredMass;
447 }
448 adjointWithMass[adjointWithMass.length - 1] = -1.;
449 return adjointWithMass;
450 }
451
452 private static AdjointDynamicsProvider getAdjointDynamicsProvider(final double massFlowRateFactor,
453 final double maximumThrustMagnitude,
454 final CartesianAdjointEquationTerm... terms) {
455 final String adjointName = ADJOINT_NAME;
456 if (maximumThrustMagnitude == Double.POSITIVE_INFINITY) {
457 if (massFlowRateFactor == 0) {
458 return CartesianAdjointDynamicsProviderFactory.buildUnboundedEnergyProviderNeglectingMass(adjointName,
459 terms);
460 }
461 return CartesianAdjointDynamicsProviderFactory.buildUnboundedEnergyProvider(adjointName, massFlowRateFactor,
462 EventDetectionSettings.getDefaultEventDetectionSettings(), terms);
463 } else {
464 return CartesianAdjointDynamicsProviderFactory.buildBoundedEnergyProvider(adjointName, massFlowRateFactor,
465 maximumThrustMagnitude, EventDetectionSettings.getDefaultEventDetectionSettings(), terms);
466 }
467 }
468
469 private NewtonFixedBoundaryCartesianSingleShooting getHeliocentricShootingMethod(final double massFlowRateFactor,
470 final FixedTimeBoundaryOrbits boundaryOrbits) {
471
472 final double tolerancePosition = 1e5;
473 final double toleranceVelocity = 1e0;
474 final CartesianBoundaryConditionChecker conditionChecker = new NormBasedCartesianConditionChecker(10,
475 tolerancePosition, toleranceVelocity);
476 final ShootingIntegrationSettings integrationSettings = ShootingIntegrationSettingsFactory.getDormandPrince54IntegratorSettings(2e2, 1e5,
477 ToleranceProvider.of(CartesianToleranceProvider.of(1e5, 1e-1, CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE)));
478 final EventDetectionSettings detectionSettings = new EventDetectionSettings(1e5, 1e3, EventDetectionSettings.DEFAULT_MAX_ITER);
479 final ShootingPropagationSettings propagationSettings = createHeliocentricShootingSettings(boundaryOrbits.getInitialOrbit(),
480 massFlowRateFactor, detectionSettings, integrationSettings);
481 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
482 boundaryOrbits, conditionChecker);
483 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
484 final double toleranceMassAdjoint = 1e-7;
485 shooting.setToleranceMassAdjoint(toleranceMassAdjoint);
486 return shooting;
487 }
488
489 @Test
490 void testGetSingularityThreshold() {
491
492 final FixedTimeBoundaryOrbits boundaryOrbits = getHeliocentricBoundary();
493 final NewtonFixedBoundaryCartesianSingleShooting shooting = getHeliocentricShootingMethod(1. / (4000. * Constants.G0_STANDARD_GRAVITY),
494 boundaryOrbits);
495 final double expectedThreshold = 1.;
496 shooting.setSingularityThreshold(expectedThreshold);
497
498 final double actualThreshold = shooting.getSingularityThreshold();
499
500 Assertions.assertEquals(expectedThreshold, actualThreshold);
501 }
502
503 @Test
504 void testSolveHeliocentric() {
505
506 final FixedTimeBoundaryOrbits boundaryOrbits = getHeliocentricBoundary();
507 final NewtonFixedBoundaryCartesianSingleShooting shooting = getHeliocentricShootingMethod(1. / (4000. * Constants.G0_STANDARD_GRAVITY),
508 boundaryOrbits);
509 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
510 final double mass = 2e3;
511 final double[] guess = guessWithoutMass(ADJOINT_NAME, mass, shooting.getPropagationSettings().getIntegrationSettings(),
512 boundaryOrbits, shooting.getConditionChecker());
513
514 final ShootingBoundaryOutput output = shooting.solve(mass, guess);
515
516 Assertions.assertTrue(output.isConverged());
517 }
518
519 private static FixedTimeBoundaryOrbits getHeliocentricBoundary() {
520 final double mu = Constants.JPL_SSD_SUN_GM;
521 final Frame frame = FramesFactory.getGCRF();
522 final AbsoluteDate date = new AbsoluteDate(new DateTimeComponents(2035, 1, 1, 0, 0, 0),
523 TimeScalesFactory.getUTC());
524 final Vector3D position = new Vector3D(269630575634.1845, -317928797663.87445, -117503661424.1842);
525 final Vector3D velocity = new Vector3D(12803.992418160833, 12346.009014593829, 2789.3378661767967);
526 final Orbit initialOrbit = new CartesianOrbit(new TimeStampedPVCoordinates(date, position, velocity), frame, mu);
527 final AbsoluteDate terminalDate = new AbsoluteDate(new DateTimeComponents(2038, 4, 20, 7, 48, 0),
528 TimeScalesFactory.getUTC());
529 final Vector3D terminalPosition = new Vector3D(-254040098474.26975, 292309940514.6629, 61765199864.609174);
530 final Vector3D terminalVelocity = new Vector3D(-15342.352873059252, -10427.635262141607, -7365.033285214819);
531 final Orbit terminalOrbit = new CartesianOrbit(new TimeStampedPVCoordinates(terminalDate, terminalPosition, terminalVelocity), frame, mu);
532 return new FixedTimeBoundaryOrbits(initialOrbit, terminalOrbit);
533 }
534
535 private static ShootingPropagationSettings createHeliocentricShootingSettings(final Orbit initialOrbit,
536 final double massFlowRateFactor,
537 final EventDetectionSettings eventDetectionSettings,
538 final ShootingIntegrationSettings integrationSettings) {
539 final NewtonianAttraction newtonianAttraction = new NewtonianAttraction(initialOrbit.getMu());
540 final List<ForceModel> forceModelList = new ArrayList<>();
541 forceModelList.add(newtonianAttraction);
542 final CartesianAdjointKeplerianTerm keplerianTerm = new CartesianAdjointKeplerianTerm(initialOrbit.getMu());
543 final int dimension = (massFlowRateFactor == 0) ? 6 : 7;
544 final CartesianAdjointDynamicsProvider adjointDynamicsProvider = new CartesianAdjointDynamicsProvider(ADJOINT_NAME, dimension) {
545
546 @Override
547 public CartesianAdjointDerivativesProvider buildAdditionalDerivativesProvider() {
548 final CartesianCost cost;
549 if (massFlowRateFactor == 0) {
550 cost = new UnboundedCartesianEnergyNeglectingMass(getAdjointName());
551 } else {
552 cost = new UnboundedCartesianEnergy(getAdjointName(), massFlowRateFactor, eventDetectionSettings);
553 }
554 return new CartesianAdjointDerivativesProvider(cost, keplerianTerm);
555 }
556
557 @Override
558 public <T extends CalculusFieldElement<T>> FieldCartesianAdjointDerivativesProvider<T> buildFieldAdditionalDerivativesProvider(Field<T> field) {
559 final FieldCartesianCost<T> cost;
560 if (massFlowRateFactor == 0) {
561 cost = new FieldUnboundedCartesianEnergyNeglectingMass<>(getAdjointName(), field);
562 } else {
563 cost = new FieldUnboundedCartesianEnergy<>(getAdjointName(), field.getZero().newInstance(massFlowRateFactor),
564 new FieldEventDetectionSettings<>(field, eventDetectionSettings));
565 }
566 return new FieldCartesianAdjointDerivativesProvider<>(cost, keplerianTerm);
567 }
568 };
569 return new ShootingPropagationSettings(forceModelList, adjointDynamicsProvider, integrationSettings);
570 }
571
572 @Test
573 void testSolveHeliocentricWithoutMass() {
574
575 final FixedTimeBoundaryOrbits boundaryOrbits = getHeliocentricBoundary();
576 final NewtonFixedBoundaryCartesianSingleShooting shooting = getHeliocentricShootingMethod(0.,
577 boundaryOrbits);
578 shooting.setStepFactor(1.);
579 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
580 final double mass = 3e3;
581
582 final ShootingBoundaryOutput output = shooting.solve(mass, new double[6]);
583
584 Assertions.assertTrue(output.isConverged());
585 final SpacecraftState repropagatedState = repropagate(shooting.getPropagationSettings(), output.getInitialState(),
586 boundaryOrbits.getTerminalOrbit().getDate());
587 final Vector3D relativePosition = repropagatedState.getPosition().subtract(boundaryOrbits.getTerminalOrbit().getPosition());
588 Assertions.assertEquals(0, relativePosition.getNorm(), 1e2);
589 }
590
591 private SpacecraftState repropagate(final ShootingPropagationSettings propagationSettings,
592 final SpacecraftState initialState, final AbsoluteDate terminalDate) {
593 final OrbitType orbitType = OrbitType.CARTESIAN;
594 final ODEIntegrator integrator = propagationSettings.getIntegrationSettings().getIntegratorBuilder()
595 .buildIntegrator(initialState.getOrbit(), orbitType);
596 final NumericalPropagator propagator = new NumericalPropagator(integrator);
597 propagator.setInitialState(initialState);
598 propagator.setOrbitType(orbitType);
599 propagator.addAdditionalDerivativesProvider(propagationSettings.getAdjointDynamicsProvider().buildAdditionalDerivativesProvider());
600 return propagator.propagate(terminalDate);
601 }
602
603 @Test
604 void testQuadraticContinuation() {
605
606 final double tolerancePosition = 1e-0;
607 final double toleranceVelocity = 1e-4;
608 final CartesianBoundaryConditionChecker conditionChecker = new NormBasedCartesianConditionChecker(10,
609 tolerancePosition, toleranceVelocity);
610 final Orbit initialOrbit = createSomeInitialOrbit(2e6);
611 final double timeOfFlight = 1e4;
612 final Orbit terminalOrbit = createTerminalBoundary(initialOrbit, timeOfFlight);
613 final FixedTimeBoundaryOrbits boundaryOrbits = new FixedTimeBoundaryOrbits(initialOrbit, terminalOrbit);
614 final double flowRateFactor = 1e-2;
615 final ShootingIntegrationSettings integrationSettings = ShootingIntegrationSettingsFactory
616 .getDormandPrince54IntegratorSettings(1e-1, 1e2, ToleranceProvider.of(CartesianToleranceProvider.of(1e-3, 1e-6, CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE)));
617 final double maximumThrust = 1e1;
618 final ShootingPropagationSettings propagationSettings = createShootingSettingsForQuadraticPenalty(initialOrbit, flowRateFactor,
619 maximumThrust, 1., integrationSettings);
620 final NewtonFixedBoundaryCartesianSingleShooting shooting = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettings,
621 boundaryOrbits, conditionChecker);
622 shooting.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
623 final double toleranceMassAdjoint = 1e-10;
624 shooting.setToleranceMassAdjoint(toleranceMassAdjoint);
625 final double mass = 2e3;
626 final double[] guess = new double[]{-1.3144902474363005, -7.770298698677809, -5.328110916176676,
627 -275.6031033370172, -3049.432734131893, -1560.1101732794737, -172.14906242868724};
628 ShootingBoundaryOutput output = shooting.solve(mass, guess);
629
630 for (double epsilon = 0.9; epsilon > 0.7; epsilon -= 0.05) {
631 double[] previousAdjoint = output.getInitialState().getAdditionalState(ADJOINT_NAME);
632 final ShootingPropagationSettings propagationSettingsWithNewEpsilon = createShootingSettingsForQuadraticPenalty(initialOrbit,
633 flowRateFactor, maximumThrust, epsilon, propagationSettings.getIntegrationSettings());
634 final NewtonFixedBoundaryCartesianSingleShooting shootingWithNewEpsilon = new NewtonFixedBoundaryCartesianSingleShooting(propagationSettingsWithNewEpsilon,
635 boundaryOrbits, conditionChecker);
636 shootingWithNewEpsilon.setSingularityThreshold(THRESHOLD_LU_DECOMPOSITION);
637 output = shootingWithNewEpsilon.solve(mass, previousAdjoint);
638 Assertions.assertTrue(output.isConverged());
639 }
640 }
641
642 private static ShootingPropagationSettings createShootingSettingsForQuadraticPenalty(final Orbit initialOrbit,
643 final double massFlowRate,
644 final double maximumThrustMagnitude,
645 final double epsilon,
646 final ShootingIntegrationSettings integrationSettings) {
647 final NewtonianAttraction newtonianAttraction = new NewtonianAttraction(initialOrbit.getMu());
648 final Frame j2Frame = initialOrbit.getFrame();
649 final J2OnlyPerturbation j2OnlyPerturbation = new J2OnlyPerturbation(initialOrbit.getMu(),
650 Constants.EGM96_EARTH_EQUATORIAL_RADIUS, -Constants.EGM96_EARTH_C20, j2Frame);
651 final List<ForceModel> forceModelList = new ArrayList<>();
652 forceModelList.add(newtonianAttraction);
653 forceModelList.add(j2OnlyPerturbation);
654 final CartesianAdjointKeplerianTerm keplerianTerm = new CartesianAdjointKeplerianTerm(initialOrbit.getMu());
655 final CartesianAdjointJ2Term j2Term = new CartesianAdjointJ2Term(j2OnlyPerturbation.getMu(), j2OnlyPerturbation.getrEq(),
656 j2OnlyPerturbation.getJ2(initialOrbit.getDate()), j2OnlyPerturbation.getFrame());
657 return new ShootingPropagationSettings(forceModelList,
658 CartesianAdjointDynamicsProviderFactory.buildQuadraticPenaltyFuelCostProvider(ADJOINT_NAME,
659 massFlowRate, maximumThrustMagnitude, epsilon, EventDetectionSettings.getDefaultEventDetectionSettings(),
660 keplerianTerm, j2Term), integrationSettings);
661 }
662 }