Language interfaces

Puddin’s core library is written in Rust and exposed to other languages through thin binding layers. All interfaces accept and return values in SI units (kilograms for mass, radians for angles). Unit conversion helpers are provided for the Python interface.


Command line

Install the puddin CLI with Cargo:

cargo install --git https://github.com/transientlunatic/puddin puddin-cli
# or, from a local checkout:
cargo install --path crates/puddin-cli

Pre-built binaries for Linux, macOS, and Windows are attached to each GitHub Release.

Usage

Masses default to solar masses (M☉); use --si to switch to kilograms. Output is a bare number on stdout — suitable for shell pipelines. Add --verbose (-v) for a labelled result with units.

# Chirp mass of a 30+30 Msun binary
puddin chirp-mass 30 30
# → 26.116516898883617

puddin chirp-mass 30 30 --verbose
# → chirp_mass = 26.116516898883617 Msun

# Spin parameters
puddin chi-eff 30 30 0.5 0.5 0.0 0.0    # both aligned at 0.5 → 0.5
puddin chi-p   30 15 0.8 0.3 0.4 1.2

# Masses in kilograms
puddin --si chirp-mass 5.97e30 5.97e30

# Compose with standard Unix tools
MC=$(puddin chirp-mass 30 30)
echo "Mc = $MC Msun"

Subcommands

Subcommand

Aliases

Arguments

Output

total-mass

m

m1 m2

M (Msun/kg)

mass-ratio

q

m1 m2

q

sym-mass-ratio

eta

m1 m2

η

chirp-mass

mc

m1 m2

ℳ (Msun/kg)

chi-eff

chieff

m1 m2 a1 a2 t1 t2

χ_eff

chi-p

chip

m1 m2 a1 a2 t1 t2

χ_p

t1, t2 are tilt angles in radians. a1, a2 are dimensionless spin magnitudes in [0, 1]. Both spin subcommands require m1 m2.


Python

Install from PyPI:

pip install puddin

Optional extras:

pip install "puddin[pint]"   # pint unit support
pip install "puddin[jax]"    # JAX / JIT support

numpy arrays (SI)

import numpy as np
import puddin

MSUN = 1.988_416e30  # kg

m1 = np.full(1000, 30.0 * MSUN)
m2 = np.full(1000, 30.0 * MSUN)

mc  = puddin.chirp_mass(m1, m2)
eta = puddin.symmetric_mass_ratio(m1, m2)

astropy quantities

import astropy.units as u
import puddin

m1 = 30 * u.Msun
m2 = 30 * u.Msun

mc = puddin.chirp_mass(m1, m2)   # returns astropy Quantity in kg

pint quantities

import pint
ureg = pint.UnitRegistry()

m1 = 30 * ureg.solar_mass
mc = puddin.chirp_mass(m1, m1)

JAX

Puddin functions are usable inside jax.jit-compiled code via jax.pure_callback. Analytical custom_vjp rules are registered so that gradients flow through correctly.

import jax
import jax.numpy as jnp
import puddin

MSUN = 1.988_416e30

@jax.jit
def log_chirp_mass(m1, m2):
    return jnp.log(puddin.chirp_mass(m1, m2))

m = jnp.array(30.0 * MSUN)
val, grad = jax.value_and_grad(log_chirp_mass)(m, m)

JavaScript / TypeScript

Install from npm:

npm install puddin-wasm

The package is built with wasm-pack and ships TypeScript declaration files. It targets modern ES module bundlers (Vite, esbuild, webpack 5, Rollup).

Vectorised API (Float64Array)

All core functions accept and return Float64Array for batch processing:

import { chirp_mass, symmetric_mass_ratio, MSUN } from 'puddin-wasm/puddin';

const m1 = new Float64Array([30 * MSUN, 10 * MSUN]);
const m2 = new Float64Array([30 * MSUN,  5 * MSUN]);

const mc  = chirp_mass(m1, m2);
const eta = symmetric_mass_ratio(m1, m2);

Scalar convenience functions

The TypeScript wrapper layer provides *_scalar() helpers that accept and return plain number, useful for single-event interactive work:

import { chirp_mass_scalar, chi_eff_scalar, MSUN } from 'puddin-wasm/puddin';

