Atlas Engine Dev
Atlas is a C++20 particle simulation engine with a single float scalar type, a TBB (CPU) or CUDA (GPU) backend chosen at configure time, protobuf snapshot serialization, and optional nanobind-based Python bindings. The CPU build needs neither nvcc nor the CUDA toolkit.
The engine targets large domains, simply and fast. Where physical fidelity trades against speed or simplicity, Atlas takes the simple option — and says so where it does.
The whole public API is aggregated through one umbrella header:
#include <atlas/atlas.h>
All public types live under a single atlas:: namespace (e.g. atlas::Fluid,
atlas::Universe, atlas::Float3, atlas::Box); enum values use snake_case
(atlas::SourceType::volume, atlas::MaterialType::molecule).
Features
- one source tree, two backends: a plain host-compiler build on
std::vector+ TBB, or an nvcc build on Thrust + CUDA - a GPU is needed only to run the CUDA build, never to build it
- a six-stage step pipeline on one object:
System::update()runs emit → search → allocate → solve → advect → remove - particle compaction in
Fluid: sinks mark survivors, one scan rebuilds every particle array - material records (
Molecule,Atom,Ion,Neutron,Solid) and four particle generators - analytic geometry: box, sphere, cylinder, plane, circle, square, triangle, and triangle mesh
- spatial primitives and acceleration structures: rays, AABBs, spatial hashing, BVH, LBVH, and SAH BVH
- DSMC solver with hard-sphere, VHS, and VSS collision kernels
- source, sink, collider, codec, searcher, observer, and serialization runtime modules
- CSV observation and protobuf snapshot save / restart
- optional nanobind-based Python bindings, packaged as self-contained TBB and CUDA wheels (see Python Bindings)
The tagged-union leaf pattern
Most runtime modules share one shape: a concrete umbrella type wraps one of several self-contained leaf types and dispatches to it. No virtual dispatch, because these types are captured by value into device lambdas.
Which umbrella a module uses is decided by one question — does the leaf own a
DeviceBuffer?
| Modules | Umbrella | |
|---|---|---|
| Trivially copyable leaves | Geometry, Material, Collider, Sink, DsmcKernel |
DeviceVariant — host+device, copy-based |
Leaves owning a DeviceBuffer |
Source, Generator, Codec |
HostVariant — host-only, move-based |
Solver is the exception: an abstract base class, because a solver's solve()
runs on the host and launches its own kernels.
An object that owns DeviceBuffers cannot itself enter a kernel, so each hands
out a trivially-copyable view of raw pointers instead:
SpatialHashingSearcherView, FluidDsmcView, UniverseDsmcView,
TriangleMeshView, BvhView.
The backend split
CMake defines exactly one of ATLAS_BACKEND_CUDA and ATLAS_BACKEND_TBB. Only
ten headers — under buffer/,
memory/, parallel/, and
scan/ — branch on it. The other ~120 files name
DeviceBuffer and parallel_for and never learn the difference.
| CUDA backend | Host backend | |
|---|---|---|
HostBuffer<T> / DeviceBuffer<T> |
thrust::host_vector / thrust::device_vector |
std::vector / std::vector |
parallel_for, parallel_fill, parallel_sort |
Thrust | TBB |
exclusive_scan |
thrust::exclusive_scan |
std::exclusive_scan |
default_random_engine, uniform_real_distribution |
one shared implementation | one shared implementation |
The RNG is written out rather than aliased so both backends draw from the
identical stream — a case reproduced on the CPU has to match the GPU run it
checks. The engine holds no __global__, no <<<>>>, and no
cudaMalloc outside memory.h; every kernel is a parallel_for over an
ATLAS_ALL_DEVICE lambda, and those annotations vanish outside __CUDACC__.
That is why the .cu sources compile as ordinary C++.
Requirements
The reference development environment is the Docker dev image (see
Quick Start); the requirements below apply to bare-metal setups.
Core (always required)
| Dependency | Notes |
|---|---|
| TBB | Backs the host-side parallel algorithms in every configuration |
| CMake 3.20+ | Presets are provided in CMakePresets.json |
| C++20 host compiler | GCC 11+ is a reasonable target |
| Ninja | All presets use Ninja |
GPU builds only
| Dependency | Notes |
|---|---|
| CUDA Toolkit 12.x | nvcc compiles every translation unit and provides Thrust. Needed for ATLAS_DEVICE_SYSTEM=CUDA, or for ATLAS_HOST_COMPILER=nvcc |
| NVIDIA driver | Needed only to run ATLAS_DEVICE_SYSTEM=CUDA builds |
Python Bindings
| Dependency | Notes |
|---|---|
| Python 3.8+ | Interpreter with development headers (Development.Module) |
nanobind is vendored under external/, but it carries its own submodule, so a
clone that skipped --recursive must run this before ATLAS_PYTHON=ON will
configure:
git submodule update --init --recursive external/nanobind
Other in-tree dependencies (all vendored under external/):
tinyobjloader (OBJ mesh loading), googletest, googlebenchmark, and
protobuf (binary snapshot serialization). protobuf pulls Abseil in at
configure time.
Quick Start
Docker (recommended)
Build the development image and work inside it — the CUDA toolkit, TBB, CMake, and Ninja are preinstalled:
docker build --target dev -t atlas-dev .
docker run --rm -it -v "$PWD":/workspace atlas-dev
Build and test
Inside the container, or on a bare-metal host. The CPU build needs only TBB and a C++20 compiler:
cmake --preset tbb-debug
cmake --build build/tbb-debug -j$(nproc)
ctest --preset ctest-tbb-debug
GPU build — needs the CUDA toolkit; a GPU only to run the result:
cmake --preset cuda-debug
cmake --build build/cuda-debug -j$(nproc)
Bare-metal Linux prerequisites
sudo apt-get update
sudo apt-get install -y ninja-build libtbb-dev
# that is everything the tbb-* presets need.
# the cuda-* presets additionally need the CUDA 12.x toolkit from NVIDIA's repositories.
Build Presets
| Kind | TBB (host compiler) | CUDA (nvcc) |
|---|---|---|
| Configure | tbb-debug, tbb-release |
cuda-debug, cuda-release |
| Build | build-tbb-debug, build-tbb-release |
build-cuda-debug, build-cuda-release |
| Test | ctest-tbb-debug |
ctest-cuda-debug |
The debug presets turn everything on — logging, tests, the Python module, the examples, and the benchmark cases. The release presets turn all five off and build the engine alone.
tbb-nvcc-debug builds the CPU backend through nvcc. It exists for CI: nvcc
rejects constructs the host compiler accepts — notably an extended
__host__ __device__ lambda inside a private member function — and a
host-compiler-only build would stop catching them.
Important CMake Options
| Option | Debug presets | Release presets | Meaning |
|---|---|---|---|
ATLAS_DEVICE_SYSTEM |
— | — | Parallel backend: TBB (CPU, std + TBB) or CUDA (GPU, nvcc + Thrust) |
ATLAS_HOST_COMPILER |
native |
native |
Compiler for a TBB build: native or nvcc |
ATLAS_LOGGING |
ON |
OFF |
Build logging support (atlas::warn, …) |
ATLAS_GOOGLE_TEST |
ON |
OFF |
Build GoogleTest-based C++ tests |
ATLAS_PYTHON |
ON |
OFF |
Build the nanobind Python bindings |
ATLAS_EXAMPLES |
ON |
OFF |
Build the C++ example programs (examples/cpp/) |
ATLAS_BENCHMARKS |
ON |
OFF |
Build the Google Benchmark cases (benchmarks/atlas/) |
TBB is required in every configuration. The CUDA toolkit is required only when nvcc compiles the sources.
Running a Simulation
The ready-to-run simulation programs are the C++ examples under
examples/cpp/. Each case is a main.cu — a standalone
simulation driven straight through the Atlas API, with no framework, printing its
own timings — built as atlas_example_<case> when ATLAS_EXAMPLES is on.
Examples are on in the debug presets. Build a case and run it:
cmake --preset tbb-debug
cmake --build build/tbb-debug --target atlas_example_cylinder -j$(nproc)
./build/tbb-debug/examples/cpp/atlas_example_cylinder
| Case | Source | Scenario |
|---|---|---|
cylinder |
cylinder/main.cu |
rarefied N₂ crossflow over a cylinder mesh at Kn ≈ 0.05 |
atlas_example_cylinder [steps] [assets_dir] [output_dir] loads
assets/cylinder.obj, prints the freestream regime it resolved to, steps the
System, and writes per-step CSV under <output_dir>/data/.
To see how a simulation is wired up in code — builders, boundary units, the
solver, and the observer — read
cylinder/main.cu.
Google Benchmark cases live separately under
benchmarks/atlas/ as main.cpp files built into
atlas_benchmark_<case>_gbench when ATLAS_BENCHMARKS is on. Only a smoke
case is wired for now — the representative benchmarks are to be rewritten on
Google Benchmark.
Python Bindings
Build the module in-tree (the debug presets already enable it):
cmake --preset tbb-debug
cmake --build build/tbb-debug --target atlas_python -j$(nproc)
The module is a full assembly API that mirrors the C++ builders: every builder is
exposed as a lower-case factory function returning a ready object, and
atlas.build_system(fluid, universe, dt, solver=, source=, generator=, colliders=, sinks=, codec=, observer=) wires them into a runnable System. Initial particle
state is seeded from numpy with fluid_from_arrays(positions, velocities, ...), and
load_fluid / load_universe reload snapshots. The System exposes
update() and save(), the scalars step, dt, particle_count, and
cell_count, and positions() / velocities() / species() returning numpy arrays.
import atlas
import numpy as np
Vec = atlas.Float3
materials = atlas.material_dictionary([
atlas.molecule(mass=4.65e-26, translational_energy=0.0, rotational_energy=0.0,
vibrational_energy=0.0, reference_diameter=4.17e-10,
reference_temperature=273.0, viscosity_index=0.74,
scattering_parameter=1.0),
])
rng = np.random.default_rng(0)
positions = rng.uniform(0.1, 0.9, size=(200, 3)).astype(np.float32)
velocities = (rng.standard_normal((200, 3)) * 300.0).astype(np.float32)
fluid = atlas.fluid_from_arrays(positions, velocities, statistical_weight=1e18, materials=materials)
universe = atlas.universe(Vec(0, 0, 0), Vec(1, 1, 1), cell_size=1.0)
solver = atlas.dsmc_solver(kernel_type=atlas.DsmcKernelType.variable_hard_sphere)
system = atlas.build_system(fluid=fluid, universe=universe, dt=1e-4, solver=solver)
for _ in range(20):
system.update()
print(system.particle_count, np.linalg.norm(system.velocities(), axis=1).mean())
A runnable version is examples/python/dsmc_dense_cell.py;
the full API reference is docs/guidelines/python.md.
Self-contained, redistributable wheels (bundling libtbb / libcudart) are built inside the packaging image. Initialise the submodules the build needs first:
git submodule update --init --recursive \
external/nanobind external/tinyobj external/protobuf
docker build --target wheel -t atlas-wheel .
# ATLAS_WHEEL_BACKENDS=TBB builds a CPU-only wheel (no GPU or driver at run time);
# omit it to build both TBB and CUDA wheels.
docker run --rm -v "$PWD":/workspace -w /workspace \
-e ATLAS_WHEEL_BACKENDS=TBB \
atlas-wheel -c 'bash scripts/build_wheels.sh'
# -> dist/tbb/*.whl (and dist/cuda/*.whl when both backends are built)
pip install dist/tbb/*.whl
The full wheel flow — auditwheel repair, the abi3 stable-ABI details, and building
without Docker — is in docs/guidelines/python.md.
Tests
Tests live under tests/atlas/, mirroring include/atlas/ and
src/atlas/, and use GoogleTest directly. Sources are globbed recursively into
an aggregate atlas_tests binary plus one atlas_tests_<module> binary per
directory, so a new test file needs no CMake edit. Run them from a *-debug
preset:
cmake --preset tbb-debug
cmake --build build/tbb-debug -j$(nproc)
ctest --preset ctest-tbb-debug
GitHub Actions: TBB on main
The TBB CI workflow runs automatically on pushes to
main. To run it manually, open Actions → TBB CI → Run workflow and select
main. Other branches and pull requests do not run this workflow; a manual run
on another branch skips its job.
The Ubuntu 22.04 runner uses the native C++ compiler and TBB, with no CUDA toolkit
or GPU. It builds and runs the aggregate GoogleTest suite, creates a Python source
distribution, builds a wheel from that archive, checks package metadata, installs
the wheel, and runs the Python DSMC example. Successful runs attach the
tbb-linux-cp311 artifact, containing the source archive and
Python 3.11 wheel. This CI wheel requires the runner-compatible Linux/TBB runtime;
use the manylinux release wheels below for redistribution.
Python Distribution
The distribution name is atlas-engine and the import name is atlas.
Packaging metadata and bundled source dependencies are configured in
pyproject.toml. Source archives include the required git
submodule contents, so installing an archive does not require a git checkout.
Building from source still requires a C++20 compiler, CMake, Ninja, TBB, and
network access for build tools and Abseil.
The Publish Python workflow runs manually on
main only. It builds a source archive and uses that archive to build and test
TBB manylinux x86_64 wheels for CPython 3.9–3.13. CUDA wheels remain a separate
local build through scripts/build_wheels.sh; they are not uploaded to PyPI.
To prepare or publish a version:
- Update
project.versioninpyproject.tomland the matching version inCITATION.cff, then push the changes tomain. Packagedatlas.__version__follows the Python distribution version automatically. - Open Actions → Publish Python → Run workflow, selecting
main. Leave Publish the validated distributions to PyPI unchecked to download thepython-sdistandpython-wheelsartifacts without publishing. - For actual publication, configure a PyPI Trusted Publisher for this repository,
workflow
publish-python.yml, and GitHub environmentpypi. Create the matching GitHub environment, then run the workflow onmainwith the publish checkbox enabled. Each uploaded version must be new on PyPI.
Tag pushes and GitHub releases do not trigger Python publication. The Zenodo release/DOI workflow described below remains separate. Once a version has been published, install its CPU package with:
python -m pip install atlas-engine
python -c "import atlas; print(atlas.__version__)"
See the official GitHub branch-filter documentation and PyPI Trusted Publishing guide.
Documentation
Deeper design and contributor material lives in the repository, not this file:
docs/atlas/— per-module documentation: the tagged-union leaf pattern and how to extend each module. There is a document for every module underinclude/atlas/.docs/guidelines/— how to work on it: workflow, code style, build/test, dependencies, and the Python bindings guide. Start atdocs/guidelines/README.md.AGENTS.mdis the entry map into both directories and the repository-wide instruction file for AI coding agents.- Every header and source under
include/atlas/andsrc/atlas/carries Doxygen comments; the convention is recorded incoding-style.md§7.
Citation and Zenodo DOI
Citation metadata is maintained in CITATION.cff, which is
supported by both GitHub and Zenodo. Its author, version, and license match
the project metadata in pyproject.toml.
Version v0.1.0, released on 2026-09-14, is archived on Zenodo:
- Version DOI: 10.5281/zenodo.22752179. Use this DOI when citing results produced with v0.1.0.
- Concept DOI (all versions): 10.5281/zenodo.22752178. The README badge uses this DOI to link to the latest published version.
For subsequent releases:
- Sign in to Zenodo with GitHub and enable
Wontae-Lee/atlas-engine-devin the GitHub integration settings. - Review the authors in
CITATION.cff, adding any coauthors and known ORCID identifiers or affiliations. Updateversionto match the release and adddate-releasedinYYYY-MM-DDformat when the release date is known. - Commit and push the metadata, then publish a GitHub release whose tag includes that commit. Use a new version tag for each release.
- Wait for Zenodo to process the release and check the published record's metadata and DOI. Adding these files alone does not register a DOI.
- Add the issued release DOI to
CITATION.cffasdoiand link the Zenodo record here. Keep the DOI, version, and release date referring to the same release; do not carry a previous release's DOI into a new release.
See Zenodo's citation metadata guide
and GitHub release archiving guide.
CITATION.cff is sufficient for this workflow; if a .zenodo.json file is
added later, Zenodo will use that file instead of CITATION.cff.
License
Licensed under the GNU General Public License v3.0 (GPLv3) or later. See LICENSE for the full text.
Metadata
Release files for atlas-engine 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| atlas_engine-0.1.0.tar.gz | 17.8 MB | With provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| atlas_engine-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl | CPython 3.13 | CPython 3.13 | Linux glibc 2.28+ x86-64 | Details |
| atlas_engine-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl | CPython 3.12 | CPython 3.12 | Linux glibc 2.28+ x86-64 | Details |
| atlas_engine-0.1.0-cp311-cp311-manylinux_2_28_x86_64.whl | CPython 3.11 | CPython 3.11 | Linux glibc 2.28+ x86-64 | Details |
| atlas_engine-0.1.0-cp310-cp310-manylinux_2_28_x86_64.whl | CPython 3.10 | CPython 3.10 | Linux glibc 2.28+ x86-64 | Details |
| atlas_engine-0.1.0-cp39-cp39-manylinux_2_28_x86_64.whl | CPython 3.9 | CPython 3.9 | Linux glibc 2.28+ x86-64 | Details |
Total release size: 28.3 MB
Release files / atlas_engine-0.1.0.tar.gz
| Download URL | atlas_engine-0.1.0.tar.gz |
|---|---|
| Size | 17.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2a65bb91a586192a0fa13d4e1a453804c745fdd47d26fabc18e9d4dc35abcf56
|
|
BLAKE2b-256 checksum How to use checksums |
3b08e2dd8ade549c04f61c35b03e6d1f6298676a690ceb92da6b8f826b547992
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / atlas_engine-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl
| Download URL | atlas_engine-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.13 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
2399a90810c2f9f3871ec857ae6a0bb82d41e8ad0f1e7f213e25aee4897f417d
|
|
BLAKE2b-256 checksum How to use checksums |
fe357a22366bbb3e5892150a9d72db06c8e8e4e1a8406bbe2f8205ea35d57c63
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / atlas_engine-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl
| Download URL | atlas_engine-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.12 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
cfdd1df740fa56686f5d01711fc632cf67b8fc4479ff3183002119f2dc2cdf82
|
|
BLAKE2b-256 checksum How to use checksums |
860677abe7f13ba479a3f77d0a653ddc70c6f3bbd8b57fc7bfe2e7023450ee29
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / atlas_engine-0.1.0-cp311-cp311-manylinux_2_28_x86_64.whl
| Download URL | atlas_engine-0.1.0-cp311-cp311-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.11 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
30d16ed13dd71bca263577a2cec6b40e916105cc41646631bbb297cd7ffa1034
|
|
BLAKE2b-256 checksum How to use checksums |
1eb44caf168fcd2826becc3cab0ca59bcf23384c9c096f7c5910b37fc2741d3d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / atlas_engine-0.1.0-cp310-cp310-manylinux_2_28_x86_64.whl
| Download URL | atlas_engine-0.1.0-cp310-cp310-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
69847b4e55cd5302e1966cf663532bd39ca9140481a4a307e31c8fcd4c8c4bba
|
|
BLAKE2b-256 checksum How to use checksums |
fe7d41a6722e5b2bf9759c3192d788587ee880c9db55164b08c3551659a7ff5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / atlas_engine-0.1.0-cp39-cp39-manylinux_2_28_x86_64.whl
| Download URL | atlas_engine-0.1.0-cp39-cp39-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.9 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
cf1735e3ca4c86a9d26aee4763d08888b8c4d00fb23ab11971aa9eedf410d613
|
|
BLAKE2b-256 checksum How to use checksums |
a15cb3e28515401a0e9dcdeac63e925a2f754df8285a40eaa4ebb60e4ff77df2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logSecurity for atlas-engine 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 6 files in this release have been uploaded with provenance metadata.
Attestation for atlas_engine-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.