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 |
|---|---|---|---|
|
|
|
M (Msun/kg) |
|
|
|
q |
|
|
|
η |
|
|
|
ℳ (Msun/kg) |
|
|
|
χ_eff |
|
|
|
χ_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 |
|---|---|
|
\(M = m_1 + m_2\) |
|
\(q = m_2/m_1\) |
|
\(\eta = m_1 m_2/M^2\) |
|
\(\mathcal{M} = (m_1 m_2)^{3/5}/M^{1/5}\) |
|
Effective inspiral spin |
|
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.
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 |
|---|---|
|
\(M = m_1 + m_2\) |
|
\(q = m_2/m_1\) |
|
\(\eta = m_1 m_2/M^2\) |
|
\(\mathcal{M} = (m_1 m_2)^{3/5}/M^{1/5}\) |
|
Effective inspiral spin |
|
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.