Skip to main content

cuNIBS

pypi

cuNIBS computes the electric field induced by transcranial magnetic stimulation (TMS) in a tetrahedral head model. It solves the magneto-quasistatic finite-element problem on the GPU with first-order elements, magnetic dipole coil models, and a mixed-precision conjugate-gradient solve preconditioned by an aggregation-AMG V-cycle. The mesh and the AMG hierarchy stay on the GPU and are reused across coil placements, so a sweep pays the setup cost once.

It supports isotropic conductivity models, conductivity uncertainty quantification, coil-placement optimization, and NVIDIA GPUs.

Installation

python -m pip install cunibs

Requires Python 3.12 or later and an NVIDIA GPU. Wheels are published for x86-64 Linux and Windows, for CPython 3.12 through 3.14 including free-threaded 3.14. The install pulls the CUDA 13 toolkit wheels and cupy, so no system CUDA installation is needed, but the driver must support CUDA 13 (r580 or later).

Quickstart

from cunibs import Placement, Subject
from cunibs.coil import Coil, MAGSTIM_D70

subject = Subject.from_mesh("subject.msh")
coil = Coil.load(MAGSTIM_D70)

placement = Placement(
    center_mm=[0.0, 20.0, 80.0],
    handle_mm=[0.0, 70.0, 80.0],
    distance_mm=4.0,
)

result = subject.simulate(coil, placement, didt=1.0e6)

print(result.peak_magnE())
print(result.peak_location_mm())
print(result.focality(frac=0.5))

center_mm is the scalp target and handle_mm is a point in the positive handle direction. cuNIBS projects the target onto the scalp, builds the coil frame from the local surface normal, and offsets by distance_mm along that normal. A handle lying on the normal itself leaves the rotation about the normal undefined and is rejected.

didt is the stimulator's current rate of change in A/s. The field is linear in it. Each bundled coil carries its rated peak as coil.didt_max.

Input data

Head mesh

Subject.from_mesh reads binary Gmsh 2.2 files with first-order tetrahedra and an oriented scalp surface (tag 1005). Coordinates are read as millimetres. Volume tags select the built-in conductivities; unrecognized volume tags and surface triangles are dropped on load.

Generate individualized models with the SimNIBS CHARM pipeline:

charm subject_id T1w.nii.gz T2w.nii.gz

CHARM writes m2m_subject_id/subject_id.msh. A T1-weighted scan is enough, but a T2-weighted scan improves skull segmentation. Inspect the segmentation before simulating.

Conductivities follow the standard SimNIBS values:

Tag Tissue Conductivity (S/m) Source
1 White matter 0.126 Wagner et al. (2004)
2 Gray matter 0.275 Wagner et al. (2004)
3 Cerebrospinal fluid 1.654 Wagner et al. (2004)
4 Average bone 0.010 Wagner et al. (2004)
5 Scalp 0.465 Wagner et al. (2004)
6 Eye 0.500 Opitz et al. (2015)
7 Cortical bone 0.008 Opitz et al. (2015)
8 Cancellous bone 0.025 Opitz et al. (2015)
9 Blood 0.600 Gabriel et al. (2009)
10 Muscle 0.160 Gabriel et al. (2009)

Coil model

The package bundles the 25 validated coil models of Drakaki et al. (2022), available as constants in cunibs.coil and loaded with Coil.load. Dipole positions are in metres and moments in A m².

Convert a SimNIBS CCD coil to the HDF5 dipole format:

from pathlib import Path

from cunibs.coil import Coil, encode_ccd

encode_ccd(Path("coil.ccd"), Path("coil.h5"))
coil = Coil.load("coil.h5")

Sweeping placements

simulate takes one placement. iter_simulate streams many, reusing the assembled system and the AMG hierarchy:

placements = [
    Placement([0.0, 20.0, 80.0], [0.0, 70.0, 80.0]),
    Placement([20.0, 0.0, 80.0], [70.0, 0.0, 80.0]),
]

for result in subject.iter_simulate(coil, placements, didt=1.0e6):
    print(result.peak_magnE())

