ForceModel remove/delete for NumericalPropagator

Hello everyone, I have several questions about using NumericalPropagator.

I have a scenario where I have to add/remove a Maneuver ForceModel in loop. For example, I want to find maneuver duration such that osculating semimajor axis after maneuver is close to desired one.

NumericalPropagator supports addForceModel(ForceModel), but not something like removeForceModel(ForceModel) (I see a problem with implementation of this method, because it can be several ForceModel of same type) or removeLastAddedForceModel(). So I have to do some workaround and write some extra code allowing me to do that.

So, my questions are:

  1. Is there a way solve my problem for scenario I described above by the means of Orekit (or maybe you have some recommended approach to do that)?
  2. Do you have any plans about add such a feature (to remove last force/specific force)? Maybe there were some reasons not to implement such methods?

Thank you in advance for your answers.

Hi @Carboh , welcome to the forum

You can look at the DateBasedManeuverTriggers to build your maneuver. This trigger exposes several ParameterDriver instances thanks to its getParametersDrivers method, and these drivers allow you to change the maneuver dates. You could either use the first two drivers that correspond to start and stop date, or you can use the last two drivers that correspond to median date and duration. When you use setValue in this drivers, you change the corresponding date/duration.

This feature can be used either to estimate maneuvers in orbit determination or to optimize maneuvers.
We wanted to add a tutorial showing this last use case, but did not wrote it yet.

Hi @luc , thanks for your answer

Seems your advice worked for me. Please, can you review code snippets below to confirm that it is recommended approach and I’m doing everything right?

First, I create a Maneuver with DateBasedManeuverTriggers trigger and add it to propagator:

AttitudeProvider impulseAttitude = new LofOffset(
        FramesFactory.getGCRF(),
        LOFType.VNC);

DateBasedManeuverTriggers trigger = new DateBasedManeuverTriggers(
        maneuverStartDate, Constants.JULIAN_DAY);

PropulsionModel propulsionModel = new BasicConstantThrustPropulsionModel(
        5,
        300,
        Vector3D.PLUS_I,
        "Maneuver");

Maneuver maneuver = new Maneuver(impulseAttitude, trigger, propulsionModel);
propagator.addForceModel(maneuver);

After, I use a bisection method to find a needed duration (the scenario described in my first commentary in this topic):

double durationMin = 1.0;
double durationMax = 1000.0;
double duration = 0.0;

while (Math.abs(desiredSemimajorAxis - currentSemimajorAxis) > 1e-3) {
    duration = (durationMin + durationMax) / 2.0;

    // change maneuver duration using following 3 lines of code
    var drivers = trigger.getParametersDrivers();
    var durationDriver = drivers.get(3);
    durationDriver.setValue(duration);

    AbsoluteDate maneuverFinish = maneuverStartDate.shiftedBy(duration);
    SpacecraftState finalState = propagator.propagate(maneuverFinish);
    
    currentSemimajorAxis = finalState.getA();
    if (currentSemimajorAxis > desiredSemimajorAxis) {
        durationMax = duration;
    } else {
        durationMin = duration;
    }

    propagator.resetInitialState(initialState);
}

final double finalDuration = maneuver.getManeuverTriggers().getParametersDrivers().get(3).getValue();

This looks correct to me.
You may perhaps save some evaluations using a more efficient root finder. I’ll suggest this:

  final double durationMin = 1.0;
  final double durationMax = 1000.0;
  final ParameterDriver durationDriver = trigger.
                                         getParametersDrivers().
                                         stream().
                                         filter(d -> d.getName().endsWith(ParameterDrivenDateIntervalDetector.DURATION_SUFFIX)).
                                         findFirst().
                                         get();

  final double relativeAccuracy              = 1.0e-13;
  final double absoluteAccuracy              = 1.0e-9;
  final double functionValueAccuracy         = 1.0e-3;
  final int    maximumOrder                  = 5;
  final int    maxEval                       = 1000;
  final BracketingNthOrderBrentSolver solver = new BracketingNthOrderBrentSolver(relativeAccuracy,
                                                                                 absoluteAccuracy,
                                                                                 functionValueAccuracy,
                                                                                 maximumOrder);
  final double finalDuration = solver.solve(maxEval,
                                            d -> {
                                                durationDriver.setValue(d);
                                                propagator.resetInitialState(initialState);
                                                SpacecraftState s = propagator.propagate(maneuverStartDate.shiftedBy(d));
                                                return s.getA() - desiredSemimajorAxis;
                                            },
                                            durationMin,
                                            durationMax);