const mc = chirp_mass_scalar(30 * MSUN, 30 * MSUN);

const xe = chi_eff_scalar(
  30 * MSUN, 30 * MSUN,  // m1, m2
  0.5, 0.3,              // a1, a2
  0.2, 1.1               // tilt1, tilt2 (radians)
);

Available functions

Function

Description

total_mass / total_mass_scalar

\(M = m_1 + m_2\)

mass_ratio / mass_ratio_scalar

\(q = m_2/m_1\)

symmetric_mass_ratio / symmetric_mass_ratio_scalar

\(\eta = m_1 m_2/M^2\)

chirp_mass / chirp_mass_scalar

\(\mathcal{M} = (m_1 m_2)^{3/5}/M^{1/5}\)

chi_eff / chi_eff_scalar

Effective inspiral spin

chi_p / chi_p_scalar

Effective precession spin (\(m_1 \geq m_2\) required)


Julia

The Julia interface uses ccall to call into a Rust shared library (libpuddin_julia.so / .dylib / .dll). The C API is scalar only — Julia’s native broadcasting handles arrays idiomatically without any extra wrapping.

Note on distribution: The recommended approach for a registered Julia package with binary dependencies is a companion JLL package created with BinaryBuilder.jl. The instructions below describe the development workflow from source.

Build the shared library

cargo build --release -p puddin-julia
# → target/release/libpuddin_julia.{so,dylib,dll}

Add the package

From the Julia REPL, with the monorepo checked out:

import Pkg
Pkg.develop(path="bindings/julia")

Usage

using Puddin

const MSUN = Puddin.MSUN   # 1.988_416e30 kg

# Scalar
mc = chirp_mass(30.0 * MSUN, 30.0 * MSUN)   # ≈ 26.1 M☉ in kg

# Vectorised via Julia broadcasting — no extra work needed
m1 = rand(1000) .* 50.0 .* MSUN
m2 = rand(1000) .* 50.0 .* MSUN

mc_arr  = chirp_mass.(m1, m2)
eta_arr = symmetric_mass_ratio.(m1, m2)

xe_arr  = chi_eff.(m1, m2,
                   fill(0.5, 1000), fill(0.3, 1000),
                   fill(0.2, 1000), fill(1.1, 1000))

Available functions

Function

Description

total_mass(m1, m2)

\(M = m_1 + m_2\)

mass_ratio(m1, m2)

\(q = m_2/m_1\)

symmetric_mass_ratio(m1, m2)

\(\eta = m_1 m_2/M^2\)

chirp_mass(m1, m2)

\(\mathcal{M} = (m_1 m_2)^{3/5}/M^{1/5}\)

chi_eff(m1, m2, a1, a2, tilt1, tilt2)

Effective inspiral spin

chi_p(m1, m2, a1, a2, tilt1, tilt2)

Effective precession spin (\(m_1 \geq m_2\))

All masses in kg, all angles in radians


Rust

Add to Cargo.toml:

[dependencies]
puddin = "0.1"
uom    = { version = "0.36", features = ["f64", "si"] }

All public functions use uom quantity types for compile-time dimensional analysis:

use puddin::binary::{chirp_mass, chi_eff, MSUN};
use uom::si::f64::Mass;
use uom::si::mass::kilogram;

let m = Mass::new::<kilogram>(30.0 * MSUN);
let mc = chirp_mass(m, m);

// Spin parameters (dimensionless f64)
let xe = chi_eff(m, m, 0.5, 0.3, 0.2, 1.1);

Attempting to pass a length where a mass is expected is a compile error — no runtime checks needed.


C

The bindings/julia crate produces a standard C shared library. Include bindings/julia/include/puddin.h to get the function declarations and the PUDDIN_MSUN constant.

# Build the library
cargo build --release -p puddin-julia

# Compile and link your program
gcc example.c \
    -I bindings/julia/include \
    -L target/release -lpuddin_julia \
    -Wl,-rpath,$(pwd)/target/release \
    -o example -lm
#include "puddin.h"
#include <stdio.h>