It is a generator: nothing is computed until you iterate, results come back in the order given, and each is freed as the loop advances. Peak memory is bounded by one block rather than by the sweep length. Wrap it in list() to collect everything, which is always safe for summaries.

Placements are solved in blocks that share one stiffness and hierarchy read per block. block_k sets the block width (default 8, 1 solves one at a time) and also caps peak memory when fields are retained. It is a throughput and memory knob only; the same placement returns the same field at any width, bit for bit.

for result in subject.iter_simulate(coil, placements, didt=1.0e6, block_k=4):
    ...

Many subjects

A Subject holds its solver context and AMG hierarchy on the GPU for its lifetime. Use the context manager (or subject.free()) to release that memory between subjects:

from pathlib import Path

for mesh_file in Path("subjects").glob("m2m_*/*.msh"):
    with Subject.from_mesh(mesh_file) as subject:
        result = subject.simulate(coil, placement, didt=1.0e6)
        ...  # collect results

Results

Every FieldResult carries summary, the gray-matter metrics:

result.peak_magnE()
result.focality(0.5)
result.summary["distribution"]["p99"]

The metric API reports the peak field, peak location, stimulated volume, field-weighted centre of gravity, and volume-weighted distribution statistics. peak_magnE() is the true maximum of |E|. Focality is measured against the volume-weighted 99.9th percentile instead, since on a tetrahedral mesh the true maximum is routinely set by a single sliver element at a tissue boundary: focality(0.5) is the volume with |E| at or above half that percentile.

Full-volume arrays are opt-in, because they are what makes a result large. On a 4M-tetrahedron model the three together are about 70 MB (E 69%, magnE 23%, v 8%); with none of them a result is about a kilobyte. Ask for what you need:

result = subject.simulate(coil, placement, didt=1.0e6, magnitude=True)

for result in subject.iter_simulate(
    coil, placements, magnitude=True, vectors=True, potential=True
):
    ...

result.magnE, result.E and result.v are None when not requested. Retaining magnitude also unlocks the two metrics a precomputed summary cannot answer, summary_for(region) for a non-default tissue and focality(frac) at an arbitrary fraction:

gray_matter = result.summary_for("gray_matter")
whole_model = result.summary_for("all")

Results are always NumPy. For device-resident fields, call cunibs.fem.solve_placements_block directly.

FieldResult contains:

Attribute Description Units
E Electric field per tetrahedron V/m
magnE Electric-field magnitude per tetrahedron V/m
v Electric scalar potential per node V
transform Coil-to-head affine matrix translation in mm
vols Tetrahedron volumes
tet_tags Volume tissue tags dimensionless
barycenters_mm Tetrahedron barycentres mm
didt Coil current rate of change A/s
recovery Which post-processing produced E/magnE/summary dimensionless
E_slots Recovered field per slot, with nodal=True V/m
slot_node Mesh node each slot belongs to, with nodal=True dimensionless
slot_tag Tissue tag each slot belongs to, or None when slots are nodes dimensionless
n_nodes Mesh node count, so nodal_field can size its output dimensionless

Save a result and its metric inputs to HDF5. Fields that were not retained are absent from the file and load back as None:

result.save("placement.h5")

from cunibs import FieldResult

loaded = FieldResult.load("placement.h5")

Field recovery

The raw finite-element field is constant over each tetrahedron. recovery= post-processes it by fitting a local patch around each node and interpolating from there, the same step SimNIBS applies before mapping a field to a surface or volume.

result = subject.simulate(coil, placement, didt=1.0e6, magnitude=True)

result.magnE  # |E| of the recovered field
result.recovery  # "harmonic"

raw = subject.simulate(coil, placement, didt=1.0e6, magnitude=True, recovery="raw")

E, magnE and summary all describe whichever field the mode selected. The raw field is not additionally retained.

recovery What it does
"harmonic" Recovers the potential in the space of harmonic polynomials and differentiates it. The default, and the most accurate mode at a tissue interface.
"raw" The per-tetrahedron field straight from the solve. Its peak is the largest element average rather than a value at a point, so it reads high and moves with mesh resolution.
"spr_tissue" Zienkiewicz-Zhu superconvergent patch recovery (Zienkiewicz and Zhu, 1992) with each patch restricted to one tissue. What SimNIBS's surface and volume overlays report, so use it when comparing against them. Its boundary rule is SimNIBS's too: wherever the cropped tissue ends, including at every tissue interface, each side takes its own one-sided volume-weighted average rather than a fit.
"spr_global" The same fit over patches spanning every incident tetrahedron regardless of tissue. Corresponds to SimNIBS's continuous=True.

