1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.forces.maneuvers;
18
19 import org.hipparchus.CalculusFieldElement;
20 import org.hipparchus.Field;
21 import org.hipparchus.geometry.euclidean.threed.Vector3D;
22 import org.hipparchus.ode.nonstiff.DormandPrince54FieldIntegrator;
23 import org.hipparchus.ode.nonstiff.DormandPrince54Integrator;
24 import org.hipparchus.util.Decimal64Field;
25 import org.hipparchus.util.FastMath;
26 import org.hipparchus.util.MathUtils;
27 import org.junit.Assert;
28 import org.junit.Before;
29 import org.junit.Test;
30 import org.orekit.Utils;
31 import org.orekit.attitudes.AttitudeProvider;
32 import org.orekit.attitudes.LofOffset;
33 import org.orekit.errors.OrekitException;
34 import org.orekit.errors.OrekitMessages;
35 import org.orekit.forces.maneuvers.propulsion.ThrustDirectionAndAttitudeProvider;
36 import org.orekit.forces.maneuvers.trigger.DateBasedManeuverTriggers;
37 import org.orekit.forces.maneuvers.trigger.EventBasedManeuverTriggers;
38 import org.orekit.frames.Frame;
39 import org.orekit.frames.FramesFactory;
40 import org.orekit.frames.LOFType;
41 import org.orekit.orbits.EquinoctialOrbit;
42 import org.orekit.orbits.FieldKeplerianOrbit;
43 import org.orekit.orbits.KeplerianOrbit;
44 import org.orekit.orbits.Orbit;
45 import org.orekit.orbits.OrbitType;
46 import org.orekit.orbits.PositionAngle;
47 import org.orekit.propagation.FieldSpacecraftState;
48 import org.orekit.propagation.SpacecraftState;
49 import org.orekit.propagation.events.AbstractDetector;
50 import org.orekit.propagation.events.BooleanDetector;
51 import org.orekit.propagation.events.EventDetector;
52 import org.orekit.propagation.events.NegateDetector;
53 import org.orekit.propagation.events.PositionAngleDetector;
54 import org.orekit.propagation.events.handlers.ContinueOnEvent;
55 import org.orekit.propagation.events.handlers.EventHandler;
56 import org.orekit.propagation.events.handlers.StopOnEvent;
57 import org.orekit.propagation.numerical.FieldNumericalPropagator;
58 import org.orekit.propagation.numerical.NumericalPropagator;
59 import org.orekit.time.AbsoluteDate;
60 import org.orekit.time.DateComponents;
61 import org.orekit.time.FieldAbsoluteDate;
62 import org.orekit.time.TimeComponents;
63 import org.orekit.time.TimeScalesFactory;
64 import org.orekit.utils.Constants;
65 import org.orekit.utils.FieldPVCoordinates;
66 import org.orekit.utils.IERSConventions;
67
68 public class ConfigurableLowThrustManeuverTest {
69
70 private static double maxCheck = 100;
71
72 private static double maxThreshold = AbstractDetector.DEFAULT_THRESHOLD;
73
74
75 private double thrust = 2e-3;
76
77 private double isp = 800;
78
79 private double halfThrustArc = FastMath.PI / 4;
80
81 @Before
82 public void setUp() {
83 Utils.setDataRoot("regular-data");
84 }
85
86 public abstract class EquinoctialLongitudeIntervalDetector<T extends EventDetector>
87 extends AbstractDetector<EquinoctialLongitudeIntervalDetector<T>> {
88
89
90 private double halfArcLength;
91
92 private final PositionAngle type;
93
94
95
96
97
98
99
100
101 protected EquinoctialLongitudeIntervalDetector(final double halfArcLength, final PositionAngle type,
102 final EventHandler<? super EquinoctialLongitudeIntervalDetector<T>> handler) {
103 this(halfArcLength, type, maxCheck, maxThreshold, DEFAULT_MAX_ITER, handler);
104 }
105
106 public EquinoctialLongitudeIntervalDetector(final double halfArcLength, final PositionAngle type,
107 final double maxCheck, final double threshold, final int maxIter,
108 final EventHandler<? super EquinoctialLongitudeIntervalDetector<T>> handler) {
109 super(maxCheck, threshold, maxIter, handler);
110 this.halfArcLength = halfArcLength;
111 this.type = type;
112 }
113
114 public abstract double getReferenceEquinoctialLongitude(SpacecraftState s);
115
116 @Override
117 public double g(final SpacecraftState s) {
118 if (halfArcLength <= 0) {
119 return -1;
120 }
121
122 final EquinoctialOrbit orbit = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(s.getOrbit());
123 final double current = MathUtils.normalizeAngle(orbit.getL(type), 0);
124 final double ret;
125 final double lonStart = getReferenceEquinoctialLongitude(s) - halfArcLength;
126 final double lonEnd = getReferenceEquinoctialLongitude(s) + halfArcLength;
127
128 final double diffStart = MathUtils.normalizeAngle(current, lonStart) - lonStart;
129 final double diffEnd = MathUtils.normalizeAngle(lonEnd, current) - current;
130
131 final double sin1 = FastMath.sin(diffStart);
132 final double sin2 = FastMath.sin(-diffEnd);
133 if (lonEnd - lonStart < FastMath.PI) {
134 ret = FastMath.min(sin1, -sin2);
135 } else {
136 ret = FastMath.max(sin1, -sin2);
137 }
138 if (Double.isNaN(ret)) {
139 if (Double.isNaN(current)) {
140 throw new RuntimeException("Detector of type " + this.getClass().getSimpleName() +
141 " returned NaN because bad orbit provided");
142 }
143 throw new RuntimeException("Detector of type " + this.getClass().getSimpleName() + " returned NaN");
144 }
145 return ret;
146 }
147
148 public double getHalfArcLength() {
149 return halfArcLength;
150 }
151
152 public PositionAngle getType() {
153 return type;
154 }
155
156
157
158
159
160
161 public void setHalfArcLength(final double halfArcLength) {
162 if (Double.isNaN(halfArcLength)) {
163 throw new RuntimeException("Trying to set an half arc with NaN value on " + getClass().getSimpleName());
164 }
165 if (halfArcLength < 0) {
166 throw new RuntimeException("Trying to set a negative value (" + FastMath.toDegrees(halfArcLength) +
167 "deg) on " + getClass().getSimpleName());
168 }
169 if (halfArcLength >= FastMath.PI) {
170 throw new RuntimeException("Trying to set an half arc higher than PI (" +
171 FastMath.toDegrees(halfArcLength) + "deg) on " + getClass().getSimpleName());
172 }
173
174 this.halfArcLength = halfArcLength;
175 }
176
177 }
178
179 public class PerigeeCenteredIntervalDetector
180 extends EquinoctialLongitudeIntervalDetector<PerigeeCenteredIntervalDetector> {
181
182 protected PerigeeCenteredIntervalDetector(final double halfArcLength, final PositionAngle type,
183 final double maxCheck, final double threshold, final int maxIter,
184 final EventHandler<? super EquinoctialLongitudeIntervalDetector<PerigeeCenteredIntervalDetector>> handler) {
185
186 super(halfArcLength, type, maxCheck, threshold, maxIter, handler);
187 }
188
189 public PerigeeCenteredIntervalDetector(final double halfArcLength, final PositionAngle type,
190 final EventHandler<? super EquinoctialLongitudeIntervalDetector<PerigeeCenteredIntervalDetector>> handler) {
191
192 super(halfArcLength, type, maxCheck, maxThreshold, DEFAULT_MAX_ITER, handler);
193 }
194
195 @Override
196 public double getReferenceEquinoctialLongitude(final SpacecraftState s) {
197
198 final KeplerianOrbit orb = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(s.getOrbit());
199
200 final double longitudeOfPerigee = orb.getRightAscensionOfAscendingNode() + orb.getPerigeeArgument();
201
202 return longitudeOfPerigee;
203 }
204
205 @Override
206 protected EquinoctialLongitudeIntervalDetector<PerigeeCenteredIntervalDetector> create(final double newMaxCheck,
207 final double newThreshold, final int newMaxIter,
208 final EventHandler<? super EquinoctialLongitudeIntervalDetector<PerigeeCenteredIntervalDetector>> newHandler) {
209 return new PerigeeCenteredIntervalDetector(getHalfArcLength(), getType(), newMaxCheck, newThreshold,
210 newMaxIter, newHandler);
211 }
212 }
213
214 public class ApogeeCenteredIntervalDetector
215 extends EquinoctialLongitudeIntervalDetector<ApogeeCenteredIntervalDetector> {
216
217 protected ApogeeCenteredIntervalDetector(final double halfArcLength, final PositionAngle type,
218 final double maxCheck, final double threshold, final int maxIter,
219 final EventHandler<? super EquinoctialLongitudeIntervalDetector<ApogeeCenteredIntervalDetector>> handler) {
220
221 super(halfArcLength, type, maxCheck, threshold, maxIter, handler);
222 }
223
224 public ApogeeCenteredIntervalDetector(final double halfArcLength, final PositionAngle type,
225 final EventHandler<? super EquinoctialLongitudeIntervalDetector<ApogeeCenteredIntervalDetector>> handler) {
226
227 super(halfArcLength, type, maxCheck, maxThreshold, DEFAULT_MAX_ITER, handler);
228 }
229
230 @Override
231 public double getReferenceEquinoctialLongitude(final SpacecraftState s) {
232
233 final KeplerianOrbit orb = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(s.getOrbit());
234
235 final double longitudeOfApogee = orb.getRightAscensionOfAscendingNode() + orb.getPerigeeArgument() +
236 FastMath.PI;
237
238 return longitudeOfApogee;
239 }
240
241 @Override
242 protected EquinoctialLongitudeIntervalDetector<ApogeeCenteredIntervalDetector> create(final double newMaxCheck,
243 final double newThreshold, final int newMaxIter,
244 final EventHandler<? super EquinoctialLongitudeIntervalDetector<ApogeeCenteredIntervalDetector>> newHandler) {
245
246 return new ApogeeCenteredIntervalDetector(getHalfArcLength(), getType(), newMaxCheck, newThreshold,
247 newMaxIter, newHandler);
248 }
249 }
250
251 public class DateIntervalDetector extends AbstractDetector<DateIntervalDetector> {
252
253
254 private final AbsoluteDate startDate;
255
256 private final AbsoluteDate endDate;
257
258 public DateIntervalDetector(final AbsoluteDate startDate, final AbsoluteDate endDate) {
259 this(startDate, endDate, 1.0e10, 1.e-9 , DEFAULT_MAX_ITER,
260 new StopOnEvent<DateIntervalDetector>());
261 }
262
263 protected DateIntervalDetector(final AbsoluteDate startDate, final AbsoluteDate endDate, final double maxCheck,
264 final double threshold, final int maxIter, final EventHandler<? super DateIntervalDetector> handler) {
265 super(maxCheck, threshold, maxIter, handler);
266 this.startDate = startDate;
267 this.endDate = endDate;
268 if (startDate.durationFrom(endDate) >= 0) {
269 throw new RuntimeException("StartDate(" + startDate + ") should be before EndDate(" + endDate + ")");
270 }
271 }
272
273 @Override
274 public double g(final SpacecraftState s) {
275 final AbsoluteDate gDate = s.getDate();
276 final double durationFromStart = gDate.durationFrom(startDate);
277 if (durationFromStart < 0) {
278 return durationFromStart;
279 }
280 final double durationBeforeEnd = endDate.durationFrom(gDate);
281 if (durationBeforeEnd < 0) {
282 return durationBeforeEnd;
283 }
284
285 final double ret = FastMath.min(durationFromStart, durationBeforeEnd);
286 if (Double.isNaN(ret)) {
287 throw new RuntimeException("Detector of type " + this.getClass().getSimpleName() + " returned NaN");
288 }
289 return ret;
290 }
291
292 @Override
293 protected DateIntervalDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter,
294 final EventHandler<? super DateIntervalDetector> newHandler) {
295 return new DateIntervalDetector(startDate, endDate, newMaxCheck, newThreshold, newMaxIter, newHandler);
296 }
297
298 }
299
300 public static NumericalPropagator buildNumericalPropagator(final Orbit initialOrbit) {
301 final OrbitType orbitType = OrbitType.EQUINOCTIAL;
302 final double minStep = 1e-6;
303 final double maxStep = 100;
304
305 final double[][] tol = NumericalPropagator.tolerances(1.0e-5, initialOrbit, orbitType);
306 final DormandPrince54Integrator integrator = new DormandPrince54Integrator(minStep, maxStep, tol[0], tol[1]);
307 final NumericalPropagator propagator = new NumericalPropagator(integrator);
308 propagator.setOrbitType(orbitType);
309 propagator.setAttitudeProvider(buildVelocityAttitudeProvider(initialOrbit.getFrame()));
310 return propagator;
311 }
312
313 private static KeplerianOrbit buildInitOrbit() {
314 return buildInitOrbitWithAnomaly(FastMath.toRadians(0));
315 }
316
317 private static KeplerianOrbit buildInitOrbitWithAnomaly(final double anomaly) {
318 final AbsoluteDate date = new AbsoluteDate(2020, 01, 01, TimeScalesFactory.getUTC());
319
320 final double sma = Constants.EGM96_EARTH_EQUATORIAL_RADIUS + 700e3;
321 final double ecc = 0.01;
322 final double inc = FastMath.toRadians(60);
323 final double pa = FastMath.toRadians(0);
324 final double raan = 0.;
325
326 final KeplerianOrbit kep = new KeplerianOrbit(sma, ecc, inc, pa, raan, anomaly, PositionAngle.MEAN,
327 FramesFactory.getCIRF(IERSConventions.IERS_2010, true), date, Constants.EGM96_EARTH_MU);
328 return kep;
329 }
330
331 private static AttitudeProvider buildVelocityAttitudeProvider(final Frame frame) {
332 return new LofOffset(frame, LOFType.TNW);
333 }
334
335 private static ThrustDirectionAndAttitudeProvider buildVelocityThrustDirectionProvider() {
336 return ThrustDirectionAndAttitudeProvider.buildFromFixedDirectionInSatelliteFrame(Vector3D.PLUS_I);
337 }
338
339 private ConfigurableLowThrustManeuver buildApogeeManeuver() {
340
341 final ApogeeCenteredIntervalDetector maneuverStartDetector = new ApogeeCenteredIntervalDetector(halfThrustArc,
342 PositionAngle.MEAN, new ContinueOnEvent<>());
343 final NegateDetector maneuverStopDetector = BooleanDetector.notCombine(maneuverStartDetector);
344
345
346 return new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(), maneuverStartDetector,
347 maneuverStopDetector, thrust, isp);
348 }
349
350 private ConfigurableLowThrustManeuver buildPerigeeManeuver() {
351
352 final PerigeeCenteredIntervalDetector maneuverStartDetector = new PerigeeCenteredIntervalDetector(halfThrustArc,
353 PositionAngle.MEAN, new ContinueOnEvent<>());
354
355 final NegateDetector maneuverStopDetector = BooleanDetector.notCombine(maneuverStartDetector);
356
357
358 return new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(), maneuverStartDetector,
359 maneuverStopDetector, thrust, isp);
360 }
361
362 private ConfigurableLowThrustManeuver buildPsoManeuver() {
363
364 final PositionAngleDetector maneuverStartDetector = new PositionAngleDetector(OrbitType.EQUINOCTIAL,
365 PositionAngle.MEAN, FastMath.toRadians(0.0));
366
367 final PositionAngleDetector maneuverStopDetector = new PositionAngleDetector(OrbitType.EQUINOCTIAL,
368 PositionAngle.MEAN, FastMath.toRadians(90.0));
369
370
371 return new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(), maneuverStartDetector,
372 maneuverStopDetector, thrust, isp);
373 }
374
375 @Test
376 public void testNominalUseCase() {
377
378 final KeplerianOrbit initOrbit = buildInitOrbit();
379 final double initMass = 20;
380 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
381 final AbsoluteDate initialDate = initOrbit.getDate();
382 final double simulationDuration = 2 * 86400;
383 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
384
385
386
387 final NumericalPropagator numericalPropagator = buildNumericalPropagator(initOrbit);
388 numericalPropagator.addForceModel(buildApogeeManeuver());
389 numericalPropagator.addForceModel(buildPerigeeManeuver());
390 numericalPropagator.setInitialState(initialState);
391 final SpacecraftState finalStateNumerical = numericalPropagator.propagate(finalDate);
392
393
394 final double expectedPropellantConsumption = -0.022;
395 final double expectedDeltaSemiMajorAxisRealized = 16397;
396 Assert.assertEquals(expectedPropellantConsumption, finalStateNumerical.getMass() - initialState.getMass(),
397 0.005);
398 Assert.assertEquals(expectedDeltaSemiMajorAxisRealized, finalStateNumerical.getA() - initialState.getA(), 100);
399 }
400
401 @Test
402 public void testBackwardPropagationDisabled() {
403
404 final KeplerianOrbit initOrbit = buildInitOrbit();
405 final double initMass = 20;
406 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
407 final AbsoluteDate initialDate = initOrbit.getDate();
408 final double simulationDuration = -2 * 86400;
409 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
410
411
412 final NumericalPropagator numericalPropagatorForward = buildNumericalPropagator(initOrbit);
413 numericalPropagatorForward.addForceModel(buildApogeeManeuver());
414 numericalPropagatorForward.setInitialState(initialState);
415 try {
416 numericalPropagatorForward.propagate(finalDate);
417 Assert.fail("an exception should have been thrown");
418 } catch (OrekitException oe) {
419 Assert.assertEquals(OrekitMessages.BACKWARD_PROPAGATION_NOT_ALLOWED, oe.getSpecifier());
420 }
421
422 }
423
424 @Test
425 public void testFielddPropagationDisabled() {
426 doTestFielddPropagationDisabled(Decimal64Field.getInstance());
427 }
428
429 private <T extends CalculusFieldElement<T>> void doTestFielddPropagationDisabled(Field<T> field) {
430
431 final KeplerianOrbit o = buildInitOrbit();
432 final FieldKeplerianOrbit<T> initOrbit = new FieldKeplerianOrbit<>(new FieldPVCoordinates<>(field, o.getPVCoordinates()),
433 o.getFrame(), new FieldAbsoluteDate<>(field, o.getDate()),
434 field.getZero().newInstance(o.getMu()));
435 final double initMass = 20;
436 final FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(initOrbit, field.getZero().newInstance(initMass));
437 final FieldAbsoluteDate<T> initialDate = initOrbit.getDate();
438 final double simulationDuration = -2 * 86400;
439 final FieldAbsoluteDate<T> finalDate = initialDate.shiftedBy(simulationDuration);
440
441
442 final OrbitType orbitType = OrbitType.EQUINOCTIAL;
443 final double minStep = 1e-6;
444 final double maxStep = 100;
445
446 final double[][] tol = FieldNumericalPropagator.tolerances(field.getZero().newInstance(1.0e-5), initOrbit, orbitType);
447 final DormandPrince54FieldIntegrator<T> integrator = new DormandPrince54FieldIntegrator<>(field, minStep, maxStep, tol[0], tol[1]);
448 final FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
449 propagator.setOrbitType(orbitType);
450 propagator.setAttitudeProvider(buildVelocityAttitudeProvider(o.getFrame()));
451 propagator.addForceModel(buildApogeeManeuver());
452 propagator.setInitialState(initialState);
453 try {
454 propagator.propagate(finalDate);
455 Assert.fail("an exception should have been thrown");
456 } catch (OrekitException oe) {
457 Assert.assertEquals(OrekitMessages.FUNCTION_NOT_IMPLEMENTED, oe.getSpecifier());
458 Assert.assertEquals("EventBasedManeuverTriggers.getFieldEventsDetectors", oe.getParts()[0]);
459 }
460
461 }
462
463 @Test
464 public void testDateBasedManeuverTriggers() {
465
466 final double f = 0.1;
467 final double isp = 2000;
468 final double duration = 3000.0;
469
470 final Orbit orbit =
471 new KeplerianOrbit(24396159, 0.72831215, FastMath.toRadians(7),
472 FastMath.toRadians(180), FastMath.toRadians(261),
473 FastMath.toRadians(0.0), PositionAngle.MEAN,
474 FramesFactory.getEME2000(),
475 new AbsoluteDate(new DateComponents(2004, 01, 01),
476 new TimeComponents(23, 30, 00.000),
477 TimeScalesFactory.getUTC()),
478 Constants.EIGEN5C_EARTH_MU);
479 final AttitudeProvider law = new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS);
480 final SpacecraftState initialState =
481 new SpacecraftState(orbit, law.getAttitude(orbit, orbit.getDate(), orbit.getFrame()), 2500.0);
482 final AbsoluteDate startDate = orbit.getDate().shiftedBy(17461.084);
483
484
485 final NumericalPropagator forwardPropagator = buildNumericalPropagator(orbit);
486 forwardPropagator.addForceModel(new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(),
487 new DateBasedManeuverTriggers(startDate, duration),
488 f, isp));
489 forwardPropagator.setInitialState(initialState);
490 final SpacecraftState finalStateNumerical = forwardPropagator.propagate(startDate.shiftedBy(duration + 900.0));
491
492
493 final NumericalPropagator backwardPropagator = buildNumericalPropagator(finalStateNumerical.getOrbit());
494 backwardPropagator.addForceModel(new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(),
495 new DateBasedManeuverTriggers(startDate, duration),
496 f, isp));
497 backwardPropagator.setInitialState(finalStateNumerical);
498 final SpacecraftState recoveredStateNumerical = backwardPropagator.propagate(orbit.getDate());
499
500
501 Assert.assertEquals(0.0,
502 Vector3D.distance(orbit.getPVCoordinates().getPosition(),
503 recoveredStateNumerical.getPVCoordinates().getPosition()),
504 0.015);
505
506 }
507
508 @Test
509 public void testBackwardPropagationEnabled() {
510
511 final double f = 0.1;
512 final double isp = 2000;
513 final double duration = 3000.0;
514
515 final Orbit orbit =
516 new KeplerianOrbit(24396159, 0.72831215, FastMath.toRadians(7),
517 FastMath.toRadians(180), FastMath.toRadians(261),
518 FastMath.toRadians(0.0), PositionAngle.MEAN,
519 FramesFactory.getEME2000(),
520 new AbsoluteDate(new DateComponents(2004, 01, 01),
521 new TimeComponents(23, 30, 00.000),
522 TimeScalesFactory.getUTC()),
523 Constants.EIGEN5C_EARTH_MU);
524 final AttitudeProvider law = new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS);
525 final SpacecraftState initialState =
526 new SpacecraftState(orbit, law.getAttitude(orbit, orbit.getDate(), orbit.getFrame()), 2500.0);
527 final AbsoluteDate startDate = orbit.getDate().shiftedBy(17461.084);
528 final DateIntervalDetector intervalDetector = new DateIntervalDetector(startDate, startDate.shiftedBy(duration));
529
530
531 final NumericalPropagator forwardPropagator = buildNumericalPropagator(orbit);
532 final EventBasedManeuverTriggers forwardDetector = new EventBasedManeuverTriggers(intervalDetector,
533 BooleanDetector.notCombine(intervalDetector),
534 true);
535 forwardPropagator.addForceModel(new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(),
536 forwardDetector, f, isp));
537 forwardPropagator.setInitialState(initialState);
538 final SpacecraftState finalStateNumerical = forwardPropagator.propagate(startDate.shiftedBy(duration + 900.0));
539 Assert.assertEquals(0.0, forwardDetector.getTriggeredStart().durationFrom(startDate), 1.0e-16);
540 Assert.assertEquals(duration, forwardDetector.getTriggeredEnd().durationFrom(startDate), 1.0e-16);
541
542
543 final NumericalPropagator backwardPropagator = buildNumericalPropagator(finalStateNumerical.getOrbit());
544 final EventBasedManeuverTriggers backwardDetector = new EventBasedManeuverTriggers(intervalDetector,
545 BooleanDetector.notCombine(intervalDetector),
546 true);
547 backwardPropagator.addForceModel(new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(),
548 backwardDetector, f, isp));
549 backwardPropagator.setInitialState(finalStateNumerical);
550 final SpacecraftState recoveredStateNumerical = backwardPropagator.propagate(orbit.getDate());
551 Assert.assertEquals(0.0, backwardDetector.getTriggeredStart().durationFrom(startDate), 1.0e-16);
552 Assert.assertEquals(duration, backwardDetector.getTriggeredEnd().durationFrom(startDate), 1.0e-16);
553
554 Assert.assertFalse(backwardDetector.isFiring(new FieldAbsoluteDate<>(Decimal64Field.getInstance(), startDate.shiftedBy(-0.001)),
555 null));
556 Assert.assertTrue(backwardDetector.isFiring(new FieldAbsoluteDate<>(Decimal64Field.getInstance(), startDate.shiftedBy(+0.001)),
557 null));
558 Assert.assertTrue(backwardDetector.isFiring(new FieldAbsoluteDate<>(Decimal64Field.getInstance(), startDate.shiftedBy(duration - 0.001)),
559 null));
560 Assert.assertFalse(backwardDetector.isFiring(new FieldAbsoluteDate<>(Decimal64Field.getInstance(), startDate.shiftedBy(duration + 0.001)),
561 null));
562
563 Assert.assertEquals(0.0,
564 Vector3D.distance(orbit.getPVCoordinates().getPosition(),
565 recoveredStateNumerical.getPVCoordinates().getPosition()),
566 0.015);
567
568 }
569
570 @Test
571 public void testInitBefore() {
572
573
574 final KeplerianOrbit initOrbit = buildInitOrbitWithAnomaly(0);
575 final double initMass = 20;
576 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
577 final AbsoluteDate initialDate = initOrbit.getDate();
578 final double simulationDuration = 10;
579 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
580
581
582 final NumericalPropagator numericalPropagatorForward = buildNumericalPropagator(initOrbit);
583 numericalPropagatorForward.addForceModel(buildApogeeManeuver());
584 numericalPropagatorForward.setInitialState(initialState);
585 final SpacecraftState finalState = numericalPropagatorForward.propagate(finalDate);
586
587 Assert.assertTrue(finalState.getMass() == initialState.getMass());
588 }
589
590 @Test
591 public void testInitNearStart() {
592
593
594
595
596
597
598 final KeplerianOrbit initOrbit = buildInitOrbitWithAnomaly(FastMath.PI - halfThrustArc);
599 final double initMass = 20;
600 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
601 final AbsoluteDate initialDate = initOrbit.getDate();
602 final double simulationDuration = 10;
603 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
604
605
606 final NumericalPropagator numericalPropagatorForward = buildNumericalPropagator(initOrbit);
607 final ConfigurableLowThrustManeuver maneuver = buildApogeeManeuver();
608 numericalPropagatorForward.addForceModel(maneuver);
609 numericalPropagatorForward.setInitialState(initialState);
610 final SpacecraftState finalState = numericalPropagatorForward.propagate(finalDate);
611
612 Assert.assertTrue(finalState.getMass() < initialState.getMass());
613
614
615 maneuver.init(initialState, finalDate);
616
617 }
618
619 @Test
620 public void testInitOnStart() {
621
622 final KeplerianOrbit initOrbit = buildInitOrbit();
623 final double initMass = 20;
624 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
625 final AbsoluteDate initialDate = initOrbit.getDate();
626 final double simulationDuration = 10;
627 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
628
629 final DateIntervalDetector maneuverStartDetector = new DateIntervalDetector(initialDate,
630 initialDate.shiftedBy(60));
631 final NegateDetector maneuverStopDetector = BooleanDetector.notCombine(maneuverStartDetector);
632
633 final ConfigurableLowThrustManeuver maneuver = new ConfigurableLowThrustManeuver(
634 buildVelocityThrustDirectionProvider(), maneuverStartDetector, maneuverStopDetector, thrust, isp);
635
636
637 final NumericalPropagator numericalPropagatorForward = buildNumericalPropagator(initOrbit);
638 numericalPropagatorForward.addForceModel(maneuver);
639 numericalPropagatorForward.setInitialState(initialState);
640 final SpacecraftState finalState = numericalPropagatorForward.propagate(finalDate);
641
642 Assert.assertTrue(finalState.getMass() < initialState.getMass());
643 }
644
645 @Test
646 public void testInitFiring() {
647
648
649 final KeplerianOrbit initOrbit = buildInitOrbitWithAnomaly(FastMath.PI);
650 final double initMass = 20;
651 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
652 final AbsoluteDate initialDate = initOrbit.getDate();
653 final double simulationDuration = 10;
654 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
655
656
657 final NumericalPropagator numericalPropagatorForward = buildNumericalPropagator(initOrbit);
658 numericalPropagatorForward.addForceModel(buildApogeeManeuver());
659 numericalPropagatorForward.setInitialState(initialState);
660 final SpacecraftState finalState = numericalPropagatorForward.propagate(finalDate);
661
662 Assert.assertTrue(finalState.getMass() < initialState.getMass());
663 }
664
665 @Test
666 public void testInitNearEndOfStart() {
667
668
669
670
671
672
673
674 final KeplerianOrbit initOrbit = buildInitOrbitWithAnomaly(FastMath.PI + halfThrustArc);
675 final double initMass = 20;
676 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
677 final AbsoluteDate initialDate = initOrbit.getDate();
678 final double simulationDuration = 10;
679 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
680
681
682 final NumericalPropagator numericalPropagatorForward = buildNumericalPropagator(initOrbit);
683 numericalPropagatorForward.addForceModel(buildApogeeManeuver());
684 numericalPropagatorForward.setInitialState(initialState);
685 final SpacecraftState finalState = numericalPropagatorForward.propagate(finalDate);
686
687 Assert.assertTrue(finalState.getMass() == initialState.getMass());
688 }
689
690 @Test
691 public void testInitOnStop() {
692
693 final KeplerianOrbit initOrbit = buildInitOrbit();
694 final double initMass = 20;
695 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
696 final AbsoluteDate initialDate = initOrbit.getDate();
697 final double simulationDuration = 10;
698 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
699
700 final DateIntervalDetector maneuverStartDetector = new DateIntervalDetector(initialDate.shiftedBy(-60),
701 initialDate);
702 final NegateDetector maneuverStopDetector = BooleanDetector.notCombine(maneuverStartDetector);
703
704 final ConfigurableLowThrustManeuver maneuver = new ConfigurableLowThrustManeuver(
705 buildVelocityThrustDirectionProvider(), maneuverStartDetector, maneuverStopDetector, thrust, isp);
706
707
708 final NumericalPropagator numericalPropagatorForward = buildNumericalPropagator(initOrbit);
709 numericalPropagatorForward.addForceModel(maneuver);
710 numericalPropagatorForward.setInitialState(initialState);
711 final SpacecraftState finalState = numericalPropagatorForward.propagate(finalDate);
712
713 Assert.assertTrue(finalState.getMass() == initialState.getMass());
714 }
715
716 @Test
717 public void testInitAfter() {
718
719
720 final KeplerianOrbit initOrbit = buildInitOrbitWithAnomaly(FastMath.PI + 2 * halfThrustArc);
721 final double initMass = 20;
722 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
723 final AbsoluteDate initialDate = initOrbit.getDate();
724 final double simulationDuration = 10;
725 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
726
727
728 final NumericalPropagator numericalPropagatorForward = buildNumericalPropagator(initOrbit);
729 numericalPropagatorForward.addForceModel(buildApogeeManeuver());
730 numericalPropagatorForward.setInitialState(initialState);
731 final SpacecraftState finalState = numericalPropagatorForward.propagate(finalDate);
732
733 Assert.assertTrue(finalState.getMass() == initialState.getMass());
734 }
735
736 @Test
737 public void testGetters() {
738 final ApogeeCenteredIntervalDetector maneuverStartDetector = new ApogeeCenteredIntervalDetector(halfThrustArc,
739 PositionAngle.MEAN, new ContinueOnEvent<>());
740 final NegateDetector maneuverStopDetector = BooleanDetector.notCombine(maneuverStartDetector);
741
742 final ThrustDirectionAndAttitudeProvider attitudeProvider = buildVelocityThrustDirectionProvider();
743 final ConfigurableLowThrustManeuver maneuver = new ConfigurableLowThrustManeuver(attitudeProvider,
744 maneuverStartDetector, maneuverStopDetector, thrust, isp);
745 Assert.assertEquals(isp, maneuver.getISP(), 1e-9);
746 Assert.assertEquals(thrust, maneuver.getThrust(), 1e-9);
747 Assert.assertEquals(attitudeProvider, maneuver.getThrustDirectionProvider());
748
749 }
750
751 @Test
752 public void testIssue874() {
753
754 final KeplerianOrbit initOrbit = buildInitOrbit();
755 final double initMass = 20;
756 final SpacecraftState initialState = new SpacecraftState(initOrbit, initMass);
757 final AbsoluteDate initialDate = initOrbit.getDate();
758 final double simulationDuration = 2 * 86400;
759 final AbsoluteDate finalDate = initialDate.shiftedBy(simulationDuration);
760
761
762
763 final NumericalPropagator numericalPropagator = buildNumericalPropagator(initOrbit);
764 numericalPropagator.addForceModel(buildPsoManeuver());
765 numericalPropagator.setInitialState(initialState);
766 final SpacecraftState finalStateNumerical = numericalPropagator.propagate(finalDate);
767
768
769 final double expectedPropellantConsumption = -0.028;
770 final double expectedDeltaSemiMajorAxisRealized = 20920;
771 Assert.assertEquals(expectedPropellantConsumption, finalStateNumerical.getMass() - initialState.getMass(),
772 0.005);
773 Assert.assertEquals(expectedDeltaSemiMajorAxisRealized, finalStateNumerical.getA() - initialState.getA(), 100);
774 }
775
776 @Test(expected = OrekitException.class)
777 public void testStartDetectorNotSet() {
778 new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(), null,
779 new ApogeeCenteredIntervalDetector(halfThrustArc, PositionAngle.MEAN, new ContinueOnEvent<>()), thrust,
780 isp);
781
782 }
783
784 @Test(expected = OrekitException.class)
785 public void testStopDetectorNotSet() {
786 new ConfigurableLowThrustManeuver(buildVelocityThrustDirectionProvider(),
787 new ApogeeCenteredIntervalDetector(halfThrustArc, PositionAngle.MEAN, new ContinueOnEvent<>()), null,
788 thrust, isp);
789
790 }
791
792 }