I am currently trying to implement a battery management state related to propulsion and position with respect to the sun, but I am having trouble adding an AdditionalDerivativesProvider to my numerical propagator.
class PowerConsumption(AdditionalDerivativesProvider):
def __init__(self, trigger_name, manage_start, maneuver):
""" Initialize the Power Consumption provider. """
self.power_consumption_name = "power-" + trigger_name
self.manage_start = manage_start
self.maneuver = maneuver
def getName(self):
""" Get the name of the additional derivatives provider. """
return self.power_consumption_name
def getDimension(self):
""" Get the dimension of the derivatives. """
return 1
def init(self, initial_state, target):
""" Initialize the state at the beginning of propagation . """
pass
def combinedDerivatives(self, state):
""" Return no derivatives (pass). """
return CombinedDerivatives([0.0],[0.0])
trig = DateBasedManeuverTriggers("trig", AbsoluteDate(2024, 2, 1, TimeScalesFactory.getUTC()), 10000.0)
prop_model = BasicConstantThrustPropulsionModel(1.0, 1000.0, Vector3D.PLUS_I, "LowThrust")
my_maneuver = Maneuver(LofOffset(inertialFrame, LOFType.TNW), trig, prop_model)
initialState = SpacecraftState(initialOrbit, satellite_mass)
power_consumption_provider = PowerConsumption(trigger_name="maneuver1", manage_start=True, maneuver=my_maneuver)
power_consumption_provider.init(initialState, initial_date)
propagator = NumericalPropagator(integrator)
propagator.addAdditionalDerivativesProvider(power_consumption_provider)
propagator.setInitialState(initialState)
final_state = propagator.propagate(initial_date, end_date)
You are getting this error because you are trying to directly extends a Java interface which is not possible with the wrapper.
Every interface has a Python equivalent in the wrapper. In your case,
you’ll want to use PythonAdditionalDerivativesProvider instead of AdditionalDerivativesProvider.
class PowerConsumption(PythonAdditionalDerivativesProvider):
Also, don’t forget to call the super init method :
Okay thank you, it’s working perfectly fine, I chose the AdditionalStateProvider instead of the AdditionalDerivativesProvider to handle the battery capacity which seems to be more appropriate.
Hi again, I’m having trouble using my additional state. Basically, I’m trying to get the thrust duration in real-time to subtract the battery capacity during propagation. When I use a state.getDate() inside my additional state definition, I notice some anomalies. The dates can go backward, like this:
It’s probably due to event detection.
When the propagator detects the occurrence of an event in a time interval, this triggers a dichotomy method that will pinpoint the exact date of the event.
Thus it enters a loop with forward and backward steps.
In your case, it seems to be the same time steps though, switching between 39 and 49 seconds. I admit this looks weird.
If you can share with us a working example we may be able to understand what’s happening here.
Okay I see , those are the interesting part of the code :
This is my custom additional state provider (BMS) that tracks battery capacity based on thrust duration, using the current date from state.getDate() to accumulate time. (The print state.getDate() is there)
class BMS(PythonAdditionalStateProvider):
def __init__(self, capacity, power_rate, maneuver):
"""
Initialize the battery management system (BMS).
:param capacity: Initial battery capacity in Wh.
:param power_rate: Thruster consumption.
:param maneuver: The Maneuver object to track.
"""
super().__init__()
self.capacity = capacity
self.power_rate = power_rate
self.maneuver = maneuver
self.name = "BMS"
self.last_update_date = None # To track the last update time
def init(self, s, date):
"""
Initialize the battery management system (BMS).
:param s: SpacecraftState at the start.
:param date: AbsoluteDate, the target date for propagation.
"""
self.last_update_date = date # Set the initial date
pass
def getName(self):
"""
Returns the name of the additional state.
:return: Name of the additional state as a string.
"""
return self.name
def getAdditionalState(self, s):
current_date = s.getDate()
print(current_date)
Etat=self.maneuver.getManeuverTriggers().isFiring(current_date,[])
#print(Etat)
global mandetec
global dtglobal
if Etat and len(mandetec) ==0:
mandetec.append(current_date)
if Etat and len(mandetec) !=0:
previous_step = mandetec[-1]
dt = current_date.durationFrom(previous_step)
dtglobal=dtglobal+dt
mandetec.append(current_date)
self.capacity=self.capacity-(dt*self.power_rate)
return [self.capacity] # Return the current battery capacity
Then I have 3 detectors which are combined in a startStopDetector :
sma_start_detector = BooleanDetector.notCombine(SMA_f_Detector(SK_SMA_LINF))
sma_stop_detector = SMA_f_Detector(SK_SMA_SUP).withMaxCheck(60.0)
Remove_first_trigger = BooleanDetector.notCombine(MissionDurationDetector(initial_date.shiftedBy(2000.0)))
mission_duration_detector = BooleanDetector.notCombine(MissionDurationDetector(Mission_End_Date))
# mission_duration_detector = AbstractDetector.cast_(mission_duration_detector)
# Combine detectors: Only execute SMA maneuvers if within mission duration
combined_start_detector = BooleanDetector.andCombine([sma_start_detector, mission_duration_detector])
combined_stop_detector = BooleanDetector.andCombine([NegateDetector(BooleanDetector.notCombine(sma_stop_detector)), NegateDetector(Remove_first_trigger)])
I also have a orekitfixedstephandler but I assume that It won’t impact the situation.
Keeping a state in something called by a propagator should be avoided if possible.
This is even more important with numerical propagators. The fact is that as an integrator is attempting to compute one step, it essentially samples both in time and in state vector, trying to build a smooth model. This implies going back and forth in time during step, but it also implies that the state and time may be inconsistent in the trial points. It is only once the step has been accepted that a smooth model is built by the integrator to represent consistent time evolution of the state vector along during the step.
What this means is that when the handleStep method of a step handler or the eventOccurred method of an event detector are called, the model can be trusted, but when either a force model acceleration, or an additional state provider or an event detector g function are called, the one-point input state cannot be trusted, it is only one attempt made by the integrator that may never be on the real trajectory.
This is why in some cases, when we nevertheless need to store something, we end up using TimeSpanMap instances to at least get proper time handling (but we still lose time/state consistency).