int main(void) {
    double m1 = 30.0 * PUDDIN_MSUN;
    double m2 = 30.0 * PUDDIN_MSUN;
    printf("Chirp mass: %.4f Msun\n", puddin_chirp_mass(m1, m2) / PUDDIN_MSUN);
    return 0;
}

See examples/c/example.c in the repository for a complete runnable example with a Makefile.


C++

puddin.h includes an extern "C" guard, so the header is directly usable from C++ without modification.

g++ example.cpp \
    -I bindings/julia/include \
    -L target/release -lpuddin_julia \
    -Wl,-rpath,$(pwd)/target/release \
    -o example
#include "puddin.h"
#include <iostream>

int main() {
    constexpr double MSUN = PUDDIN_MSUN;
    double m1 = 30.0 * MSUN, m2 = 30.0 * MSUN;
    std::cout << "Chirp mass: " << puddin_chirp_mass(m1, m2) / MSUN << " Msun\n";
}

See examples/cpp/example.cpp in the repository for the full example.


Fortran

Fortran 2003+ iso_c_binding maps directly onto the C ABI. No shim layer is needed — declare the interfaces with bind(C) and call them like normal Fortran procedures.

use iso_c_binding, only: c_double

interface
    function puddin_chirp_mass(m1, m2) bind(C, name="puddin_chirp_mass")
        import c_double
        real(c_double), value :: m1, m2
        real(c_double)        :: puddin_chirp_mass
    end function
end interface

real(c_double), parameter :: MSUN = 1.988416e30_c_double
print *, puddin_chirp_mass(30*MSUN, 30*MSUN) / MSUN  ! ~26.1
gfortran example.f90 \
    -L target/release -lpuddin_julia \
    -Wl,-rpath,$(pwd)/target/release \
    -o example

See examples/fortran/example.f90 in the repository for the full example.


Go

Go’s cgo interface handles the C ABI transparently. Set the #cgo directives for include and library paths, then import the header and call functions as if they were Go functions prefixed with C..

// #cgo CFLAGS:  -I../../bindings/julia/include
// #cgo LDFLAGS: -L../../target/release -lpuddin_julia -Wl,-rpath,../../target/release
// #include "puddin.h"
import "C"
import "fmt"

func main() {
    m := C.double(30.0 * C.PUDDIN_MSUN)
    fmt.Printf("Chirp mass: %.4f Msun\n", float64(C.puddin_chirp_mass(m, m)) / float64(C.PUDDIN_MSUN))
}
cd examples/go && go run example.go

See examples/go/example.go in the repository for the full example.


R

The R package wraps the C ABI via a thin .C()-compatible shim compiled as part of the package. All public functions are vectorised with Vectorize().

Installation (development, from monorepo)

# Build the shared library first
cargo build --release -p puddin-julia

# Install the R package
R CMD INSTALL bindings/r

Usage

library(Puddin)

# Scalar
chirp_mass(30 * MSUN, 30 * MSUN) / MSUN   # ~26.1

# Vectorised — works automatically
m1 <- c(30, 20, 10) * MSUN
m2 <- c(30, 20,  5) * MSUN
chirp_mass(m1, m2) / MSUN

# Spin parameters
chi_eff(30*MSUN, 30*MSUN, 0.5, 0.5, 0, 0)   # 0.5

Note on CRAN distribution: producing a CRAN package with a compiled dependency requires either a companion r2u / CRAN binary or distributing pre-built binaries via a system dependency. The current package is suitable for direct installation from source.


MATLAB

Use MATLAB’s loadlibrary / calllib to load the shared library directly. No wrapper code is needed — the C header is loaded at runtime.

loadlibrary('libpuddin_julia', 'bindings/julia/include/puddin.h', 'alias', 'puddin');

MSUN = 1.988416e30;
mc   = calllib('puddin', 'puddin_chirp_mass', 30*MSUN, 30*MSUN) / MSUN;
fprintf('Chirp mass: %.4f Msun\n', mc);

unloadlibrary('puddin');

For batch processing, arrayfun provides a vectorised wrapper:

masses_sun = 10:10:50;
mc = arrayfun(@(m) calllib('puddin', 'puddin_chirp_mass', m*MSUN, m*MSUN)/MSUN, masses_sun);

See examples/matlab/example.m in the repository for the full example.