Revamping AdditionalStateProvider or adding a method to Propagator

When doing propagation, we can set up additional states one at a time, by registering several AdditionalStateProvider instances that manage one additional state each. In the case of numerical propagators, we also have AdditionalDerivativesProvider which manages differential equations.

In some cases, this may be insufficient or inefficient. I have one case where I need not to set up an additional state, but to modify the orbit itself, preserving the additional states.

I managed to do the former by creating a proxy propagator that extends AbstractAnalyticalPropagator wrapping a regular propagator and implementing propagateOrbit in such a way it calls propagate on the wrapped propagator, extract the orbit, patch it, and let AbstractAnalyticalPropagator finish building the state. This does allow modifying the orbit, but it does not preserve the additional states, since propagateOrbit just returns an orbit.

I could perhaps have the proxy propagator set up a bunch of AdditionalStateProvider too and extract the additional states one by one to add them. This would however imply either calling the propagate method from the wrapped propagator several times (once for the orbit and once for each additional state), or setting up some fancy cache with lazy evaluation, and in all cases it would require knowing beforehand which states are in the wrapped propagator to set up additional states providers. This seems weird and inefficient.

I would like to be able to patch states directly in one go.

I see two ways to do it (both requiring API change, so it would have to wait for 13.0).

The first way would be to revamp the AdditionalStateProvider interface so instead of just returning the additional state it would build the new SpacecraftState. Currently, these providers are used in a loop that handle yields between providers. At the core of this loop, when a provider is apply, the state is updated by a single line:

updated    = updated.addAdditionalState(provider.getName(), provider.getAdditionalState(updated));

This could be replaced by:

updated = provider.update(updated);

With such a change, one additional state provider could manage several states at once, and it could change the orbit, the attitude and the mass too.

Another way to achieve the same result would be to add a new dedicated interface (say StatePostProcessor) and a corresponding addStatePostprocessor method in Propagator. We could reproduce the yield mechanism too if users want to add several postprocessors.

I would prefer the first way, i.e. changing AdditionalStateProvider because it is straightforward to do and simpler for users.

What do you think?

Replying to myself…

In fact, changing AdditionalStateProvider API can be done in a compatible way quite easily by adding a default implementation to the interface:

    /** Update a state.
     * @param state spacecraft state to update
     * @return updated state
     * @since 12.1
     */
    default SpacecraftState update(final SpacecraftState state) {
        return state.addAdditionalState(getName(), getAdditionalState(state));
    }

Done as issue 1369.

2 Likes

Great @luc!

I have two questions on that topic:

  • What is your initial use case or goal? (just out of curiosity :wink: )
  • Is there a design reason why you used abstract classes implementing AdditionalStateProvider instead of interfaces extending the same interface?

Ah ah…

In some cases, ephemerides are not given at center of mass of satellite but at mean center of phase of an antenna (typically for GNSS navigation messages), so I need to fix the loaded pseudo-orbit by the antenna offset to go back to center of mass.

If I had used interfaces, users would have needed to implement getName and getAdditionalState. I wanted the user to just have to implement one method. So I used an abstract class that implement everything in the interface, even getName and getAdditionalState. Unfortunately, as for compatibility reasons I needed to have update implemented by default in the interface and I wanted the user to be forced to implement it, I used the trick to add a change method that could remain abstract.

Hi Luc,

Modifying the orbit introduces singularities for the numerical propagator no? Shouldn’t this be handled a bit like RESET_STATE for events?

Cheers,
Romain.

Thanks for the insight, I never thought of that.

Ok, my bad, I thought you could provide an overridden “default” version of getName and getAdditionalState in the new interface.

Maxime

For numerical propagators, probably. In fact it is the reason why in the unit tests I only modified attitude and mass, I didn’t want to take any risks.

This feature cannot be handled by RESET_STATE because it is not a single change at one point, it is change required throughout the propagation, even in step handlers and events handlers, just like additional states can be used everywhere.

I see. Well with non gravitational forces even the mass has an indirect impact on the orbit via the acceleration though (and even attitude with non isotropic objects actually). I guess we just need to be clear with the risks in the documentation.

Cheers,
Romain.