Python Impulse At Node

After reading the Impulsive Maneuver java tutorial:

https://www.orekit.org/site-orekit-tutorials-11.0/tutorials/maneuvers.html

I’m trying to convert to Python a section of the java script

src/main/java/org/orekit/tutorials/maneuvers/ImpulseAtNode.java

that appears in the tutorials. In particular, I have troubles with its line 117:

final EnablingPredicate predicate = (state, detector, g) → state.getDate().isBefore(lastAllowedDate);

I’ve tried the following Python code:

ascendingNodeStopper = NodeDetector(inertialFrame).withHandler(StopOnIncreasing())
predicate = lambda state, detector, g: state.getDate().isAfterOrEqualTo(firingDate)
triggers = EventEnablingPredicateFilter(ascendingNodeStopper, predicate)

But I get an error in the last line:

triggers = EventEnablingPredicateFilter(ascendingNodeStopper, predicate)
orekit.InvalidArgsError: (<class ‘org.orekit.propagation.events.EventEnablingPredicateFilter’>, ‘init’, (<NodeDetector: org.orekit.propagation.events.NodeDetector@61a5b4ae>, <function Satellite.add_impulsive_maneuver..predicate at 0x7f11ad6a0b80>))

This happens because predicate is not an EnablingPredicate interface, but a simple Python function.

How should I define the predicate object in Python?

Thank you very much!
Alfredo

Hi,

Yes that java syntax is a bit hard to read, I assume it is this line:

final EnablingPredicate<EventDetector> predicate =
            (state, detector, g) -> state.getDate().isBefore(lastAllowedDate);

What I understand it means is that it is subclassing the EnablingPredicate filter on the fly, assigning it to a class and overrides a function with that signature with the given code line… or something like that.

For Python this is a bit more verbal, you need to subclass the PythonEnablingPredicate

class MyPredicate(PythonEnablingPredicate):
    def eventIsEnabled(self, state, detector, g):
        return state.getDate().isAfterOrEqualTo(firingDate)

predicate = MyPredicate()

I havent tested but something like that :slight_smile:

Note: corrected post, was missing a return statement.

1 Like

This is exactly that. It is a lambda function. But I think lambda functions exist in Python too. They exist in many languages and λ-calculus was even created before computers.

1 Like

Thank you very much @petrus.hyvonen!
Your reply solved my issue!
This is how I implemented it in Python:

from org.orekit.propagation.events import NodeDetector, PythonEnablingPredicate, EventEnablingPredicateFilter

...

attitudeOverride = LofOffset(inertialFrame,LOFType.VNC)
ascendingNodeStopper = NodeDetector(inertialFrame).withHandler(StopOnIncreasing())
class MyPredicate(PythonEnablingPredicate):
    def eventIsEnabled(self, state, detector, g):
        state.getDate().isAfterOrEqualTo(firingDate)
predicate = MyPredicate()
triggers = EventEnablingPredicateFilter(ascendingNodeStopper, predicate)
impMan = ImpulseManeuver(triggers, attitudeOverride, mandv, isp)

I’d like to thank @nlupi for helping me in implementing this solution.

Kind Regards,
Alfredo

Hi @petrus.hyvonen

I actually get the following error, when I propagate with the maneuver triggered at Node:

orekit.JavaError: <super: <class 'JavaError'>, <JavaError object>>
    Java stacktrace:
java.lang.RuntimeException: type error
        at org.orekit.propagation.events.PythonEnablingPredicate.eventIsEnabled(Native Method)
        at org.orekit.propagation.events.EventEnablingPredicateFilter.g(EventEnablingPredicateFilter.java:150)
        at org.orekit.forces.maneuvers.ImpulseManeuver.g(ImpulseManeuver.java:160)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator$AdaptedEventDetector.g(AbstractIntegratedPropagator.java:777)
        at org.hipparchus.ode.events.EventState.reinitializeBegin(EventState.java:175)
        at org.hipparchus.ode.AbstractIntegrator.acceptStep(AbstractIntegrator.java:299)
        at org.hipparchus.ode.nonstiff.RungeKuttaIntegrator.integrate(RungeKuttaIntegrator.java:170)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.integrateDynamics(AbstractIntegratedPropagator.java:416)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:372)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:340)