E is discontinuous at a tissue boundary, since J_n = σE_n is continuous and the normal component of E jumps by the conductivity ratio. A single value per node cannot represent a two-valued field, so "spr_global" does not converge there however fine the mesh. "spr_tissue" averages instead of fitting there and converges at first order; "harmonic" fits and converges at roughly 1.7. On a reference head mesh 62% of nodes have a mixed-tissue patch and 90% of gray-matter tetrahedra touch one, so 81% of "spr_tissue"'s slots are volume averages.

Inside a region of constant conductivity the potential is harmonic, so the fit runs in the harmonic polynomials rather than all of them: 16 of the 20 cubics, dropping to 9 of the 10 quadratics on a patch too small to carry the cubic. That constraint is also what makes the fit well posed at an interface, where the same-tissue patch is a half-ball and cannot see the curvature normal to the flat side. The assumption is specific to this formulation, with the source outside the head; it would not carry to a problem with interior current sources.

Recovery costs one extra pass over the mesh per placement. Its patch weights are built once per subject on first use and reused across placements.

Nodal fields

A recovered field also exists on the mesh nodes, which interpolating onto a cortical surface needs:

result = subject.simulate(coil, placement, magnitude=True, nodal=True)

result.nodal_field()  # (n_nodes, 3) gray matter, NaN where it does not reach
result.nodal_field("csf")  # the CSF-side value at the same nodes

For the tissue-restricted modes the field lives on (node, tissue) slots rather than nodes, since it is two-valued at a boundary. nodal_field returns one row per mesh node carrying the requested tissue's value, and NaN where that tissue does not reach. region="all" is rejected for those modes rather than silently picking one of the two values. E_slots, slot_node and slot_tag expose the underlying slot arrays.

Conductivity uncertainty quantification

simulate_conductivity_uq runs a Monte Carlo analysis over tissue conductivities for one placement or a sequence, configured by a ConductivityUQConfig. Each tissue is an independent random variable with the nominal conductivity as its mean and a coefficient of variation you supply; draws are lognormal by default, which keeps conductivities positive. The result reports per-tetrahedron mean, standard deviation, and coefficient of variation of |E|.

from cunibs import ConductivityUQConfig, Placement, Subject
from cunibs.coil import Coil, MAGSTIM_D70

subject = Subject.from_mesh("subject.msh")
coil = Coil.load(MAGSTIM_D70)

config = ConductivityUQConfig(
    n_samples=500,
    tissue_cov={2: 0.15, 3: 0.05, 7: 0.35, 8: 0.35},
    seed=1,
)

uq_result = subject.simulate_conductivity_uq(coil, placement, config, didt=1.0e6)

print(uq_result.peak_mean_magnE())
print(uq_result.max_local_cov())

The result carries its summary the same way a FieldResult does. Pass moments=True to also retain the per-tetrahedron moment arrays; they are kept or dropped together, since the metrics need both the mean and the CoV and the third follows from those two. mean_magnE, std_magnE and cov_magnE then use the same tetrahedron ordering as FieldResult.magnE. iter_simulate_conductivity_uq streams a sequence of placements the same way iter_simulate does.

peak_mean_magnE and max_local_cov are metrics of the moment fields, not moments of a metric. For a nonlinear metric such as the peak or focality, the metric of the mean is not the mean of the metric over the ensemble. Use the per-draw arrays to characterize how a scalar is distributed across draws:

m1 = subject.roi([-45.0, -5.0, 25.0], radius_mm=5.0, region="gray_matter")

uq_result = subject.simulate_conductivity_uq(
    coil, placement, config, didt=1.0e6, record_rois={"M1": m1}
)

