Fast sweep-hull (S-hull) Delaunay triangulation in 2D and 3D, implemented in Rust
Project description
shull
S-hull: a fast sweep-hull routine for Delaunay triangulation by David Sinclair (see http://www.s-hull.org/) implemented in Rust with Python bindings.
The 2D case implements S-hull proper: a seed triangle with the smallest circumcircle, a radial sweep over points sorted by distance from its circumcenter, hull attachment via a linked ring with a pseudo-angle hash, and in-circle edge flipping to restore the Delaunay condition.
The 3D case generalizes Sinclair's Newton Apple Wrapper sweep-hull algorithm
(arXiv 1602.04707) one dimension up: points
are lifted onto a 4D paraboloid (w = x² + y² + z²), the 4D convex hull is
computed by incremental insertion along a Morton (Z-order) space-filling curve
— every lifted point is extreme on the paraboloid, so any insertion order is
valid, and the spatially local one keeps the hull walk cache-hot — and the
downward-facing facets are exactly the Delaunay tetrahedra.
In both cases all combinatorial decisions (visibility, in-circle/in-sphere
tests) fall back to Shewchuk's exact adaptive predicates (the
robust crate) — so the triangulation stays
valid even on adversarial inputs (cospherical grids, tight clusters far apart)
where plain floating point corrupts the result.
TODOs
- implementation for the 2d case
- generalize from 2 to N dimensions: 3D Delaunay (tetrahedra) via 4D sweep-hull
- improve 2d performance: ~10X faster than scipy's Qhull-based Delaunay
- improve 3d performance: ~3–5.5X faster than scipy
- scipy-compatible API:
neighbors,convex_hull,vertex_neighbor_vertices,transform,find_simplex, …
Build
cdinto directory- Activate virtual environment:
source .venv/bin/activate - Run
maturin develop(usematurin build --releaseto build wheel)
Test / benchmark
cargo test # Rust unit tests (incl. hull invariant checks)
python -m pytest tests/ # property tests + comparison against scipy
python bench.py # benchmark against scipy.spatial.Delaunay
Benchmark on an Apple-silicon laptop (random uniform points, release build):
2D (Delaunay vs scipy.spatial.Delaunay)
| n | shull | scipy (Qhull) | speedup |
|---|---|---|---|
| 10 000 | 0.002 s | 0.018 s | 9.8× |
| 100 000 | 0.029 s | 0.30 s | 10.7× |
| 1 000 000 | 0.46 s | 5.0 s | 10.9× |
3D (Delaunay3d vs scipy.spatial.Delaunay)
| n | shull | scipy (Qhull) | speedup |
|---|---|---|---|
| 10 000 | 0.031 s | 0.098 s | 3.2× |
| 100 000 | 0.32 s | 1.69 s | 5.3× |
| 1 000 000 | 3.8 s | 20.8 s | 5.5× |
Usage
2D
>>> import shull
>>> import numpy as np
>>> pts = np.random.default_rng(12345).random((10000, 2))
>>> d = shull.Delaunay(pts)
>>> d.triangles # alias: d.simplices
array([[5790, 4665, 8764],
[4665, 9599, 8764],
[7711, 64, 4665],
...,
[2821, 8418, 1189],
[1500, 9364, 8681],
[5462, 1500, 8681]], dtype=int32)
3D
>>> pts = np.random.default_rng(12345).random((10000, 3))
>>> d = shull.Delaunay(pts) # dispatches on the number of columns;
>>> d.simplices # Delaunay3d is kept as an explicit alias
array([[3661, 6693, 7492, 1937],
...], dtype=int32)
scipy compatibility
shull.Delaunay aims to be a drop-in replacement for
scipy.spatial.Delaunay. Beyond points and simplices (int32, like
scipy) it provides the derived structures. neighbors comes straight out
of the triangulation (the hull construction maintains facet adjacency
anyway, so exporting it is essentially free, like qhull) and
vertex_neighbor_vertices is built in Rust on first access (~2x faster
than scipy's); the rest are computed lazily in numpy and cached:
neighbors— neighboring simplex opposite each vertex, -1 at the boundaryconvex_hull— facets of the convex hullvertex_to_simplex— a simplex containing each vertexvertex_neighbor_vertices— CSR(indptr, indices)vertex adjacencycoplanar— points not in the triangulation (dropped exact duplicates), mapped to their kept representative; recorded by the Rust core during its dedup, not reconstructed after the facttransform— barycentric transforms, same layout as scipyfind_simplex(xi, bruteforce=False, tol=None)— point location via a vectorized visibility walk (brute force as option/fallback)npoints,nsimplex,ndim,min_bound,max_bound,furthest_site,close()
Not implemented: equations (and paraboloid_scale/paraboloid_shift,
plane_distance, lift_points), incremental mode (add_points),
furthest_site=True and qhull_options — the constructor accepts scipy's
keyword arguments but raises NotImplementedError for non-default values.
Unlike scipy, float32 points are kept as float32 (see below).
Notes (both dimensions):
- Output simplices are positively oriented (counterclockwise triangles in 2D,
positive-volume tetrahedra in 3D) and index into
points. - float32 input is supported natively: no upcast copy is made (
d.pointskeeps the float32 dtype). Coordinates widen to float64 exactly internally, so the result is identical to passingpoints.astype(np.float64). Other dtypes are converted to float64. - Exact duplicate points are dropped, keeping the first occurrence as the
representative. scipy/Qhull likewise never includes duplicate indices in
simplices(though which copy Qhull keeps is arbitrary, while shull's choice is deterministic). Dropped points are reported incoplanar(scipy's convention); the rawcalculate_shull_*functions return the(dropped, kept)index pairs as a third array. - Degenerate input (too few distinct points, all points collinear in 2D, all
points coplanar/cospherical in 3D) raises
ValueError— a full-dimensional triangulation does not exist in those cases. - Points are triangulated after centering on their centroid (a ≤1-ulp perturbation of the coordinates), which makes the result robust to clouds positioned far from the origin.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shull-0.2.0.tar.gz.
File metadata
- Download URL: shull-0.2.0.tar.gz
- Upload date:
- Size: 57.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04e6f1195375a261246484b9cfc6c157beec378f01bb78ef1f54deb9fa1dc5f3
|
|
| MD5 |
fa32a7e6922bab2a32e71eb6d2713857
|
|
| BLAKE2b-256 |
9d7b112df66e4d6ff11d97f790371d9cd0470f6c3058a879d3906b4987965e23
|
File details
Details for the file shull-0.2.0-cp38-abi3-win_amd64.whl.
File metadata
- Download URL: shull-0.2.0-cp38-abi3-win_amd64.whl
- Upload date:
- Size: 257.8 kB
- Tags: CPython 3.8+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7db4ab972b4f99ba064be50d68d5f4400f000fb5820b3148c971dbf3d125ac06
|
|
| MD5 |
584a23a51d6d2c472a33bcb1ef1991f7
|
|
| BLAKE2b-256 |
18d6db7c351c1b04fcb6bd81dc56ed833bd0ad02bebf66445851299ff40c757e
|
File details
Details for the file shull-0.2.0-cp38-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: shull-0.2.0-cp38-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 612.1 kB
- Tags: CPython 3.8+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e51dbc9cd971bf48a6c0cbdca02a97dc88b8ec13d2d3832b6ceeff7ead02edd3
|
|
| MD5 |
294f8e2a56454bfb4e0922eb10879660
|
|
| BLAKE2b-256 |
58861da5d54c4a1a6398a8dc047e6ec557a1bfe88a5b1e60b3c4cef7ae0f40b5
|
File details
Details for the file shull-0.2.0-cp38-abi3-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: shull-0.2.0-cp38-abi3-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 573.1 kB
- Tags: CPython 3.8+, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f45f8fc60947a96b4568ef2b1388f49cc2686fbeb146e55dd9f6e82d2ab5585
|
|
| MD5 |
742014ef017b5cd03c24116f0e40a210
|
|
| BLAKE2b-256 |
fe508213bb52f26d3817ac62faa14ad6b5d478e427f562b9cf7913fa669520e5
|
File details
Details for the file shull-0.2.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: shull-0.2.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 407.9 kB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
044ec148873b162b1f4b3f3fc529edbd49ebe578f578f86768cf66f692a1ef8c
|
|
| MD5 |
5c9e746ac9de468485334a83c83aa132
|
|
| BLAKE2b-256 |
489dd817df9a5a6146a557fa8acdba540ca22f47f67a8b0aa1960340c073a589
|
File details
Details for the file shull-0.2.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: shull-0.2.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 398.2 kB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d9b562c3805b41ede0fbc809b98e1cea850b52ce2b3f79f65695e61232f404e
|
|
| MD5 |
8a390d9df1bb83892ba451ba10ee988d
|
|
| BLAKE2b-256 |
6e4ac6e9e7544c0b2f2b998e88ee7022cddde3531c874172bd84adf72fb92208
|
File details
Details for the file shull-0.2.0-cp38-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: shull-0.2.0-cp38-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 351.4 kB
- Tags: CPython 3.8+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2826fcc6ecc00864853d8fbb1f5780352c735dfa9fe945c3af18917de48a817b
|
|
| MD5 |
fd7b3052fa20eb3c34bd136d84549269
|
|
| BLAKE2b-256 |
7e12bb903e20acb5e08c36275cff20ddf79b7cee067ae27824321397caf3c451
|
File details
Details for the file shull-0.2.0-cp38-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: shull-0.2.0-cp38-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 366.5 kB
- Tags: CPython 3.8+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2889fc9ea0c45e874262fb13af56b724e2aab5e18d8c8876ee7e55b9ace4ec5f
|
|
| MD5 |
17469b79d1946bffbc70deeed895b125
|
|
| BLAKE2b-256 |
4226b5d26b97bd918be57218ad798d74fd8ce7aa8f2ce2b9916aa28af7604b54
|