/*
* Licensed to the Hipparchus project under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.propagation.events;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hipparchus.util.FastMath;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.orekit.Utils;
import org.orekit.bodies.OneAxisEllipsoid;
import org.orekit.errors.OrekitException;
import org.orekit.frames.Frame;
import org.orekit.frames.FramesFactory;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.PositionAngle;
import org.orekit.propagation.Propagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.events.handlers.EventHandler.Action;
import org.orekit.propagation.events.handlers.RecordAndContinue;
import org.orekit.propagation.events.handlers.RecordAndContinue.Event;
import org.orekit.propagation.events.handlers.StopOnEvent;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.Constants;
/**
* Check events are detected correctly when the event times are close.
*
* @author Evan Ward
*/
public abstract class CloseEventsAbstractTest {
public static final AbsoluteDate epoch = AbsoluteDate.J2000_EPOCH;
public static final double mu = Constants.EIGEN5C_EARTH_MU;
public static final Frame eci = FramesFactory.getGCRF();
public static final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, eci);
public static final KeplerianOrbit initialOrbit = new KeplerianOrbit(
6378137 + 500e3, 0, 0, 0, 0, 0, PositionAngle.TRUE,
eci, epoch, mu);
@BeforeClass
public static void setUpBefore() {
Utils.setDataRoot("regular-data");
}
/**
* Create a propagator using the {@link #initialOrbit}.
*
* @param stepSize required minimum step of integrator.
* @return a usable propagator.
* @throws OrekitException
*/
public abstract Propagator getPropagator(double stepSize) throws OrekitException;
@Test
public void testCloseEventsFirstOneIsReset() throws OrekitException {
// setup
// a fairly rare state to reproduce this bug. Two dates, d1 < d2, that
// are very close. Event triggers on d1 will reset state to break out of
// event handling loop in AbstractIntegrator.acceptStep(). At this point
// detector2 has g0Positive == true but the event time is set to just
// before the event so g(t0) is negative. Now on processing the
// next step the root solver checks the sign of the start, midpoint,
// and end of the interval so we need another event less than half a max
// check interval after d2 so that the g function will be negative at
// all three times. Then we get a non bracketing exception.
Propagator propagator = getPropagator(10.0);
double t1 = 49, t2 = t1 + 1e-15, t3 = t1 + 4.9;
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detector1 = new TimeDetector(t1)
.withHandler(new Handler<>(events, Action.RESET_DERIVATIVES))
.withMaxCheck(10)
.withThreshold(1e-9);
propagator.addEventDetector(detector1);
TimeDetector detector2 = new TimeDetector(t2, t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(11)
.withThreshold(1e-9);
propagator.addEventDetector(detector2);
// action
propagator.propagate(epoch.shiftedBy(60));
// verify
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(detector1, events.get(0).getDetector());
}
@Test
public void testCloseEvents() throws OrekitException {
// setup
double tolerance = 1;
Propagator propagator = getPropagator(10);
RecordAndContinue<EventDetector> handler = new RecordAndContinue<>();
TimeDetector detector1 = new TimeDetector(5)
.withHandler(handler)
.withMaxCheck(10)
.withThreshold(tolerance);
propagator.addEventDetector(detector1);
TimeDetector detector2 = new TimeDetector(5.5)
.withHandler(handler)
.withMaxCheck(10)
.withThreshold(tolerance);
propagator.addEventDetector(detector2);
// action
propagator.propagate(epoch.shiftedBy(20));
// verify
List<Event<EventDetector>> events = handler.getEvents();
Assert.assertEquals(2, events.size());
Assert.assertEquals(5, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(detector1, events.get(0).getDetector());
Assert.assertEquals(5.5, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(detector2, events.get(1).getDetector());
}
@Test
public void testSimultaneousEvents() throws OrekitException {
// setup
Propagator propagator = getPropagator(10);
RecordAndContinue<EventDetector> handler1 = new RecordAndContinue<>();
TimeDetector detector1 = new TimeDetector(5)
.withHandler(handler1)
.withMaxCheck(10)
.withThreshold(1);
propagator.addEventDetector(detector1);
RecordAndContinue<EventDetector> handler2 = new RecordAndContinue<>();
TimeDetector detector2 = new TimeDetector(5)
.withHandler(handler2)
.withMaxCheck(10)
.withThreshold(1);
propagator.addEventDetector(detector2);
// action
propagator.propagate(epoch.shiftedBy(20));
// verify
List<Event<EventDetector>> events1 = handler1.getEvents();
Assert.assertEquals(1, events1.size());
Assert.assertEquals(5, events1.get(0).getState().getDate().durationFrom(epoch), 0.0);
List<Event<EventDetector>> events2 = handler2.getEvents();
Assert.assertEquals(1, events2.size());
Assert.assertEquals(5, events2.get(0).getState().getDate().durationFrom(epoch), 0.0);
}
/**
* test the g function switching with a period shorter than the tolerance. We don't
* need to find any of the events, but we do need to not crash. And we need to
* preserve the alternating increasing / decreasing sequence.
*/
@Test
public void testFastSwitching() throws OrekitException {
// setup
// step size of 10 to land in between two events we would otherwise miss
Propagator propagator = getPropagator(10);
RecordAndContinue<EventDetector> handler = new RecordAndContinue<>();
TimeDetector detector1 = new TimeDetector(9.9, 10.1, 12)
.withHandler(handler)
.withMaxCheck(10)
.withThreshold(0.2);
propagator.addEventDetector(detector1);
// action
propagator.propagate(epoch.shiftedBy(20));
//verify
// finds one or three events. Not 2.
List<Event<EventDetector>> events1 = handler.getEvents();
Assert.assertEquals(1, events1.size());
Assert.assertEquals(9.9, events1.get(0).getState().getDate().durationFrom(epoch), 0.1);
Assert.assertEquals(true, events1.get(0).isIncreasing());
}
/** "A Tricky Problem" from bug #239. */
@Test
public void testTrickyCaseLower() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = 1.0, t2 = 15, t3 = 16, t4 = 17, t5 = 18;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
TimeDetector detectorB = new TimeDetector(-10, t1, t2, t5)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
TimeDetector detectorC = new TimeDetector(t4)
.withHandler(new Handler<>(events, Action.RESET_DERIVATIVES))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
propagator.addEventDetector(detectorC);
// action
propagator.propagate(epoch.shiftedBy(30));
//verify
// really we only care that the Rules of Event Handling are not violated,
// but I only know one way to do that in this case.
Assert.assertEquals(5, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(0).isIncreasing());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertEquals(t3, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(2).isIncreasing());
Assert.assertEquals(t4, events.get(3).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(3).isIncreasing());
Assert.assertEquals(t5, events.get(4).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(4).isIncreasing());
}
/**
* Test case for two event detectors. DetectorA has event at t2, DetectorB at t3, but
* due to the root finding tolerance DetectorB's event occurs at t1. With t1 < t2 <
* t3.
*/
@Test
public void testRootFindingTolerance() throws OrekitException {
//setup
double maxCheck = 10;
double t2 = 11, t3 = t2 + 1e-5;
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t2)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(1e-6);
FlatDetector detectorB = new FlatDetector(t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(0.5);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
// if these fail the event finding did its job,
// but this test isn't testing what it is supposed to be
Assert.assertSame(detectorB, events.get(0).getDetector());
Assert.assertSame(detectorA, events.get(1).getDetector());
Assert.assertTrue(events.get(0).getState().getDate().compareTo(
events.get(1).getState().getDate()) < 0);
// check event detection worked
Assert.assertEquals(2, events.size());
Assert.assertEquals(t3, events.get(0).getState().getDate().durationFrom(epoch), 0.5);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), 1e-6);
Assert.assertEquals(true, events.get(1).isIncreasing());
}
/** check when g(t < root) < 0, g(root + convergence) < 0. */
@Test
public void testRootPlusToleranceHasWrongSign() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
final double toleranceB = 0.3;
double t1 = 11, t2 = 11.1, t3 = 11.2;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t2)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(1e-6);
TimeDetector detectorB = new TimeDetector(t1, t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(toleranceB);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
// we only care that the rules are satisfied, there are other solutions
Assert.assertEquals(3, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), toleranceB);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorB, events.get(0).getDetector());
Assert.assertEquals(t3, events.get(1).getState().getDate().durationFrom(epoch), toleranceB);
Assert.assertEquals(false, events.get(1).isIncreasing());
Assert.assertSame(detectorB, events.get(1).getDetector());
Assert.assertEquals(t2, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(2).isIncreasing());
Assert.assertSame(detectorA, events.get(2).getDetector());
// chronological
for (int i = 1; i < events.size(); i++) {
Assert.assertTrue(events.get(i).getState().getDate().compareTo(
events.get(i - 1).getState().getDate()) >= 0);
}
}
/** check when g(t < root) < 0, g(root + convergence) < 0. */
@Test
public void testRootPlusToleranceHasWrongSignAndLessThanTb() throws OrekitException {
// setup
// test is fragile w.r.t. implementation and these parameters
double maxCheck = 10;
double tolerance = 0.5;
double t1 = 11, t2 = 11.4, t3 = 12.0;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
FlatDetector detectorB = new FlatDetector(t1, t2, t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
// allowed to find t1 or t3.
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorB, events.get(0).getDetector());
}
/**
* Check when g(t) has a multiple root. e.g. g(t < root) < 0, g(root) = 0, g(t > root)
* < 0.
*/
@Test
public void testDoubleRoot() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = 11;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
TimeDetector detectorB = new TimeDetector(t1, t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
// detector worked correctly
Assert.assertTrue(detectorB.g(state(t1)) == 0.0);
Assert.assertTrue(detectorB.g(state(t1 - 1e-6)) < 0);
Assert.assertTrue(detectorB.g(state(t1 + 1e-6)) < 0);
}
/**
* Check when g(t) has a multiple root. e.g. g(t < root) > 0, g(root) = 0, g(t > root)
* > 0.
*/
@Test
public void testDoubleRootOppositeSign() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = 11;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
ContinuousDetector detectorB = new ContinuousDetector(-20, t1, t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
// detector worked correctly
Assert.assertEquals(0.0, detectorB.g(state(t1)), 0.0);
Assert.assertTrue(detectorB.g(state(t1 - 1e-6)) > 0);
Assert.assertTrue(detectorB.g(state(t1 + 1e-6)) > 0);
}
/** check root finding when zero at both ends. */
@Test
public void testZeroAtBeginningAndEndOfInterval() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = 10, t2 = 20;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
ContinuousDetector detectorA = new ContinuousDetector(t1, t2)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
Assert.assertEquals(2, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(1).isIncreasing());
Assert.assertSame(detectorA, events.get(1).getDetector());
}
/** check root finding when zero at both ends. */
@Test
public void testZeroAtBeginningAndEndOfIntervalOppositeSign() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = 10, t2 = 20;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
ContinuousDetector detectorA = new ContinuousDetector(-10, t1, t2)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
Assert.assertEquals(2, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertSame(detectorA, events.get(1).getDetector());
}
/** Test where an event detector has to back up multiple times. */
@Test
public void testMultipleBackups() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = 1.0, t2 = 2, t3 = 3, t4 = 4, t5 = 5, t6 = 6.5, t7 = 7;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
ContinuousDetector detectorA = new ContinuousDetector(t6)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
FlatDetector detectorB = new FlatDetector(t1, t3, t4, t7)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
ContinuousDetector detectorC = new ContinuousDetector(t2, t5)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
propagator.addEventDetector(detectorC);
// action
propagator.propagate(epoch.shiftedBy(30));
//verify
// need at least 5 events to check that multiple backups occurred
Assert.assertEquals(5, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertEquals(detectorB, events.get(0).getDetector());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertEquals(detectorC, events.get(1).getDetector());
// reporting t3 and t4 is optional, seeing them is not.
// we know a root was found at t3 because events are reported at t2 and t5.
/*
Assert.assertEquals(t3, events.get(2).getT(), tolerance);
Assert.assertEquals(false, events.get(2).isIncreasing());
Assert.assertEquals(detectorB, events.get(2).getHandler());
Assert.assertEquals(t4, events.get(3).getT(), tolerance);
Assert.assertEquals(true, events.get(3).isIncreasing());
Assert.assertEquals(detectorB, events.get(3).getHandler());
*/
Assert.assertEquals(t5, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(2).isIncreasing());
Assert.assertEquals(detectorC, events.get(2).getDetector());
Assert.assertEquals(t6, events.get(3).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(3).isIncreasing());
Assert.assertEquals(detectorA, events.get(3).getDetector());
Assert.assertEquals(t7, events.get(4).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(4).isIncreasing());
Assert.assertEquals(detectorB, events.get(4).getDetector());
}
/** Test a reset event triggering another event at the same time. */
@Test
public void testEventCausedByStateReset() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = 15.0;
SpacecraftState newState = new SpacecraftState(new KeplerianOrbit(
6378137 + 500e3, 0, FastMath.PI / 2, 0, 0,
FastMath.PI / 2, PositionAngle.TRUE, eci, epoch.shiftedBy(t1), mu));
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t1)
.withHandler(new Handler<EventDetector>(events, Action.RESET_STATE) {
@Override
public SpacecraftState resetState(EventDetector detector,
SpacecraftState oldState) {
return newState;
}
})
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
LatitudeCrossingDetector detectorB =
new LatitudeCrossingDetector(earth, FastMath.toRadians(80))
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(40.0));
//verify
// really we only care that the Rules of Event Handling are not violated,
Assert.assertEquals(2, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertEquals(detectorA, events.get(0).getDetector());
Assert.assertEquals(t1, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertEquals(detectorB, events.get(1).getDetector());
}
/** check when t + tolerance == t. */
@Test
public void testConvergenceTooTight() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-18;
double t1 = 15;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
ContinuousDetector detectorA = new ContinuousDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
}
/**
* test when one event detector changes the definition of another's g function before
* the end of the step as a result of a continue action. Not sure if this should be
* officially supported, but it is used in Orekit's DateDetector, it's useful, and not
* too hard to implement.
*/
@Test
public void testEventChangesGFunctionDefinition() throws OrekitException {
// setup
double maxCheck = 5;
double tolerance = 1e-6;
double t1 = 11, t2 = 19;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
// mutable boolean
boolean[] swap = new boolean[1];
final ContinuousDetector detectorA = new ContinuousDetector(t1)
.withThreshold(maxCheck)
.withThreshold(tolerance)
.withHandler(new RecordAndContinue<EventDetector>(events) {
@Override
public Action eventOccurred(SpacecraftState s,
EventDetector detector,
boolean increasing) {
swap[0] = true;
return super.eventOccurred(s, detector, increasing);
}
});
ContinuousDetector detectorB = new ContinuousDetector(t2);
EventDetector detectorC = new AbstractDetector<EventDetector>
(maxCheck, tolerance, 100, new RecordAndContinue<>(events)) {
private static final long serialVersionUID = 1L;
@Override
public double g(SpacecraftState s) throws OrekitException {
if (swap[0]) {
return detectorB.g(s);
} else {
return -1;
}
}
@Override
protected EventDetector create(
double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super EventDetector> newHandler) {
return null;
}
};
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorC);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
Assert.assertEquals(2, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertSame(detectorC, events.get(1).getDetector());
}
/** check when root finding tolerance > event finding tolerance. */
@Test
public void testToleranceStop() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-18; // less than 1 ulp
double t1 = 15.1;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
FlatDetector detectorA = new FlatDetector(t1)
.withHandler(new Handler<>(events, Action.STOP))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
SpacecraftState finalState = propagator.propagate(epoch.shiftedBy(30));
// verify
Assert.assertEquals(1, events.size());
// use root finder tolerance instead of event finder tolerance.
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t1, finalState.getDate().durationFrom(epoch), tolerance);
// try to resume propagation
finalState = propagator.propagate(epoch.shiftedBy(30));
// verify it got to the end
Assert.assertEquals(30.0, finalState.getDate().durationFrom(epoch), 0.0);
}
/**
* The root finder requires the start point to be in the interval (a, b) which is hard
* when there aren't many numbers between a and b. This test uses a second event
* detector to force a very small window for the first event detector.
*/
@Test
public void testShortBracketingInterval() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
final double t1 = FastMath.nextUp(10.0), t2 = 10.5;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
// never zero so there is no easy way out
EventDetector detectorA = new AbstractDetector<EventDetector>
(maxCheck, tolerance, 100, new RecordAndContinue<>(events)) {
private static final long serialVersionUID = 1L;
@Override
public double g(SpacecraftState state) {
final AbsoluteDate t = state.getDate();
if (t.compareTo(epoch.shiftedBy(t1)) < 0) {
return -1;
} else if (t.compareTo(epoch.shiftedBy(t2)) < 0) {
return 1;
} else {
return -1;
}
}
@Override
protected EventDetector create(
double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super EventDetector> newHandler) {
return null;
}
};
TimeDetector detectorB = new TimeDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(30.0));
// verify
Assert.assertEquals(3, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t1, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertSame(detectorB, events.get(1).getDetector());
Assert.assertEquals(t2, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(2).isIncreasing());
Assert.assertSame(detectorA, events.get(2).getDetector());
}
/** check when root finding tolerance > event finding tolerance. */
@Test
public void testToleranceMaxIterations() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-18; // less than 1 ulp
AbsoluteDate t1 = epoch.shiftedBy(15).shiftedBy(FastMath.ulp(15.0) / 8);
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
FlatDetector detectorA = new FlatDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
propagator.propagate(epoch.shiftedBy(30));
// verify
Assert.assertEquals(1, events.size());
// use root finder tolerance instead of event finder tolerance.
Assert.assertEquals(t1.durationFrom(epoch),
events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
}
/* The following tests are copies of the above tests, except that they propagate in
* the reverse direction and all the signs on the time values are negated.
*/
@Test
public void testCloseEventsFirstOneIsResetReverse() throws OrekitException {
// setup
// a fairly rare state to reproduce this bug. Two dates, d1 < d2, that
// are very close. Event triggers on d1 will reset state to break out of
// event handling loop in AbstractIntegrator.acceptStep(). At this point
// detector2 has g0Positive == true but the event time is set to just
// before the event so g(t0) is negative. Now on processing the
// next step the root solver checks the sign of the start, midpoint,
// and end of the interval so we need another event less than half a max
// check interval after d2 so that the g function will be negative at
// all three times. Then we get a non bracketing exception.
Propagator propagator = getPropagator(10.0);
// switched for 9 to 1 to be close to the start of the step
double t1 = -1;
Handler<EventDetector> handler1 = new Handler<>(Action.RESET_DERIVATIVES);
TimeDetector detector1 = new TimeDetector(t1)
.withHandler(handler1)
.withMaxCheck(10)
.withThreshold(1e-9);
propagator.addEventDetector(detector1);
RecordAndContinue<EventDetector> handler2 = new RecordAndContinue<>();
TimeDetector detector2 = new TimeDetector(t1 - 1e-15, t1 - 4.9)
.withHandler(handler2)
.withMaxCheck(11)
.withThreshold(1e-9);
propagator.addEventDetector(detector2);
// action
propagator.propagate(epoch.shiftedBy(-20));
// verify
List<Event<EventDetector>> events1 = handler1.getEvents();
Assert.assertEquals(1, events1.size());
Assert.assertEquals(t1, events1.get(0).getState().getDate().durationFrom(epoch), 0.0);
List<Event<EventDetector>> events2 = handler2.getEvents();
Assert.assertEquals(0, events2.size());
}
@Test
public void testCloseEventsReverse() throws OrekitException {
// setup
double tolerance = 1;
Propagator propagator = getPropagator(10);
RecordAndContinue<EventDetector> handler1 = new RecordAndContinue<>();
TimeDetector detector1 = new TimeDetector(-5)
.withHandler(handler1)
.withMaxCheck(10)
.withThreshold(tolerance);
propagator.addEventDetector(detector1);
RecordAndContinue<EventDetector> handler2 = new RecordAndContinue<>();
TimeDetector detector2 = new TimeDetector(-5.5)
.withHandler(handler2)
.withMaxCheck(10)
.withThreshold(tolerance);
propagator.addEventDetector(detector2);
// action
propagator.propagate(epoch.shiftedBy(-20));
// verify
List<Event<EventDetector>> events1 = handler1.getEvents();
Assert.assertEquals(1, events1.size());
Assert.assertEquals(-5, events1.get(0).getState().getDate().durationFrom(epoch), tolerance);
List<Event<EventDetector>> events2 = handler2.getEvents();
Assert.assertEquals(1, events2.size());
Assert.assertEquals(-5.5, events2.get(0).getState().getDate().durationFrom(epoch), tolerance);
}
@Test
public void testSimultaneousEventsReverse() throws OrekitException {
// setup
Propagator propagator = getPropagator(10);
RecordAndContinue<EventDetector> handler1 = new RecordAndContinue<>();
TimeDetector detector1 = new TimeDetector(-5)
.withHandler(handler1)
.withMaxCheck(10)
.withThreshold(1);
propagator.addEventDetector(detector1);
RecordAndContinue<EventDetector> handler2 = new RecordAndContinue<>();
TimeDetector detector2 = new TimeDetector(-5)
.withHandler(handler2)
.withMaxCheck(10)
.withThreshold(1);
propagator.addEventDetector(detector2);
// action
propagator.propagate(epoch.shiftedBy(-20));
// verify
List<Event<EventDetector>> events1 = handler1.getEvents();
Assert.assertEquals(1, events1.size());
Assert.assertEquals(-5, events1.get(0).getState().getDate().durationFrom(epoch), 0.0);
List<Event<EventDetector>> events2 = handler2.getEvents();
Assert.assertEquals(1, events2.size());
Assert.assertEquals(-5, events2.get(0).getState().getDate().durationFrom(epoch), 0.0);
}
/**
* test the g function switching with a period shorter than the tolerance. We don't
* need to find any of the events, but we do need to not crash. And we need to
* preserve the alternating increasing / decreasing sequence.
*/
@Test
public void testFastSwitchingReverse() throws OrekitException {
// setup
// step size of 10 to land in between two events we would otherwise miss
Propagator propagator = getPropagator(10);
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detector1 = new TimeDetector(-9.9, -10.1, -12)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(10)
.withThreshold(0.2);
propagator.addEventDetector(detector1);
// action
propagator.propagate(epoch.shiftedBy(-20));
//verify
// finds one or three events. Not 2.
Assert.assertEquals(1, events.size());
Assert.assertEquals(-9.9, events.get(0).getState().getDate().durationFrom(epoch), 0.2);
Assert.assertEquals(true, events.get(0).isIncreasing());
}
/** "A Tricky Problem" from bug #239. */
@Test
public void testTrickyCaseLowerReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = -1.0, t2 = -15, t3 = -16, t4 = -17, t5 = -18;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
TimeDetector detectorB = new TimeDetector(-50, t1, t2, t5)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
TimeDetector detectorC = new TimeDetector(t4)
.withHandler(new Handler<>(events, Action.RESET_DERIVATIVES))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
propagator.addEventDetector(detectorC);
// action
propagator.propagate(epoch.shiftedBy(-30));
//verify
// really we only care that the Rules of Event Handling are not violated,
// but I only know one way to do that in this case.
Assert.assertEquals(5, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(0).isIncreasing());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertEquals(t3, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(2).isIncreasing());
Assert.assertEquals(t4, events.get(3).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(3).isIncreasing());
Assert.assertEquals(t5, events.get(4).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(4).isIncreasing());
}
/**
* Test case for two event detectors. DetectorA has event at t2, DetectorB at t3, but
* due to the root finding tolerance DetectorB's event occurs at t1. With t1 < t2 <
* t3.
*/
@Test
public void testRootFindingToleranceReverse() throws OrekitException {
//setup
double maxCheck = 10;
double t2 = -11, t3 = t2 - 1e-5;
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t2)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(1e-6);
FlatDetector detectorB = new FlatDetector(t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(0.5);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
// if these fail the event finding did its job,
// but this test isn't testing what it is supposed to be
Assert.assertSame(detectorB, events.get(0).getDetector());
Assert.assertSame(detectorA, events.get(1).getDetector());
Assert.assertTrue(events.get(0).getState().getDate().compareTo(
events.get(1).getState().getDate()) > 0);
// check event detection worked
Assert.assertEquals(2, events.size());
Assert.assertEquals(t3, events.get(0).getState().getDate().durationFrom(epoch), 0.5);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), 1e-6);
Assert.assertEquals(true, events.get(1).isIncreasing());
}
/** check when g(t < root) < 0, g(root + convergence) < 0. */
@Test
public void testRootPlusToleranceHasWrongSignReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
final double toleranceB = 0.3;
double t1 = -11, t2 = -11.1, t3 = -11.2;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t2)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
TimeDetector detectorB = new TimeDetector(-50, t1, t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(toleranceB);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
// we only care that the rules are satisfied. There are multiple solutions.
Assert.assertEquals(3, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), toleranceB);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorB, events.get(0).getDetector());
Assert.assertEquals(t3, events.get(1).getState().getDate().durationFrom(epoch), toleranceB);
Assert.assertEquals(false, events.get(1).isIncreasing());
Assert.assertSame(detectorB, events.get(1).getDetector());
Assert.assertEquals(t2, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(2).isIncreasing());
Assert.assertSame(detectorA, events.get(2).getDetector());
// ascending order
Assert.assertTrue(events.get(0).getState().getDate().compareTo(
events.get(1).getState().getDate()) >= 0);
Assert.assertTrue(events.get(1).getState().getDate().compareTo(
events.get(2).getState().getDate()) >= 0);
}
/** check when g(t < root) < 0, g(root + convergence) < 0. */
@Test
public void testRootPlusToleranceHasWrongSignAndLessThanTbReverse() throws OrekitException {
// setup
// test is fragile w.r.t. implementation and these parameters
double maxCheck = 10;
double tolerance = 0.5;
double t1 = -11, t2 = -11.4, t3 = -12.0;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
FlatDetector detectorB = new FlatDetector(t1, t2, t3)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
// allowed to report t1 or t3.
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorB, events.get(0).getDetector());
}
/**
* Check when g(t) has a multiple root. e.g. g(t < root) < 0, g(root) = 0, g(t > root)
* < 0.
*/
@Test
public void testDoubleRootReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = -11;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
TimeDetector detectorB = new TimeDetector(t1, t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
// detector worked correctly
Assert.assertTrue(detectorB.g(state(t1)) == 0.0);
Assert.assertTrue(detectorB.g(state(t1 + 1e-6)) < 0);
Assert.assertTrue(detectorB.g(state(t1 - 1e-6)) < 0);
}
/**
* Check when g(t) has a multiple root. e.g. g(t < root) > 0, g(root) = 0, g(t > root)
* > 0.
*/
@Test
public void testDoubleRootOppositeSignReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = -11;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
ContinuousDetector detectorB = new ContinuousDetector(-50, t1, t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
detectorB.g(state(t1));
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
// detector worked correctly
Assert.assertEquals(0.0, detectorB.g(state(t1)), 0.0);
Assert.assertTrue(detectorB.g(state(t1 + 1e-6)) > 0);
Assert.assertTrue(detectorB.g(state(t1 - 1e-6)) > 0);
}
/** check root finding when zero at both ends. */
@Test
public void testZeroAtBeginningAndEndOfIntervalReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = -10, t2 = -20;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
ContinuousDetector detectorA = new ContinuousDetector(-50, t1, t2)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
Assert.assertEquals(2, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(1).isIncreasing());
Assert.assertSame(detectorA, events.get(1).getDetector());
}
/** check root finding when zero at both ends. */
@Test
public void testZeroAtBeginningAndEndOfIntervalOppositeSignReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = -10, t2 = -20;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
ContinuousDetector detectorA = new ContinuousDetector(t1, t2)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
propagator.propagate(epoch.shiftedBy(-30));
// verify
Assert.assertEquals(2, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(false, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertSame(detectorA, events.get(1).getDetector());
}
/** Test where an event detector has to back up multiple times. */
@Test
public void testMultipleBackupsReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = -1.0, t2 = -2, t3 = -3, t4 = -4, t5 = -5, t6 = -6.5, t7 = -7;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
ContinuousDetector detectorA = new ContinuousDetector(t6)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
ContinuousDetector detectorB = new ContinuousDetector(-50, t1, t3, t4, t7)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
ContinuousDetector detectorC = new ContinuousDetector(-50, t2, t5)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
propagator.addEventDetector(detectorC);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
//verify
// really we only care that the Rules of Event Handling are not violated,
Assert.assertEquals(5, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertEquals(detectorB, events.get(0).getDetector());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertEquals(detectorC, events.get(1).getDetector());
// reporting t3 and t4 is optional, seeing them is not.
// we know a root was found at t3 because events are reported at t2 and t5.
/*
Assert.assertEquals(t3, events.get(2).getT(), tolerance);
Assert.assertEquals(false, events.get(2).isIncreasing());
Assert.assertEquals(detectorB, events.get(2).getHandler());
Assert.assertEquals(t4, events.get(3).getT(), tolerance);
Assert.assertEquals(true, events.get(3).isIncreasing());
Assert.assertEquals(detectorB, events.get(3).getHandler());
*/
Assert.assertEquals(t5, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(2).isIncreasing());
Assert.assertEquals(detectorC, events.get(2).getDetector());
Assert.assertEquals(t6, events.get(3).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(3).isIncreasing());
Assert.assertEquals(detectorA, events.get(3).getDetector());
Assert.assertEquals(t7, events.get(4).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(4).isIncreasing());
Assert.assertEquals(detectorB, events.get(4).getDetector());
}
/** Test a reset event triggering another event at the same time. */
@Test
public void testEventCausedByStateResetReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
double t1 = -15.0;
SpacecraftState newState = new SpacecraftState(new KeplerianOrbit(
6378137 + 500e3, 0, FastMath.PI / 2, 0, 0,
FastMath.PI / 2, PositionAngle.TRUE, eci, epoch.shiftedBy(t1), mu));
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
TimeDetector detectorA = new TimeDetector(t1)
.withHandler(new Handler<EventDetector>(events, Action.RESET_STATE) {
@Override
public SpacecraftState resetState(EventDetector detector,
SpacecraftState oldState) {
return newState;
}
})
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
LatitudeCrossingDetector detectorB =
new LatitudeCrossingDetector(earth, FastMath.toRadians(80))
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(-40.0));
//verify
// really we only care that the Rules of Event Handling are not violated,
Assert.assertEquals(2, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertEquals(detectorA, events.get(0).getDetector());
Assert.assertEquals(t1, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(1).isIncreasing());
Assert.assertEquals(detectorB, events.get(1).getDetector());
}
/** check when t + tolerance == t. */
@Test
public void testConvergenceTooTightReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-18;
double t1 = -15;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
ContinuousDetector detectorA = new ContinuousDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
Assert.assertEquals(1, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
}
/**
* test when one event detector changes the definition of another's g function before
* the end of the step as a result of a continue action. Not sure if this should be
* officially supported, but it is used in Orekit's DateDetector, it's useful, and not
* too hard to implement.
*/
@Test
public void testEventChangesGFunctionDefinitionReverse() throws OrekitException {
// setup
double maxCheck = 5;
double tolerance = 1e-6;
double t1 = -11, t2 = -19;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
// mutable boolean
boolean[] swap = new boolean[1];
ContinuousDetector detectorA = new ContinuousDetector(t1)
.withMaxCheck(maxCheck)
.withThreshold(tolerance)
.withHandler(new RecordAndContinue<EventDetector>(events) {
@Override
public Action eventOccurred(SpacecraftState s,
EventDetector detector,
boolean increasing) {
swap[0] = true;
return super.eventOccurred(s, detector, increasing);
}
});
ContinuousDetector detectorB = new ContinuousDetector(t2);
EventDetector detectorC = new AbstractDetector<EventDetector>
(maxCheck, tolerance, 100, new RecordAndContinue<>(events)) {
private static final long serialVersionUID = 1L;
@Override
public double g(SpacecraftState state) throws OrekitException {
if (swap[0]) {
return detectorB.g(state);
} else {
return 1;
}
}
@Override
protected EventDetector create(double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super EventDetector> newHandler) {
return null;
}
};
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorC);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
Assert.assertEquals(2, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertSame(detectorC, events.get(1).getDetector());
}
/** check when root finding tolerance > event finding tolerance. */
@Test
public void testToleranceStopReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-18; // less than 1 ulp
double t1 = -15.1;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
FlatDetector detectorA = new FlatDetector(t1)
.withHandler(new Handler<>(events, Action.STOP))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
SpacecraftState finalState = propagator.propagate(epoch.shiftedBy(-30.0));
// verify
Assert.assertEquals(1, events.size());
// use root finder tolerance instead of event finder tolerance.
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t1, finalState.getDate().durationFrom(epoch), tolerance);
// try to resume propagation
finalState = propagator.propagate(epoch.shiftedBy(-30.0));
// verify it got to the end
Assert.assertEquals(-30.0, finalState.getDate().durationFrom(epoch), 0.0);
}
/**
* The root finder requires the start point to be in the interval (a, b) which is hard
* when there aren't many numbers between a and b. This test uses a second event
* detector to force a very small window for the first event detector.
*/
@Test
public void testShortBracketingIntervalReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-6;
final double t1 = FastMath.nextDown(-10.0), t2 = -10.5;
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
// never zero so there is no easy way out
EventDetector detectorA = new AbstractDetector<EventDetector>
(maxCheck, tolerance, 100, new RecordAndContinue<>(events)) {
private static final long serialVersionUID = 1L;
@Override
public double g(SpacecraftState state) {
final AbsoluteDate t = state.getDate();
if (t.compareTo(epoch.shiftedBy(t1)) > 0) {
return -1;
} else if (t.compareTo(epoch.shiftedBy(t2)) > 0) {
return 1;
} else {
return -1;
}
}
@Override
protected EventDetector create(
double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super EventDetector> newHandler) {
return null;
}
};
TimeDetector detectorB = new TimeDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
propagator.addEventDetector(detectorB);
// action
propagator.propagate(epoch.shiftedBy(-30.0));
// verify
Assert.assertEquals(3, events.size());
Assert.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(false, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
Assert.assertEquals(t1, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(1).isIncreasing());
Assert.assertSame(detectorB, events.get(1).getDetector());
Assert.assertEquals(t2, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
Assert.assertEquals(true, events.get(2).isIncreasing());
Assert.assertSame(detectorA, events.get(2).getDetector());
}
/** check when root finding tolerance > event finding tolerance. */
@Test
public void testToleranceMaxIterationsReverse() throws OrekitException {
// setup
double maxCheck = 10;
double tolerance = 1e-18; // less than 1 ulp
AbsoluteDate t1 = epoch.shiftedBy(-15).shiftedBy(FastMath.ulp(-15.0) / 8);
// shared event list so we know the order in which they occurred
List<Event<EventDetector>> events = new ArrayList<>();
FlatDetector detectorA = new FlatDetector(t1)
.withHandler(new RecordAndContinue<>(events))
.withMaxCheck(maxCheck)
.withThreshold(tolerance);
Propagator propagator = getPropagator(10);
propagator.addEventDetector(detectorA);
// action
propagator.propagate(epoch.shiftedBy(-30));
// verify
Assert.assertEquals(1, events.size());
// use root finder tolerance instead of event finder tolerance.
Assert.assertEquals(t1.durationFrom(epoch),
events.get(0).getState().getDate().durationFrom(epoch),
FastMath.ulp(-15.0));
Assert.assertEquals(true, events.get(0).isIncreasing());
Assert.assertSame(detectorA, events.get(0).getDetector());
}
/* utility classes and methods */
/**
* Create a state at a time.
*
* @param t time of state.
* @return new state.
*/
private SpacecraftState state(double t) throws OrekitException {
return new SpacecraftState(
new KeplerianOrbit(6378137 + 500e3, 0, 0, 0, 0, 0,
PositionAngle.TRUE, eci, epoch.shiftedBy(t),
mu));
}
private static List<AbsoluteDate> toDates(double[] eventTs) {
Arrays.sort(eventTs);
final List<AbsoluteDate> ret = new ArrayList<>();
for (double eventT : eventTs) {
ret.add(epoch.shiftedBy(eventT));
}
return ret;
}
/** Trigger an event at a particular time. */
private static class TimeDetector extends AbstractDetector<TimeDetector> {
private static final long serialVersionUID = 1L;
/** time of the event to trigger. */
private final List<AbsoluteDate> eventTs;
/**
* Create a detector that finds events at specific times.
*
* @param eventTs event times past epoch.
*/
public TimeDetector(double... eventTs) {
this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER,
new StopOnEvent<>(), toDates(eventTs));
}
/**
* Create a detector that finds events at specific times.
*
* @param eventTs event times past epoch.
*/
public TimeDetector(AbsoluteDate... eventTs) {
this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER,
new StopOnEvent<>(), Arrays.asList(eventTs));
}
private TimeDetector(double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super TimeDetector> newHandler,
List<AbsoluteDate> dates) {
super(newMaxCheck, newThreshold, newMaxIter, newHandler);
this.eventTs = dates;
}
@Override
public double g(SpacecraftState s) throws OrekitException {
final AbsoluteDate t = s.getDate();
int i = 0;
while (i < eventTs.size() && t.compareTo(eventTs.get(i)) > 0) {
i++;
}
i--;
if (i < 0) {
return t.durationFrom(eventTs.get(0));
} else {
int sign = (i % 2) * 2 - 1;
return -sign * (t.durationFrom(eventTs.get(i)));
}
}
@Override
protected TimeDetector create(double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super TimeDetector> newHandler) {
return new TimeDetector(
newMaxCheck, newThreshold, newMaxIter, newHandler, eventTs);
}
}
/**
* Same as {@link TimeDetector} except that it has a very flat g function which makes
* root finding hard.
*/
private static class FlatDetector extends AbstractDetector<FlatDetector> {
private static final long serialVersionUID = 1L;
private final EventDetector g;
public FlatDetector(double... eventTs) {
this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER,
new StopOnEvent<>(), new TimeDetector(eventTs));
}
public FlatDetector(AbsoluteDate... eventTs) {
this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER,
new StopOnEvent<>(), new TimeDetector(eventTs));
}
private FlatDetector(double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super FlatDetector> newHandler,
EventDetector g) {
super(newMaxCheck, newThreshold, newMaxIter, newHandler);
this.g = g;
}
@Override
public double g(SpacecraftState s) throws OrekitException {
return FastMath.signum(g.g(s));
}
@Override
protected FlatDetector create(double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super FlatDetector> newHandler) {
return new FlatDetector(newMaxCheck, newThreshold, newMaxIter, newHandler, g);
}
}
/** quadratic. */
private static class ContinuousDetector extends AbstractDetector<ContinuousDetector> {
private static final long serialVersionUID = 1L;
/** time of the event to trigger. */
private final List<AbsoluteDate> eventTs;
public ContinuousDetector(double... eventTs) {
this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER,
new StopOnEvent<>(), toDates(eventTs));
}
private ContinuousDetector(double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super ContinuousDetector> newHandler,
List<AbsoluteDate> eventDates) {
super(newMaxCheck, newThreshold, newMaxIter, newHandler);
this.eventTs = eventDates;
}
@Override
public double g(SpacecraftState s) throws OrekitException {
final AbsoluteDate t = s.getDate();
int i = 0;
while (i < eventTs.size() && t.compareTo(eventTs.get(i)) > 0) {
i++;
}
i--;
if (i < 0) {
return t.durationFrom(eventTs.get(0));
} else if (i < eventTs.size() - 1) {
int sign = (i % 2) * 2 - 1;
return -sign * (t.durationFrom(eventTs.get(i)))
* (eventTs.get(i + 1).durationFrom(t));
} else {
int sign = (i % 2) * 2 - 1;
return -sign * (t.durationFrom(eventTs.get(i)));
}
}
@Override
protected ContinuousDetector create(
double newMaxCheck,
double newThreshold,
int newMaxIter,
EventHandler<? super ContinuousDetector> newHandler) {
return new ContinuousDetector(
newMaxCheck, newThreshold, newMaxIter, newHandler, eventTs);
}
}
private static class Handler<T extends EventDetector> extends RecordAndContinue<T> {
private final Action action;
public Handler(Action action) {
this.action = action;
}
public Handler(List<Event<T>> events, Action action) {
super(events);
this.action = action;
}
@Override
public Action eventOccurred(SpacecraftState s, T detector, boolean increasing) {
super.eventOccurred(s, detector, increasing);
return this.action;
}
}
}