uq_result.roi_samples["M1"]  # (n_samples,) per-draw ROI mean |E| (V/m)
uq_result.peak_samples  # (n_samples,) per-draw gray-matter peak |E|
uq_result.focality_samples  # (n_samples,) per-draw stimulated volume (m³)
uq_result.peak_location_samples  # (n_samples, 3) per-draw peak location (mm)
uq_result.tissue_sensitivity("peak")  # variance share per tissue tag

tissue_sensitivity regresses the log of a per-draw scalar ("peak", "focality", or an ROI name) on the log conductivity draws. It is a first-order linear-in-log index on the i.i.d. ensemble, not a Saltelli Sobol estimate.

Results save to HDF5 with uq_result.save(path) and load with ConductivityUQResult.load(path).

Coil-placement optimization

cunibs.adm implements the Auxiliary Dipole Method (Gomez et al., 2021). A few one-time adjoint solves, reusing the forward AMG hierarchy, sample a reciprocity field on a regular grid. The target E-field of any placement is then a trilinear interpolation plus a dipole sum, with no further FEM solve. This evaluates candidates orders of magnitude faster than a forward solve each, and matches a forward solve at the optimum to a relative error of 4e-4.

import numpy as np

from cunibs import Subject, Target, adm
from cunibs.coil import Coil, MAGSTIM_D70

subject = Subject.from_mesh("subject.msh")
coil = Coil.load(MAGSTIM_D70)

# Omit `direction` to maximize |E| (three adjoint solves), or pass one to
# maximize a directional component.
target = Target(position_mm=[-45.0, -5.0, 25.0], region="gray_matter")

# Candidate scalp positions to search (each is projected onto the scalp).
centers = np.array([[x, y, 80.0] for x in range(-30, 31, 5) for y in range(-30, 31, 5)])

result = adm.optimize(subject.context, coil, target, centers)

result.best_objective  # peak |E| at the target (V/m)
result.best_center_mm  # optimal scalp position
result.best_angle_rad  # optimal in-plane rotation

The in-plane rotation is optimized in closed form: the target E-field is a rigid rotation of the coil, so each component is band-limited in the angle. It is sampled at n_samples angles, trigonometrically interpolated, and |E(θ)|² is maximized analytically.

For repeated queries against a fixed target, build the reciprocity field once and reuse it:

recip = adm.build_reciprocity(subject.context, coil, target, centers)
E = adm.evaluate(recip, coil, placements, didt=1.0e6)  # (P, D) target E-vectors

Reproducibility

A placement's field is a function of the mesh, the coil, the placement, didt, the solve tolerance and the recovery mode, and of nothing else. It does not depend on block_k, on which other placements shared its block, or on where it fell in the sweep. Repeating a run reproduces it bitwise. Splitting a sweep across calls, resuming an interrupted one, or retuning block_k for a different GPU all leave the numbers unchanged. A conductivity-UQ draw depends on its own conductivity vector and not on the ensemble size or the other draws, so the same seed reproduces the same draws at any n_samples.

Results can still differ across GPU architectures, CUDA versions, compiler versions, and dependency versions.

Citation

No archival citation is provided yet. For reproducible academic use, cite the software by name, author, version, and Git commit, and archive the exact input mesh, coil model, and placement parameters used.

