Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/keluaa/juinko

Embedding Julia into the JVM using Kotlin
https://github.com/keluaa/juinko

Last synced: about 1 month ago
JSON representation

Embedding Julia into the JVM using Kotlin

Awesome Lists containing this project

README

        

# JuInKo

[![Release](https://jitpack.io/v/Keluaa/JuInKo.svg)](https://jitpack.io/#Keluaa/JuInKo)

JuInKo (for *Julia In Kotlin*) allows you to embed Julia into your
Kotlin/Java project using [JNA](https://github.com/java-native-access/jna)
bindings.
The API is made to be very close to the Julia C API, with some common patterns
made easier to write thanks to Kotlin.

Currently only supports the Julia version 1.9 and above.

Bindings for Julia 1.7 to 1.8 can be used but without multi-threading support.

## Adding JuInKo to your project

```kotlin
repositories {
mavenCentral()
maven("https://jitpack.io")
}

dependencies {
implementation("com.github.Keluaa:JuInKo:1.0")
}
```

## Usage

### Loading Julia

`JuliaPath` attempts 3 methods to find the Julia libraries, in this order:
- the `"juinko.julia_path"` system property
- the `JULIA_BINDIR` environment variable
- the `JULIA` environment variable
- or from the command line (and therefore the `PATH`): `julia -E "unsafe_string(ccall(:jl_get_libdir, Cstring, ()))"`

Then `JuliaLoader.get()` allows to access and initialize the `Julia` interface instance
corresponding to the library found by `JuliaPath`.
Depending on the `JuliaVersion` loaded, not all functions will be available.

`JuliaVersion` is set to the currently loaded version of Julia, and can be compared
with as you would with a `VersionNumber` in Julia.

Both `libjulia` and `libjulia-internal` are loaded, but not all of their functions are
made available: they are implemented as they are needed.
Do not hesitate to create a new issue for this, most functions are simple to implement.

Julia options must be set before `JuliaLoader.get()` initializes Julia.
The workflow should look like this:

```kotlin
JuliaLoader.loadLibrary()
val jloptions = JuliaLoader.getOptions()
// Set the options here...
val jl = JuliaLoader.get()
```

### The `Julia.kt` interface

It serves as the equivalent of `julia.h` and `julia-internal.h` (and a few other things).
It is version independent: functions/variables defined in 1.10 will also be defined if
the loaded Julia version is in 1.9, but will raise a `VersionException` if called.

Common global C variables are directly available (e.g. `jl_main_module`, `jl_nothing`, etc...),
and more specific ones can be accessed through `Julia::getGlobalVar`.

Some common macros defined in `julia.h` where transformed into functions.

### Garbage Collector Management

`GCStack` provides a context to manage a GC stack, without the need to use any
`JL_GC_PUSH` or `JL_GC_POP`:

```kotlin
val jl = JuliaLoader.get()
val result = GCStack(jl, 2).use { stack ->
stack[0] = jl.jl_box_int64(1)
stack[1] = jl.jl_box_int64(2)
val resultBoxed = jl.jl_call(jl.getBaseObj("+"), stack.array(), 2)
jl.jl_unbox_int64(resultBoxed!!)
}
println(result) // 3
```

`GlobalMemory` (accessible from any `Julia` instance) can be used to store any
value with a longer lifetime (with immutables being handled differently from mutables).

### Multi-threading

The Julia API can only be called from threads which are set up by Julia.
While the main JVM thread (which loaded the library) is one of those, any
other JVM thread might be unsafe.

As from Julia 1.9, `jl_adopt_thread` allows to set up any thread to use the Julia API.
The `Julia::runInJuliaThread` function will call `jl_adopt_thread` if needed, as well as
handle some GC shenanigans.

```kotlin
jl.runInJuliaThread {
val jl_tid = jl.jl_threadid()
val jvm_tid = Thread.currentThread().id
println("jl_tid: $jl_tid, jvm_tid: $jvm_tid")
}
```

### Exceptions

`Julia::exceptionCheck` encapsulates `jl_current_exception` to throw a `JuliaException`
with the message and traceback of the Julia exception (note: this involves doing allocations,
use it safely in a `GCStack` context).

It is impossible to have an equivalent of the `JL_TRY` and `JL_CATCH` macros in the JVM,
since they rely on the `setjmp` and `longjmp` C functions.

Any Julia API function calling `jl_error` will result in a JVM crash with
`"fatal: error thrown and no exception handler available"` as an error message.
Most C functions have a wrapper defined in `jlapi.c` which surrounds the call with a
try-catch block.
Use those instead or do your call through a `jl_call`.