I wonder however if in this specific case another approach would be faster.
As you don’t change the start date but only the duration of the maneuver and as you check the semi-major axis at maneuver end only, I would use an event detector.

The idea would be to run just one propagation, with a duration set to durationMax, and to add to the propagator an event detector checking the difference between current semi-major axis and desired semi-major axis, and an event handler that would return Action.STOP when the event occurs. Then during the propagation, the semi-major axis will change due to the maneuver, but as soon as it reaches the expected value, the propagation will stop. Then comparing the date of final state and the date of maneuver start, you will get the duration you seek.

You could even use this as a maneuver trigger to stop just the maneuver itself and continue propagation when you have reached your desired semi major axis.

Thanks! Looks like exactly I was looking for.

Wow, great idea, thanks!

I’ve implemented your idea. In this particular case, it works faster than with BracketingNthOrderBrentSolver, but just a little bit. However, I have some use cases, where the F is more costly to compute. I’ll try to change my old solution to this new idea and see how it affects performance.

In fact, I have a scenario where I need to change both Maneuver start date and duration. The code I wrote looks similar to one I wrote above. The difference it that I additionally use startTimeDriver (drivers.get(0)).

If my provided use case is suitable for example of ParameterDriver usage, I can open PR and add it.

Beware when mixing one parameter driver from the first pair (start/stop) and from the second pair (middle/duration). These pairs are intended to work as pair of independent parameters. This is important when computing partial derivatives. If for example you compute ∂a/∂tₛ where tₛ is the start time, it assumes the end time is fixed, hence it has an effect on duration has duration will be end minus start. If on the other hand you compute ∂a/∂d where d is duration, it assumes middle time is fixed, hence it moves both start and end time symmetrically.

A PR on the tutorials would be welcome!

I have removed the “solved” flag in a previous post in this thread as I now think this solution was flawed. The problem is linked to the mix between start and duration.

As I explained in the previous post, start/stop work as a pair, and median/duration work as another pair. In fact, what is really done under the hood is that start and stop are the raw parameters, whereas median date and duration are derived parameters. What really happens when median date is changed by Δt is that both start and stop are shifted by Δt. What really happens when duration is changed by Δt is that start is shifted by -Δt/2 and stop is shifted by +Δt/2. So when we just reset duration, we change start date. If we change duration by Δt and reset initial state as if start date was not changed, we miss Δt/2 worth of propulsion time.

In order to match your needs, the solution would be to use start and stop, not start and duration. So I would suggest that you build the raw maneuver as if it was zero duration with

DateBasedManeuverTriggers trigger = new DateBasedManeuverTriggers(maneuverStartDate, 0.0);

and then to select the parameter driver that ends with ParameterDrivenDateIntervalDetector.STOP_SUFFIX and set the value of this parameter to the duration of the maneuver. As the zero value for this parameter is to have a zero duration maneuver, then setting it to Δt implies you have a maneuver with a total duration of Δt, so it is important that the raw maneuver really has zero duration.

With this change, you properly have a maneuver with fixed start date and parameterized stop date.

So, in my example above, to make things right, I should change:

  1. DateBasedManeuverTriggers duration to 0:
DateBasedManeuverTriggers trigger = new DateBasedManeuverTriggers(
        maneuverStartDate, 0.0); // zero duration!
  1. durationDriver to stopDriver:
final ParameterDriver stopDriver = trigger.
        getParametersDrivers().
        stream().
        filter(d -> d.getName().endsWith(ParameterDrivenDateIntervalDetector.STOP_SUFFIX)). // instead of DURATION_SUFFIX
        findFirst().
        get();
  1. durationDriver.setValue(*) to stopDriver.setValue(*):
final double finalDuration = solver.solve(maxEval,
        d -> {
            stopDriver.setValue(d); // instead of durationDriver.setValue(d);
            propagator.resetInitialState(initialState);
            SpacecraftState s = propagator.propagate(maneuverStartDate.shiftedBy(d));
            return s.getA() - desiredSemimajorAxis;
        },
        durationMin,
        durationMax);

If so, I have a problem with SpacecraftState s = propagator.propagate(maneuverStartDate.shiftedBy(d));
propagate is only called once, and seems propagation becomes eternal. It never finishes.