Adding java classes to Orekit python wrapper as a second Python extension

Hi,

I have been using Orekit for a while, and for a new project I decided to try using the Python wrapper.

Due to one of the requirements being that the code must be reasonably optimized, I had to write some java classes to be used as FixedStepHandlers so the code doesn’t have to switch between the Java VM and the Python VM as much.

In order to use these classes in Python I originally rebuilt the whole wrapper with a similar script to the one in conda-forge but adding an extra jar with my compiled classes. Unfortunately, due to a change in requirements for the project, now I need to build a separate python jcc extension for my classes, and I haven’t managed to make it work. I followed JCC’s instructions on building both orekit and my extension with the shared flag and importing orekit into my extension, and then running initVM only on my package with both java class paths.

The issue I’m hitting is that, when doing this, static members of classes such as Vector3D.PLUS_K are not loaded into the Python side of things, resulting in errors. If, instead, I use initVM on the original orekit package with both java classpaths, it loads these static members but then fails to load the wrappers for my extension.

Has anyone found themselves in a similar situation and managed to figure it out?

Hi aviros,

A first thought, for your use case I think the alternative orekit python bridge implementation using jpype might be a project to have a look at. It uses JPype instead of JCC for interfacing the JVM but does it in a different way so it dynamically interfaces the JVM, no pre-compiled classes. This has several advantages, but also disadvantages - the main is that one cannot subclass pure java classes. Note that it is possible to use interfaces which enables a large part of what is needed (see examples in repo). It is less tested than the JCC wrapped version but has worked in some smaller projects i’ve done.

See the package and some examples at Files · master · Petrus Hyvönen / orekit_jpype · GitLab

Will try to think on the JCC stuff, but have not tried to build separate packages and integrate them with shared, only in a common build like the inclusion of rugged into orekit. As a test, have you tried the conda package and just including your additions as a separate jar, adding them to the build.sh/build.bat scripts? That “should” work :slight_smile: As that conda is a somewhat controlled environment it is easier to start there I think.

Regards
/Petrus

Hi!

Thanks for the suggestion. I did manage to make it work with JCC, but JPype definitely looks like the much better option after how painful this was to make work. As you mention, adding the jar with the new classes in the conda package scripts works, but due to my use case, I have to compile the classes in a different package, so that is not possible (plus, it makes code iteration much slower because Orekit is pretty slow to compile).

In case anyone else needs to go through the double extension process as well:

  1. You have to modify the JCC python extension itself, specifically jcc/python.py, the line where the sources are picked needs to change from:
    sources = ['JObject.cpp', 'JArray.cpp', 'functions.cpp', 'types.cpp']"
    to:
    if len(imports) == 0:
    sources = ['JObject.cpp', 'JArray.cpp', 'functions.cpp', 'types.cpp']
    else:
    sources = []
    This allows extensions to share JObject and JArray python objects without which you can’t call a function in module B with an object with a class defined in module A.
  2. You need to compile the orekit python extension as a shared JCC module, modifying the build.sh of the conda package to add --shared to it.
  3. You need to compile the second extension with --import orekit and --shared.

With both extensions installed, next is initalization. Simply calling initVM() on both extensions will crash, so instead you have do something like the following:
import orekit, java_extension

orekit.initVM(
vmargs="-XX:+UseParallelGC",
classpath=os.pathsep.join([orekit.CLASSPATH, java_extension.CLASSPATH]),
)
java_extension.initVM(classpath="")

This ensures everything is initialized. Missing initVM means static members of classes do not get initialized, but the classpath can only be passed in one of the two extensions.

Notice the use of the parallel GC for java, this is because on Java 17 the default GC results in crashes/segmentation faults when using JCC due to the extensive use of finalizers in the code.

Hopefully this helps whoever needs this, or convinces people they should try something else.

Interesting, thanks for sharing! That is indeed some work behind to get it to function…