Metadata-Version: 2.4
Name: orekit_jpype
Version: 12.2.1.1
Summary: A space dynamics library. This package is a jpype wrapping of the original orekit java code.
Project-URL: source, https://gitlab.orekit.org/orekit/orekit_jpype/-/tree/master
Project-URL: homepage, https://www.orekit.org/
Project-URL: forum, https://forum.orekit.org/c/orekit-python-wrapper-usage/17
Author-email: Petrus Hyvönen <petrus.hyvonen@gmail.com>
Maintainer-email: Clément Jonglez <clement@jonglez.space>
License: License :: OSI Approved :: Apache Software License
License-File: LICENSE.txt
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.7
Requires-Dist: jpype1==1.5.0
Requires-Dist: numpy
Provides-Extra: tests
Requires-Dist: coverage; extra == 'tests'
Requires-Dist: pytest; extra == 'tests'
Requires-Dist: pytest-cov; extra == 'tests'
Description-Content-Type: text/markdown

# orekit_jpype

[![PyPI - Version](https://img.shields.io/pypi/v/orekit-jpype.svg)](https://pypi.org/project/orekit-jpype)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/orekit-jpype.svg)](https://pypi.org/project/orekit-jpype)
[![Pipeline passing](https://gitlab.orekit.org/orekit/orekit_jpype/badges/master/pipeline.svg?ignore_skipped=true)](https://gitlab.orekit.org/orekit/orekit_jpype/-/pipelines)
[![coverage report](https://gitlab.orekit.org/orekit/orekit_jpype/badges/master/coverage.svg)](https://gitlab.orekit.org/orekit/orekit_jpype/-/commits/master)

An alternative implementation of orekit python wrapper based on Jpype and stubs generated by stubgenj. The interface is largely similar to the original JCC wrapped orekit version which is a more capable wrapper as it can subclass java classes, which Jpype cannot. The Jpype version is however easy to install, use and for most purposes sufficient.

Orekit (https://www.orekit.org/) is a low level library for space flight dynamics and is written in Java. The java API is located at: https://www.orekit.org/site-orekit-latest/apidocs/index.html

The original Orekit python wrapper based on JCC is located at:
https://gitlab.orekit.org/orekit-labs/python-wrapper/-/wikis/home

Please see the [repository](https://gitlab.orekit.org/orekit/orekit_jpype) example notebooks and test cases for usage, as well as jpype documentation https://jpype.readthedocs.io/en/latest/index.html


# Installation

* Requirements:
  * JDK installed on your system (as Orekit is based on Java)
    * Or alternatively [jdk4py](https://pypi.org/project/jdk4py/) can be used to provide JDK using pip, see usage instructions below
  * Python >= 3.7 . Orekit Jpype is only tested for Python versions >= 3.9 because the CI pipeline depends on `jdk4py`, but Python 3.7 and 3.8 should work.
  * `pip`
  * Recommended: a `venv`

To install from PyPi:
```bash
pip install orekit-jpype
```

To install from source:
```bash
git clone https://gitlab.orekit.org/orekit/orekit_jpype.git
pip install . -vv
```
Or alternatively:
```bash
pip install git+https://gitlab.orekit.org/orekit/orekit_jpype.git
```

To install the required orekit data from pip:
```bash
pip install git+https://gitlab.orekit.org/orekit/orekit-data.git
```


# usage

See the example notebooks (`examples` folder) and the package `test` folder (only available in the source distribution or in the [git repo](https://gitlab.orekit.org/orekit/orekit_jpype)) for examples.

## Usage difference from JCC based wrapper

### Casting
In the JCC version there is sometimes needed a .cast_() method to cast the object to the correct type. This is not needed in the JPype version.

### Implementation of Orekit interfaces
In the JCC version, the interfaces are implemented as special classes named PythonClassName that is then subclassed in Python. In the JPype version, the interfaces are implemented as python classes and marked with decorators, @JImplements and @JOverride, see https://jpype.readthedocs.io/en/latest/userguide.html#proxy-method-overloading

### Subclassing of Orekit abstract classes
In the JCC version of Orekit it is possible to subclass classes from the set of PythonClassName classes. This is not possible in the Jpype version of Orekit, which is limited to implementation of interfaces only. But instead it is possible to subclass abstract classes in Java, compile the JAR, and add the JAR to Orekit JPype, see section below.

### Using custom JARs

You can add your own JARs to Orekit Jpype. For that, you can pass a list of classpaths via the argument `additional_classpaths` to the method `orekit_jpype.initVM`. An example can be found in the test case `tests-jvm/orekit_jpype_test_init_vm_with_extra_jars.py`.

### Java imports require the JVM to be started

As jpype loads the Java classes from JAR files on-the-fly, it cannot know which Java packages/classes exist before the JVM is started. Therefore the Java packages/classes can only be imported after the JVM is started.

Hence, the following will not work:

```python
from java.util import ArrayList
from org.orekit.data import DirectoryCrawler, DataContext
import orekit_jpype as orekit

if __name__ == '__main__':
    orekit.initVM()
```

Instead you should do the following:

```python
import orekit_jpype as orekit
orekit.initVM()

from java.util import ArrayList
from org.orekit.data import DirectoryCrawler, DataContext

if __name__ == '__main__':
    # do stuff
```

The imports after the `orekit.initVM()` call will probably trigger PEP8 E402 warnings from `flake8` or `pylint`. As a workaround, you can add the following comment after the import to ignore these warnings:

```python
from java.util import ArrayList  # noqa: E402
```

### The JVM cannot be restarted

If you call the `jpype.shutdownJVM()` method, you won't be able to restart the JVM afterwards by calling `jpype.startJVM()` or `orekit_jpype.initVM()`. This is a known limitation of the JNI used by `jpype`: https://github.com/jpype-project/jpype/issues/959

### No JDK is provided

Installing Orekit JCC via conda also installed an OpenJDK8. Instead, when installing Orekit Jpype via pip, no JDK is installed, so it's up to the user to install JDK on their machine.

Another solution could be installing a JDK with pip thanks to the [jdk4py project](https://pypi.org/project/jdk4py/), and passing the path to the libjvm file to jpype by using the following syntax (taken from the test case `tests-jvm/orekit_jpype_test_init_vm_jdk4py.py`):

```python
import orekit_jpype as orekit
import os
import jdk4py

libjvm_path = os.path.join(jdk4py.JAVA_HOME, "lib", "server", "libjvm.so")

orekit.initVM(jvmpath=libjvm_path)
```

Alternatively, you can just set the `JAVA_HOME` environment variable from `jdk4py`:

```python
import orekit_jpype as orekit
import os
import jdk4py

os.environ["JAVA_HOME"] = str(jdk4py.JAVA_HOME)

orekit.initVM()
```

However, using `jpype` together with `jdk4py` seems to cause trouble in Windows: https://github.com/jpype-project/jpype/issues/1151


# Packaging a project with `pyinstaller`

This repository contains hooks for `pyinstaller` so that your project relying on Orekit can be packaged into an executable. No additional arguments are needed to `pyinstaller` thanks to the hooks, as long as `orekit_jpype` is installed in your Python packages.

## Packaging the orekit data folder or library

### Using the orekitdata Python library

If you are using the [orekit data repository as a Python library](https://gitlab.orekit.org/orekit/orekit-data#notes-for-orekit-python-users), it already contains hooks for `pyinstaller` so you don't have to pass any argument, the orekit data folder will be automatically collected:

```bash
pyinstaller <your main Python script>
```

This will create a folder `dist/` containing your executable.

### Locally managed orekit data folder

If the `orekit-data` folder is located in the same folder as your main Python script, you can for instance use the following syntax to load the orekit data in your Python code:

```python
from orekit_jpype.pyhelpers import setup_orekit_data
dirpath = os.path.dirname(os.path.abspath(__file__))
setup_orekit_data(filenames=os.path.join(dirpath, "orekit-data"), from_pip_library=False)
```

Then you can use the following option to package your `orekit-data` folder in your executable.

```bash
pyinstaller --add-data orekit-data/*:./orekit-data/ <your main Python script>
```

This will create a folder `dist/` containing your executable.

## For developers: testing the hook

```bash
python -m PyInstaller.utils.run_tests --include_only orekit_jpype._pyinstaller
```

This will package a minimal executable containing orekit_jpype, orekitdata and run the test case `test/OrekitDataLoadOnlyLibTest.py` to test that everything works as expected.
TODO: include this test procedure in a CI pipeline.


# Notes on differences between the original JCC-based Orekit Python wrapper and this jpype-based wrapper

The biggest difference between JCC and Jpype is that JCC is a static wrapping, creating c++ classes that are pre-defined when building the package, while JPype is dynamic and investigating the java machine on-the-fly.

Each model has advantages and disadvantages.


### JCC Pro's
- Can (probably) implement anything that is doable in Java in Python using the special classes (subclassing, interfaces)
- Stable when a compiled version has been created
- Well tested with orekit
- Good support for JCC but mainly one person who maintains and understand the code.
  - Easier to add custom java code to the system

### JCC Con's
- Special classes for subclassing.
- Special classes for interfaces
- Casting of types for classes with multiple inheritance
- Can be tricky to get it to compile (but when it does it is stable)
- Poor docstring support
- Very hard to understand the inner working of the wrapping code
- Need to recompile to add user java code pieces

### JPype Pro's
- Support for docstrings, reasonable rendering of javadoc
- Automatic adaptors enabling things like automatic translation between python datetime and AbsoluteDate.
- Well documented
- Medium hard to understand the wrapping code
- The wrapping is separate from the orekit part, maybe less issues with compiling (using conda)
- Easy to add user java code pieces as separate jars
- Direct mapping of numpy arrays to java arrays

### JPype Cons
- No subclassing of Java classes (Abstract classes in orekit cannot be used)
- Issues with restarting the JVM
- The unknown issues we don't know.
- Seems to have a slight performance hit (~10% increase in time in some very basic tests)



# Development
## Creating a venv

Using a Python virtual environment is highly recommended, do not install packages in your global python environment. To do so (in this example the venv will be located in the root of this git folder, but no worries, it is git-ignored):

```bash
python3 -m pip install virtualenv
python3 -m venv .orekitvenv
```

Activate the venv using:
```bash
source .orekitvenv/bin/activate
```

## Downloading JAR files and generating Python stubs

When a new version of one of the Java dependencies is available, the new JAR files must be downloaded and the stubs re-generated:

Downloading JARs requires having `maven` installed on your system.
And to generate stub files, the `stubgenj` package is required, it should be installed via pip together with the other dependencies of `orekit_jpype` (https://gitlab.cern.ch/scripting-tools/stubgenj)

* change the version of the corresponding artifact in `pom.xml`
* The following Python script will download new JARs and re-generate the stubs:
```bash
python3 dl_jars_and_render_stubs.py
```

## Checklist before uploading a new package version

* Open a merge request from your private branch into the `master` branch, containing:
  * your changes, for instance newer versions of the Orekit JARs, changes to stubs, changes to test suites, etc.
  * bump the package version in `pyproject.toml`.
    * The package version should follow the Orekit versions: for example if based on Orekit `12.0.2`, the `orekit_jpype` package version should start with `12.0.2`, for instance `12.0.2.0`
    * Follow the [Python version specifiers conventions](https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers)
* The merge request should be merged only if the CI pipeline passes. In particular, major releases of Orekit introduce breaking changes so it's likely that unit tests will fail and need to be modified
* After your branch is merged into the `master` branch, switch to the `master` branch and create a tag with the same name as the package version, for example `12.0.2.0`
* Stay in the `master` branch in the same tagged commit, and follow the instructions below to build and upload the wheel to PyPi

## Building wheel

* Make sure you have the latest version of PyPA’s [build](https://packaging.python.org/en/latest/key_projects/#build) installed:
```bash
python3 -m pip install --upgrade build
```
* To build the wheel:
```bash
python3 -m build
```

## Uploading wheel

Make sure you have the latest version of `twine`:
```bash
python3 -m pip install --upgrade twine
```

### Uploading wheel to test PyPi server

* To upload to the PyPi test server:
```bash
python3 -m twine upload --repository testpypi dist/*
```
* To try to install the newly uploaded wheel from the PyPi test server (in this example for version `12.0.2.0`):
```bash
python3 -m pip install -i https://test.pypi.org/simple/ orekit-jpype==12.0.2.0
```

### Uploading wheel to the real PyPi server

* To upload the wheel to PyPi:
```bash
python3 -m twine upload dist/*
```
* To install `orekit_jpype` from PyPi:
```bash
python3 -m pip install --upgrade orekit-jpype
```