What is this error telling me?

Thank you.

Kind Regards,
Alfredo

Hi,
hm, it is something about that the type isnt corresponding to what is expected. And it happens in the eventIsEnabled method of PythonEnabngPredicate.

hard to troubleshoot without a full code set, but it could be that the second parameter in the method is a typed parameter, and that then the MyPredicated needs to match this type. This is done with the .of_ modifier.

https://www.orekit.org/static/apidocs/org/orekit/propagation/events/EnablingPredicate.html

Try

predicate = MyPredicate().of_(NodeDetector)
1 Like

Hi @petrus.hyvonen,
thank you very much for your suggestion. Unfortunately I’m still getting the java error:

orekit.JavaError: <super: <class 'JavaError'>, <JavaError object>>
    Java stacktrace:
java.lang.RuntimeException: type error
        at org.orekit.propagation.events.PythonEnablingPredicate.eventIsEnabled(Native Method)
        at org.orekit.propagation.events.EventEnablingPredicateFilter.g(EventEnablingPredicateFilter.java:150)
        at org.orekit.forces.maneuvers.ImpulseManeuver.g(ImpulseManeuver.java:160)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator$AdaptedEventDetector.g(AbstractIntegratedPropagator.java:777)
        at org.hipparchus.ode.events.EventState.reinitializeBegin(EventState.java:175)
        at org.hipparchus.ode.AbstractIntegrator.acceptStep(AbstractIntegrator.java:299)
        at org.hipparchus.ode.nonstiff.RungeKuttaIntegrator.integrate(RungeKuttaIntegrator.java:170)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.integrateDynamics(AbstractIntegratedPropagator.java:416)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:372)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:340)

I paste here my Python code that sets up the propagator:

def setup_propagator(self, inertialFrame, dt, eclipses=False):
        '''
        sets up orbit propagator and events detectors
        '''
        orbitType = OrbitType.EQUINOCTIAL
        integrator = ClassicalRungeKuttaIntegrator(dt)
        self.prop = NumericalPropagator(integrator)
        self.prop.setOrbitType(orbitType)
        self.prop.setAttitudeProvider(LofOffset(inertialFrame, LOFType.VNC))
        # Gravity
        gravityField = HolmesFeatherstoneAttractionModel(FramesFactory.getITRF(IERSConventions.IERS_2010, True), GravityFieldFactory.getNormalizedProvider(2, 0))
        self.prop.addForceModel(gravityField)
        # Eclipses
        if eclipses:
            eclipseDetectorWithLogger = EclipseDetector(sun, Constants.SUN_RADIUS, earth).withUmbra().withHandler(ContinueOnEvent())
            self.logger = EventsLogger()
            loggedDetector = self.logger.monitorDetector(eclipseDetectorWithLogger)
            self.prop.addEventDetector(loggedDetector)
        # Nodes
        NodesDetector = NodeDetector(inertialFrame).withHandler(ContinueOnEvent())
        self.NodesLogger = EventsLogger()
        NodesloggedDetector = self.NodesLogger.monitorDetector(NodesDetector)
        self.prop.addEventDetector(NodesloggedDetector)

I paste here my Python code that inserts the maneuver in the propagator object (notice the predicate definition as you suggested me):

