I want to implement a fuel reserve state to track the fuel reserves of a spacecraft throughout the propagation. To do this I am using a PythonAdditionalDerivativesProvider but I am struggling to correctly implement it in my numerical integrator. I tried searching for examples but I could only find this Adding an AdditionalDerivativesProvider using python .
This is the minimal example used to test the implementation inspired from the linked topic. I am using it to check I am doing things right before making it more complex.
orekit.JavaError: <super: <class 'JavaError'>, <JavaError object>>
Java stacktrace:
org.orekit.errors.OrekitException: additional data "Fuel_Amount" unknown
at org.orekit.propagation.SpacecraftState.getAdditionalData(SpacecraftState.java:704)
at org.orekit.propagation.SpacecraftState.getAdditionalState(SpacecraftState.java:680)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.secondary(AbstractIntegratedPropagator.java:614)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.createInitialState(AbstractIntegratedPropagator.java:595)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.integrateDynamics(AbstractIntegratedPropagator.java:511)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:448)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:408)
I’m not sure what I’m missing. Any help on how to correctly register or initialize this additional state would be appreciated.
Thanks in advance.
Thanks a lot for your answer.
However, I am still encountering issues. Here is the code now used to add the AdditionalDerivativesProvider:
# Adding the added derivative.
Fuel_amount_provider = FuelAmount()
Fuel_amount_provider.init(initialState, start_date)
propagator_num.addAdditionalDerivativesProvider(Fuel_amount_provider)
# Setting the initial state up.
propagator_num.setOrbitType(orbitType)
initialState = initialState.addAdditionalData(Fuel_amount_provider.getName(), 100.0)
propagator_num.setInitialState(initialState)
The error I am now getting is the following:
state = [propagator_num.propagate(tt) for tt in t]
~~~~~~~~~~~~~~~~~~~~~~~~^^^^
orekit.JavaError: <super: <class 'JavaError'>, <JavaError object>>
Java stacktrace:
java.lang.RuntimeException: AttributeError
at org.orekit.propagation.integration.PythonAdditionalDerivativesProvider.yields(Native Method)
at org.orekit.propagation.integration.AbstractIntegratedPropagator$ConvertedSecondaryStateEquations.computeDerivatives(AbstractIntegratedPropagator.java:874)
at org.hipparchus.ode.ExpandableODE.computeDerivatives(ExpandableODE.java:139)
at org.hipparchus.ode.AbstractIntegrator.computeDerivatives(AbstractIntegrator.java:260)
at org.hipparchus.ode.AbstractIntegrator.initIntegration(AbstractIntegrator.java:205)
at org.hipparchus.ode.nonstiff.EmbeddedRungeKuttaIntegrator.integrate(EmbeddedRungeKuttaIntegrator.java:196)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.integrateDynamics(AbstractIntegratedPropagator.java:517)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:448)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:408)
The propagation runs perfectly when removing the code for the AdditionalDerivativesProvider. Sorry if this is obvious. Thanks for your help in advance.
to tell the propagator that it doesn’t need to wait for other variables to be computed before evaluating combinedDerivatives (if this is not the case, update it accordingly).
There is no dumb question here, especially about the Python wrappers!
Thanks a lot for your help.
The PythonAdditionalDerivativesProvider now mostly works. I have one last question, I have no problem retrieving the additional value from the SpacecraftState using getAdditionalState(“Fuel_Amount”) when processing the data at the end of the propagation. However, trying to do the same in a PythonForceModel returns an error during the propagation.
Here are the PythonAdditionalDerivativesProvider and the PythonForceModel:
# Custom derivative to track the amount of fuel stored and linking it to the SpacecraftState.
class FuelAmount(PythonAdditionalDerivativesProvider):
def __init__(self):
super().__init__()
self.name = "Fuel_Amount"
self.max_capacity = 100e3*0.1/2
def getName(self):
return self.name
def getDimension(self):
return 1
def init(self, initial_state, target):
pass
def yields(self, spacecraft_state):
return False
def combinedDerivatives(self, spacecraft_state):
return CombinedDerivatives([-1.0e-3],[0.0])
# Custom integration of the thrust.
class ThrusterModel(PythonForceModel):
def __init__(self):
super().__init__()
self.stored_fuel = 0 # Set the initial fuel value to 0 kg.
self.thrust = 5.0e-1 # Thrust amplitude in N.
self.Isp = 30e3 # Isp in s.
def acceleration(self, spacecraft_state, doublearray):
position = spacecraft_state.getPVCoordinates().getPosition()
velocity = spacecraft_state.getPVCoordinates().getVelocity()
absolute_date = spacecraft_state.getDate()
# Conversion to get the altitude
# First conversion from the inertial frame to the body frame.
inertial2body = jupiter_inertialframe.getTransformTo(jupiter_bodyframe, absolute_date)
position_vector_body = StaticTransform.cast_(inertial2body).transformPosition(position)
# Then conversion to geodetic.
geodetic_coordinates = jupiter_shape.transform(position_vector_body, jupiter_bodyframe, absolute_date)
altitude = geodetic_coordinates.getAltitude()
fuel = spacecraft_state.getAdditionalState("Fuel_Amount")
if altitude > 850e3: # When the thruster fires.
# Bad thrust integration but this works for now (has to thrust along velocity vector).
thrust_vector = velocity.scalarMultiply(1.0 / velocity.getNorm() * self.thrust / spacecraft_state.getMass())
return thrust_vector
else: # When the thruster doesn't fire.
return Vector3D(0.0, 0.0, 0.0)
def addContribution(self, spacecraft_state, timeDerivativesEquations):
timeDerivativesEquations.addNonKeplerianAcceleration(self.acceleration(spacecraft_state, None))
def getParametersDrivers(self):
return Collections.emptyList()
def init(self, spacecraft_state, absoluteDate):
pass
def getEventsDetectors(self):
return Stream.empty()
And here is the code to add them to the propagator:
# Adding the added derivative.
Fuel_amount_provider = FuelAmount()
Fuel_amount_provider.init(initialState, start_date)
propagator_num.addAdditionalDerivativesProvider(Fuel_amount_provider)
# Setting the initial state up.
propagator_num.setOrbitType(orbitType)
initialState = initialState.addAdditionalData("Fuel_Amount", 100.0)
propagator_num.setInitialState(initialState)
# Adding the forces to the propagator
# Gravity (using a simple model for now)
propagator_num.setMu(mu)
# The custom thrust
propagator_num.addForceModel(ThrusterModel())
The error returned:
state = [propagator_num.propagate(tt) for tt in t]
~~~~~~~~~~~~~~~~~~~~~~~~^^^^
orekit.JavaError: <super: <class 'JavaError'>, <JavaError object>>
Java stacktrace:
org.orekit.errors.OrekitException: additional data "Fuel_Amount" unknown
at org.orekit.propagation.SpacecraftState.getAdditionalData(SpacecraftState.java:704)
at org.orekit.propagation.SpacecraftState.getAdditionalState(SpacecraftState.java:680)
at org.orekit.forces.PythonForceModel.addContribution(Native Method)
at org.orekit.propagation.numerical.NumericalPropagator$Main.computeDerivatives(NumericalPropagator.java:995)
at org.orekit.propagation.integration.AbstractIntegratedPropagator$ConvertedMainStateEquations.computeDerivatives(AbstractIntegratedPropagator.java:818)
at org.hipparchus.ode.ExpandableODE.computeDerivatives(ExpandableODE.java:134)
at org.hipparchus.ode.AbstractIntegrator.computeDerivatives(AbstractIntegrator.java:260)
at org.hipparchus.ode.AbstractIntegrator.initIntegration(AbstractIntegrator.java:205)
at org.hipparchus.ode.nonstiff.EmbeddedRungeKuttaIntegrator.integrate(EmbeddedRungeKuttaIntegrator.java:196)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.integrateDynamics(AbstractIntegratedPropagator.java:517)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:448)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:408)
The propagation runs fine if the line fuel = spacecraft_state.getAdditionalState("Fuel_Amount") is removed from ThrusterModel. What could be causing this?
Thanks once again for your help in advance.
I’m not sure why you’re getting this error.
However, if your usecase is just to model a propulsive force consuming mass, there is already the Maneuver class.
I initially looked at the Maneuver class but it doesn’t fully fit my usecase. To my knowledge, when using the Maneuver class, the fuel mass can only be depleted and doesn’t allow any replenishment. In my case, there are phases of fuel collection and phases of thruster firing during the propagation. The AdditionalDerivativesProvider allows to have different rates (one positive and one negative) depending on the SpacecraftState, which perfectly suits my usecase. The only issue I am encountering now is accessing the AdditionalState during the propagation in a ForceModel. Maybe the ForceModel is called before the initialState with the AdditionalState initial value is set by the propagator despite the ForceModel being added after the initialState is set in the code?
Ok so if you need to only modify the rates of the mass, you don’t need an additional derivative provider, you can do it directly via a (Python)ForceModel.
Inside addContribution. Check out the Java code for Maneuver about how it’s done
Thanks again,
Inside the addContribution in a PythonForceModel, the mass derivative can be changed using the addMassDerivative on the TimeDerivativesEquations interface. However, addMassDerivative only accepts negative mass flow values, not positive ones like I would need. Maybe there is another way I am not aware of to do so. If you could link the Maneuver code you mentioned in your last message as I can’t seem to find it.
I also investigated my issue more and found a workaround (even if not particularly elegant). For some reason, the additional data is not present in the SpacecraftState passed to the ForceModel during the propagation despite it being computed by the AdditionalDerivativesProvider.
I added a line in the AdditionalDerivativesProvider to print “derivative” each time it runs and added print(spacecraft_state.hasAdditionalData("Fuel_Amount")) and print("thrust") to the ForceModel. The results for a few runs are below:
The ForceModel and the AdditionalDerivativesProvider are called one after another yet the SpacecraftState doesn’t have any additional data (it isn’t the name “Fuel_Amount” being wrong).
The workaround I found is to modify the mass directly through the mainStateDerivativesIncrements in CombinedDerivatives in the AdditionalDerivativesProvider. This modifies the mass of the SpacecraftState using either negative values or positive values and can be accessed in the ForceModel.
Actually yeah, you can define negative mass derivate in ForceModel, however when used inside NumericalPropagator it will throw an error.
It is indeed possible that the additional states are not available when evaluating the main derivatives. As you found out yourself, you can still modify them later via the combinedDerivatives method.
A piece of advice, if your mass rate has discontinuities, to reduce noise you should add event detectors with a ResetDerivativesOnEvent handler to capture them well in your numerical integration.
Thanks for the advice, I will apply it during my propagation. Yeah, it seems the propagator can have little quirks like the additional states being evaluated separately, which can cause behaviors that might not be expected like in my case.