EventDetectorsProvider NullPointerException in Orekit 12

I recently upgraded from 11.3.3 to 12.0 and am now running into the following error:

<super: <class ‘JavaError’>, >
Java stacktrace:
java.lang.NullPointerException
at org.orekit.propagation.events.EventDetectorsProvider.getEventDetectors(EventDetectorsProvider.java:85)
at org.orekit.forces.maneuvers.propulsion.PropulsionModel.getEventDetectors(PropulsionModel.java:67)
at org.orekit.forces.maneuvers.Maneuver.getEventDetectors(Maneuver.java:259)
at org.orekit.propagation.numerical.NumericalPropagator$Main.(NumericalPropagator.java:914)
at org.orekit.propagation.numerical.NumericalPropagator.getMainStateEquations(NumericalPropagator.java:890)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.createODE(AbstractIntegratedPropagator.java:598)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.integrateDynamics(AbstractIntegratedPropagator.java:471)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:424)
at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:384).

I am implementing a custom PythonThrustPropulsionModel class, but didn’t have any issues with it before in 11.3.3 and don’t see any changes in the documentation for ThrustPropulsionModel

Hi there

I don’t have the code in front of my eyes, but it’s possible that a method got renamed, like from getEventsDetectors to getEventDetectors or something.

Cheers,
Romain.

Hi Romain, thanks for the quick response. Unfortunately it doesn’t seem like the method was renamed.

I’m looking at the git difference and getEventsDetectors did become getEventDetectors. Same thing for getFieldEventsDetectors by the way, it lost the first S

Ah ok, however I actually didn’t define the getEventDetectors method originally because it’s not specified in the documentation. I tried defining it in my custom ThrustPropulsionModel and returning an empty stream, but still encounter the same error.

Hi @emery,

EventDetectorsProvider is a new interface added to avoid duplications in the Java code in v12.

The default version of PropulsionModel.getEventDetectors() method needs the method getParametersDrivers() to be implemented.

I think the Python version you implemented doesn’t understand the override of getEventDetectors you made (don’t know the reason).
So the solution here would probably be to implement the method getParametersDrivers() (and return an empty list of ParameterDriver probably).

Sorry the explanation is a bit complicated…
Maxime

Hello @emery,

You can take a look at the code snippet i have provided on this thread regarding the getParametersDrivers() if you want to return an empty list as @MaximeJ suggested

Cheers,
Vincent

1 Like

Hi @MaximeJ @Vincent, thanks for the responses/suggestions. I went ahead and implemented the suggestions, but unfortunately I still get the error. Below is a snippet of my code.

class PropulsionModel(PythonThrustPropulsionModel):
    def __init__(self, thrustDirectionProvider, isp):
        super().__init__()
        self.thrustDirectionProvider = thrustDirectionProvider
        self.isp = isp

    def getParametersDrivers(self):
        return Collections.emptyList()

    def getEventDetectors(self):
        return Stream.empty()

    def getAcceleration(self):
        pass

    def getDirection(self, s):
        direction = Vector3D.MINUS_I
        return direction
    
    def getFlowRate(self, s):
        thrust_mag, _ = self.thrustDirectionProvider.computeThrust(s)
        flow_rate = - thrust_mag / (self.isp * 9.80665)
        return float(flow_rate)
    
    def getIsp(self, s):
        return float(self.isp)

    def getMassDerivatives(self):
        pass
    
    def getThrust(self, s):
        thrust_mag, _ = self.thrustDirectionProvider.computeThrust(s)
        return thrust_mag

    def getThrustVector(self, s):

        thrust_mag, _ = self.thrustDirectionProvider.computeThrust(s)

        # propulsion vector in spacecraft frame
        thrust_vec_u = np.array([-1,0,0])
        thrust_vec = thrust_mag * thrust_vec_u
        if thrust_mag == 0:
            thrust_vec = Vector3D(-1e-20,0.0,0.0)
        else:
            thrust_vec = Vector3D(float(thrust_vec[0]),float(thrust_vec[1]),float(thrust_vec[2]))
    
        return thrust_vec

Hi @emery,

After several tries, i have finally found a way to make it work :

import orekit
from java.util import Collections
from java.util.stream import Stream
from orekit import JArray_double
from orekit.pyhelpers import setup_orekit_curdir
from org.hipparchus.geometry.euclidean.threed import Vector3D
from org.hipparchus.ode.nonstiff import DormandPrince853Integrator
from org.orekit.attitudes import FrameAlignedProvider
from org.orekit.forces.gravity import HolmesFeatherstoneAttractionModel
from org.orekit.forces.gravity.potential import GravityFieldFactory
from org.orekit.forces.maneuvers import Maneuver
from org.orekit.forces.maneuvers.propulsion import ConstantThrustDirectionProvider
from org.orekit.forces.maneuvers.propulsion import PythonPropulsionModel
from org.orekit.forces.maneuvers.trigger import DateBasedManeuverTriggers
from org.orekit.frames import FramesFactory
from org.orekit.orbits import KeplerianOrbit
from org.orekit.orbits import OrbitType
from org.orekit.orbits import PositionAngleType
from org.orekit.propagation import SpacecraftState
from org.orekit.propagation.numerical import NumericalPropagator
from org.orekit.time import AbsoluteDate
from org.orekit.utils import Constants
from org.orekit.utils import IERSConventions