def add_impulsive_maneuver(self,inertialFrame,firingDate,mandv,isp):
        '''
        This method inserts an impulsive maneuver in the propagator. Rules:
        - in plane maneuvers trigger: firingDate
        - out of plane maneuvers trigger: first Acending Node after firingDate
        '''
        attitudeOverride = LofOffset(inertialFrame,LOFType.VNC)                                     # Align attitude with VNC frame
        if abs(mandv.getY()) > 1e-3:                                                                # If 2nd component of delta-v is non zero, this is an out of plane maneuver:
            ascendingNodeStopper = NodeDetector(inertialFrame).withHandler(StopOnIncreasing())      #   stop at 1st ascending node
            class MyPredicate(PythonEnablingPredicate):                                             #   MyPredicate class definition
                def eventIsEnabled(self, state, detector, g):                                       #   eventIsEnabled method definition
                    state.getDate().isAfterOrEqualTo(firingDate)                                    #   enable event after input firing date
            predicate = MyPredicate().of_(NodeDetector)                                             #   define predicate object
            triggers = EventEnablingPredicateFilter(ascendingNodeStopper, predicate)                #   define trigger as ascending node stopper with predicate
        else:                                                                                       # This is an in plane maneuver:
            triggers = DateDetector(firingDate)                                                     #   define trigger as date detector at firing date
        impMan = ImpulseManeuver(triggers, attitudeOverride, mandv, isp)                            # define impulse maneuver
        self.prop.addEventDetector(impMan)                                                          # add maneuver to the propagator with event detector method

I paste here my Python code that propagates:

def propagate(self, startDate, finalDate, dt):
        '''
        propagates from startDate to finalDate with dt step
        '''
        self.prop.setInitialState(self.pv)                # set initial spate with self object opv (position velocity)
        states = []                                       # init states array
        while (startDate.compareTo(finalDate) <= 0.0):    # If startDate is earlier than finalDate:
            states.append(self.prop.propagate(startDate)) #   append spacecraft state after propagation
            startDate = startDate.shiftedBy(dt)           #   [AbsoluteDate] increase current date by dt
        [self.java_state_list.add(s) for s in states]     #   save states to java list
        self.pv = states[-1]                              #   save last state

The error occurs in this last method, while propagating, in line:

states.append(self.prop.propagate(startDate)) # append spacecraft state after propagation

What am I doing wrong?

Thank you so much for your support.

Kind Regards,
Alfredo

Hi quick response without testing,

It may be that you need to put the generic type on

.withHandler(StopOnIncreasing().of_(NodeDetector)

See example at:

1 Like

I’ve modified the ascending node stopper definition as:

ascendingNodeStopper = NodeDetector(inertialFrame).withHandler(StopOnIncreasing().of_(NodeDetector)) # stop at 1st ascending node

but I get the same error when propagating:

orekit.JavaError: <super: <class 'JavaError'>, <JavaError object>>
    Java stacktrace:
java.lang.RuntimeException: type error
        at org.orekit.propagation.events.PythonEnablingPredicate.eventIsEnabled(Native Method)
        at org.orekit.propagation.events.EventEnablingPredicateFilter.g(EventEnablingPredicateFilter.java:150)
        at org.orekit.forces.maneuvers.ImpulseManeuver.g(ImpulseManeuver.java:160)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator$AdaptedEventDetector.g(AbstractIntegratedPropagator.java:777)
        at org.hipparchus.ode.events.EventState.reinitializeBegin(EventState.java:175)
        at org.hipparchus.ode.AbstractIntegrator.acceptStep(AbstractIntegrator.java:299)
        at org.hipparchus.ode.nonstiff.RungeKuttaIntegrator.integrate(RungeKuttaIntegrator.java:170)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.integrateDynamics(AbstractIntegratedPropagator.java:416)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:372)
        at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:340)

What am I doing wrong?

Thanks,
Alfredo

Do you have the code as a full script/notebook that I could have a look at?

The error is, as seen in the logs, coming from when it enters the eventIsEnabled method, and there something that causes an type error occurs. Likely in the matching of types in the input parameters, or potentially in the code.

You don’t have a return statement in the method, it should return a boolean, doubt it is that that it complains on but you could try?

1 Like

Thanks @petrus.hyvonen,
I could solve my problem with the following code, suggested by @nlupi:

ascendingNodeStopper = NodeDetector(inertialFrame)                                      #   stop at 1st ascending node
class MyPredicate(PythonEnablingPredicate):                                             #   MyPredicate class definition
  def eventIsEnabled(self, state, detector, g):                                       #   eventIsEnabled method definition
    return state.getDate().isAfterOrEqualTo(firingDate)                             #   enable event after input firing date
predicate = MyPredicate()                                                               #   define predicate object
triggers = EventEnablingPredicateFilter(ascendingNodeStopper, predicate)                #   define trigger as ascending node stopper with predicate

