Hello!
Preface: I’m using Java Orekit v12.2
and (of course) Hipparchus v3.1
.
I’d like capture the SpacecraftState
at each of the steps that a NumericalPropagator
takes as it propagates to an end date.
TL;DR: Is there a way to do this already, or something better than my very hacky EventHandler
?
Details:
I figured I could write a stateful EventHandler
with ::g
that always returns 0
to tell the propagator that every step is an event, and save the state in a NavigableSet
for each event.
Unfortunately, this causes an infinite loop in reinitializeBegin
where the loop tries to perturb g
, but g
is constant so it can’t etc. etc… Even hacking past the infinite loop with a single non-zero value doesn’t work.
So, I’ve tried a few hacks, and the only thing that mostly works reliably, is to trick findRoot
by having g
start at -1
and both get smaller and flip its sign every time g()
is called (code pasted at the bottom for the curious).
Like I said, this mostly works - NumProp and analytic - but it’s really rather hacky, and it’s not entirely reliable (e.g., findRoot
can fail).
Click to show StepCaptureDetector attempt
/** An {@link EventDetector} to capture all {@link SpacecraftState states} that a {@link Propagator} computes as it
* steps in time.
*
* @author Ryan Moser */
@NotThreadSafe
public class StepCaptureDetector implements EventDetector {
/** the {@link SpacecraftState} {@link Consumer} */
private final Consumer<SpacecraftState> scConsumer;
/** an {@link AdaptableInterval} that wraps the max step size */
private final AdaptableInterval adptIterval;
/** the value to return in {@link #g(SpacecraftState)} */
private double g;
/** Constructor
*
* @param scConsumer the {@link Consumer} that handles the {@link SpacecraftState} at each step. Note: if this
* {@link Consumer} is stateful - e.g., if updating a {@link Collection} or {@link Map} - the steps are not
* necessarily going to be temporally ordered, so consider using a {@link NavigableSet} or
* {@link NavigableMap} such as {@link TreeSet} or {@link TreeMap}.
*
* @param maxStep the max step to allow. If used with a {@link NumericalPropagator}, whichever is smaller between
* this and the next adaptable step will be used. */
public StepCaptureDetector(final Consumer<SpacecraftState> scConsumer, final double maxStep) {
this.scConsumer = requireNonNull(scConsumer, "scConsumer must be specified");
this.adptIterval = AdaptableInterval.of(maxStep);
this.g = -1.0; // this will be set to -1.0 when the propagator calls "init", but set it here anyway
}
@Override
public void init(final SpacecraftState ignored1, final AbsoluteDate ignored2) {
this.g = -1.0;
}
@Override
public double g(final SpacecraftState ignored) {
this.g *= -Numerics.SQUARE_ROOT_MACHINE_EPSILON;
/* we need the root finding algorithm to think that there's a root and that g is not constant, so -
* unfortunately - we can't simply return 0.0, AND we need the sign to flip. This value has worked well so
* far. */
return this.g;
}
@Override
public double getThreshold() {
return Numerics.SQUARE_ROOT_MACHINE_EPSILON;
}
@Override
public AdaptableInterval getMaxCheckInterval() {
return this.adptIterval;
}
@Override
public int getMaxIterationCount() {
return 10;
}
@Override
public EventHandler getHandler() {
return new EventHandler() {
@Override
public Action eventOccurred(final SpacecraftState state,
final EventDetector ignored, // not needed
final boolean increasing) {
if (increasing) { /* due to the internal workings of Orekit and the contrived "g" we have here, we need
* to skip every other step, otherwise we double up. If this was a "real"
* EventDetector - i.e., one with a less problematic "g" - this wouldn't be a
* problem. */
StepCaptureDetector.this.scConsumer.accept(state); // capture the state
}
StepCaptureDetector.this.init(null, null); // reset
return Action.CONTINUE;
}
};
}
}