vm = orekit.initVM()
setup_orekit_curdir("orekit-data.zip")


class PropulsionModel(PythonPropulsionModel):

    def __init__(self, thrustDirectionProvider, isp):
        super().__init__()
        self.thrustDirectionProvider = thrustDirectionProvider
        self.isp = isp

    def init(self, spacecraftState, absoluteDate):
        pass

    def getParametersDrivers(self):
        return Collections.emptyList()

    def getEventDetectors(self):
        return Stream.empty()

    def getAcceleration(self, spacecraftState, attitude, doubleArray):
        return Vector3D(1., 0., 0.)

    def getDirection(self, s):
        direction = Vector3D.MINUS_I
        return direction

    def getFlowRate(self, s):
        thrust_mag, _ = self.thrustDirectionProvider.computeThrust(s)
        flow_rate = - thrust_mag / (self.isp * 9.80665)
        return float(flow_rate)

    def getIsp(self, s):
        return float(self.isp)

    def getThrust(self, s):
        thrust_mag, _ = self.thrustDirectionProvider.computeThrust(s)
        return thrust_mag

    def getMassDerivatives(self, spacecraftState, doubleArray):
        return 0.

    def getThrustVector(self, s):

        thrust_mag, _ = self.thrustDirectionProvider.computeThrust(s)

        # propulsion vector in spacecraft frame
        thrust_vec_u = [-1, 0, 0]
        thrust_vec = thrust_mag * thrust_vec_u
        if thrust_mag == 0:
            thrust_vec = Vector3D(-1e-20, 0.0, 0.0)
        else:
            thrust_vec = Vector3D(float(thrust_vec[0]), float(thrust_vec[1]), float(thrust_vec[2]))

        return thrust_vec


mu = Constants.IERS2010_EARTH_MU
initialDate = AbsoluteDate()
inertialFrame = FramesFactory.getEME2000()
initialOrbit = KeplerianOrbit(6878000., 0.001, 1., 0., 0., 0., PositionAngleType.MEAN, inertialFrame, initialDate, mu)

minStep = 0.001
maxstep = 1000.0
initStep = 60.0
positionTolerance = 1.0
tolerances = NumericalPropagator.tolerances(positionTolerance,
                                            initialOrbit,
                                            initialOrbit.getType())
integrator = DormandPrince853Integrator(minStep, maxstep,
                                        JArray_double.cast_(tolerances[0]),
                                        # Double array of doubles needs to be casted in Python
                                        JArray_double.cast_(tolerances[1]))
integrator.setInitialStepSize(initStep)
satellite_mass = 100.0  # The models need a spacecraft mass, unit kg.
initialState = SpacecraftState(initialOrbit, satellite_mass)
propagator_num = NumericalPropagator(integrator)
propagator_num.setOrbitType(OrbitType.CARTESIAN)
propagator_num.setInitialState(initialState)
gravityProvider = GravityFieldFactory.getNormalizedProvider(10, 10)
propagator_num.addForceModel(
    HolmesFeatherstoneAttractionModel(FramesFactory.getITRF(IERSConventions.IERS_2010, True), gravityProvider))

# Simple attitude provider
attitudeProvider = FrameAlignedProvider(inertialFrame)

# Maneuver trigger
propDate = initialDate.shiftedBy(500.)
propDuration = 100.
maneuverTrigger = DateBasedManeuverTriggers(propDate, propDuration)

# Custom propulsion model
customPropulsionModel = PropulsionModel(ConstantThrustDirectionProvider(Vector3D(1., 0., 0.)), 300.)

# Create custom maneuver
customManeuver = Maneuver(attitudeProvider, maneuverTrigger, customPropulsionModel)

# Add custom maneuver
propagator_num.addForceModel(customManeuver)

# Propagate
end_state = propagator_num.propagate(initialDate, initialDate.shiftedBy(3600.0 * 1))

It turns out that using the PythonThrustPropulsionModel was leading to a null pointer exception being thrown no matter what i did so i switched to PythonPropulsionModel

I hope it suits your needs !

Cheers,
Vincent

Yes it is a bug, thanks for identifying. I will fix this in a coming build.

The error is in that getParameterDrivers in PythonThrustPropulsionModel wasn’t exposed to Python (not made “native” in java), see https://github.com/petrushy/Orekit/blob/e16f8faaaf43a678991b87c6ee75b71a1f00e78a/src/main/java/org/orekit/forces/maneuvers/propulsion/PythonThrustPropulsionModel.java#L84

Updated conda packages are available now on conda forge, fixing this bug. (v12.0 build 1)

3 Likes

Hi @Vincent , @petrus.hyvonen thank you both for the help, I was able to get it working.

1 Like

Hi @emery, you are welcome ! Many thanks as well to @petrus.hyvonen for fixing the issue so fast.

Cheers,
Vincent

Thanks @petrus.hyvonen !

Quick question, wouldn’t it be safer to return an empty list (return Collections.emptyList();) instead of returning null in PythonThrustPropulsionModel?

Hi @MaximeJ ,

Yes, the link is to the errornous code, the nominal code is to declare this as native and let the python implementation take care of implementing the desired response:

/** {@inheritDoc} */
    @Override
    public native List<ParameterDriver> getParametersDrivers()

;

Regards

1 Like

Oh ok sorry. Thanks again Petrus