The return statement was missing in the eventIsEnabled method definition, as you pointed out.

Now, maneuvers are correctly triggered at the first ascending node after firingDate.
Propagation is performed without errors.

Thank you so much for your support.

Kind Regards,
Alfredo

@petrus.hyvonen maybe you’d like to edit this answer and add the return statement in this line, so the solution is complete and useful for others.

Thanks

Yes, corrected!

1 Like

Hi @petrus.hyvonen,

I’ve noticed that my method for out-of-plane maneuvers to be triggered at ascending node had an issue. All nodes (ascending and descending) after the firingDate and up to the end of propagation were detected. This resulted in inclination maneuvers triggered at all ascending nodes after the firingDate up to the end of propagation. Moreover, the (undesired) descending nodes were also logged in the monitorDetector object (that I called ManLoggedDectector), introduced in order to store maneuver start times.
So, I had to introduce 3 conditions for proper maneuver triggering and these are:

  • cnd1: the node crossing shall happen after the firingDate
  • cnd3: the node crossing shall happen before the firingDate + one orbital period
  • cnd3: the node crossing shall be ascending

My final, tested and working Python code is the following:

def add_impulsive_maneuver(self,inertialFrame,firingDate,mandv,isp):
        '''
        This method inserts an impulsive maneuver in the propagator.
        Rules:
        - in plane maneuvers trigger: firingDate
        - out of plane maneuvers trigger: first Acending Node after firingDate

        inertialFrame : [Frame]      Frame for the attitudeOverride
        firingDate : [AbsoluteDate]  Maneuver time
        mandv : [3D vector]          Maneuver delta v vector
        isp : [s]                    Specific impulse
        '''
        attitudeOverride = LofOffset(inertialFrame,LOFType.VNC)                                     # Align attitude with VNC frame
        if abs(mandv.getY()) > 1e-3:                                                                # If 2nd component of delta-v is non zero, this is an out of plane maneuver:
            ascendingNodeStopper = NodeDetector(inertialFrame)                                      #   stop at 1st ascending node
            class MyPredicate(PythonEnablingPredicate):                                             #   MyPredicate class definition
                def eventIsEnabled(self, state, detector, g):                                       #     eventIsEnabled method definition
                    statedate = state.getDate()                                                     #       get state date
                    Vz = state.getPVCoordinates(inertialFrame).getVelocity().getZ()                 #       get state Vz
                    mindate = firingDate                                                            #       set min date
                    maxdate = firingDate.shiftedBy(state.getKeplerianPeriod())                      #       set max date (min date + 1 orbit period)
                    cnd1 = statedate.isAfterOrEqualTo(mindate)                                      #       set condition 1 (state date >= min date)
                    cnd2 = statedate.isBefore(maxdate)                                              #       set condition 2 (state date < max date)
                    cnd3 = Vz > 0.0                                                                 #       set condition 3 (state positive Vz)
                    return cnd1 and cnd2 and cnd3                                                   #       return boolean: true if 3 conditions are met
            predicate = MyPredicate()                                                               #   define predicate object
            triggers = EventEnablingPredicateFilter(ascendingNodeStopper, predicate)                #   define trigger as ascending node stopper with predicate
        else:                                                                                       # This is an in plane maneuver:
            triggers = DateDetector(firingDate)                                                     #   define trigger as date detector at firing date
        ManLoggedDectector = self.ManLogger.monitorDetector(triggers)                               # init. monitor detector from triggers
        self.prop.addEventDetector(ManLoggedDectector)                                              # add monitor detector to propagator
        impMan = ImpulseManeuver(triggers, attitudeOverride, mandv, isp)                            # define impulse maneuver
        self.prop.addEventDetector(impMan)                                                          # add maneuver to the propagator with event detector method

Is there a more elegant way to obtain the same result? (maybe working with some construction/handler of the NodeDetector?)

Summarizing: I want to monitor and log only ascending nodes, in the revolution (orbit) following the firingDate, in order to trigger there the next inclination maneuver.

Thank you very much.

Kind Regards,
Alfredo