References

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cunibs-0.3.0.tar.gz (1.3 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

cunibs-0.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.4 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

cunibs-0.3.0-cp314-cp314-win_amd64.whl (3.5 MB view details)

Uploaded CPython 3.14Windows x86-64

cunibs-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.4 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

cunibs-0.3.0-cp313-cp313-win_amd64.whl (3.5 MB view details)

Uploaded CPython 3.13Windows x86-64

cunibs-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.4 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

cunibs-0.3.0-cp312-cp312-win_amd64.whl (3.5 MB view details)

Uploaded CPython 3.12Windows x86-64

cunibs-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.4 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file cunibs-0.3.0.tar.gz.

File metadata

  • Download URL: cunibs-0.3.0.tar.gz
  • Upload date:
  • Size: 1.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cunibs-0.3.0.tar.gz
Algorithm Hash digest
SHA256 98ec3483590b1eb6558df5c4d521143748b52bc68e58644f33c7ab5a551496d6
MD5 598adb31ee964159fa7afaec0a8dee34
BLAKE2b-256 e57c422b45f4bde8112d3a9f585b5de1f64e11c17a5a4313f50f25020125bf70

See more details on using hashes here.

Provenance

The following attestation bundles were made for cunibs-0.3.0.tar.gz:

Publisher: wheels.yml on vcubiomag/cunibs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cunibs-0.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for cunibs-0.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 13352175e5f470eefe1c5adddfb7dcd7531d20bb666412fb16b09858ddc01e3e
MD5 2bb28858ab97d8d64fd16b13273dc064
BLAKE2b-256 e0a00d4d5ed8285525aaa7361dd76c502e4826314242fe43af588bf87b902e0f

See more details on using hashes here.

Provenance

The following attestation bundles were made for cunibs-0.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on vcubiomag/cunibs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cunibs-0.3.0-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: cunibs-0.3.0-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 3.5 MB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cunibs-0.3.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 2a85cc2edb021b8630c1251d57d87347feeb24adb96ca6fede0806c7a9c2d0f1
MD5 a3b952260e4abf9da7c734a07b254db0
BLAKE2b-256 abd71a1fea5f47c0bdada81973b6b5a97d95848ff382b0c0cd0ef329199675cb

See more details on using hashes here.

Provenance

The following attestation bundles were made for cunibs-0.3.0-cp314-cp314-win_amd64.whl:

Publisher: wheels.yml on vcubiomag/cunibs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cunibs-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for cunibs-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 02e707c27a6a9dba57d256f7e7602d02b594199ed3d3a9530a055fe8f488b339
MD5 16528f778f01fde688274e089ab45f6c
BLAKE2b-256 0033e4fac6f17ae26650c57d0c71bc4a3397ad5040ed0f124e9200779c072cd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for cunibs-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on vcubiomag/cunibs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cunibs-0.3.0-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: cunibs-0.3.0-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 3.5 MB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cunibs-0.3.0-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 e1d00013f23d0a71466aab295405060de5cbc5577b7d6c2e99db87e9e9d2c25e
MD5 cada163093a0572cbd450d07c6b94332
BLAKE2b-256 82187f836f054bd6a60bbf086055b476b6e4b59ed3f08080558caf3e74f08011

See more details on using hashes here.

Provenance

The following attestation bundles were made for cunibs-0.3.0-cp313-cp313-win_amd64.whl:

Publisher: wheels.yml on vcubiomag/cunibs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cunibs-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for cunibs-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7c9c0bd7f75f4c1199fd117560634df5b7f73a27f62f47aa58e0f8c1ca039167
MD5 aca3b97449ed2394bd847a1f50cc1dc0
BLAKE2b-256 d61f277937bfbd2c0c75893f75602a0f7d6d6e9213a41ed3411a87c6b22d2307

See more details on using hashes here.

Provenance

The following attestation bundles were made for cunibs-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on vcubiomag/cunibs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cunibs-0.3.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: cunibs-0.3.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 3.5 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cunibs-0.3.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 addc9a9407d5aee2d1fe4e5d1e2bf5ecd48d2085d82662020459936688f1ec00
MD5 ce9cb46d21a28f73603f248fa0271657
BLAKE2b-256 370806749c840ca0fcc9cd08d4b88a8ecb43cef9e0e85799fa6abe0fe8ebb38c

See more details on using hashes here.

Provenance

The following attestation bundles were made for cunibs-0.3.0-cp312-cp312-win_amd64.whl:

Publisher: wheels.yml on vcubiomag/cunibs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cunibs-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for cunibs-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 8b1adf3ad6e94bc950198eb85f0ee509029fad1920ace54b37a646e1a4b220a9
MD5 52654af5ee22333b577b0b93fce82360
BLAKE2b-256 1f0034ebf38d1c0e442f575ee3200e94d4caad97fb827a7d8f39fc0df9365b8a

See more details on using hashes here.

Provenance

The following attestation bundles were made for cunibs-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on vcubiomag/cunibs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.1

8 files

0.4.0

8 files

This release

0.3.0 This release

8 files

0.2.0

8 files

0.1.0

5 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page