|
1 | 1 | # adaptive-triangulation |
2 | 2 |
|
3 | | -Fast N-dimensional Delaunay triangulation in Rust with Python bindings. |
| 3 | +[](https://github.com/python-adaptive/adaptive-triangulation/actions) |
| 4 | +[](LICENSE) |
4 | 5 |
|
5 | | -[](https://pypi.org/project/adaptive-triangulation/) |
6 | | -[](https://pypi.org/project/adaptive-triangulation/) |
7 | | -[](LICENSE) |
8 | | -[](https://github.com/python-adaptive/adaptive-triangulation/actions) |
9 | | -[](https://pypi.org/project/adaptive-triangulation/) |
| 6 | +Fast N-dimensional Delaunay triangulation in Rust with Python bindings (PyO3). |
| 7 | +Drop-in replacement for [adaptive](https://github.com/python-adaptive/adaptive)'s `Triangulation` class — **5-99× faster**. |
10 | 8 |
|
11 | | -`adaptive-triangulation` is a Rust/PyO3 implementation of the incremental Bowyer-Watson |
12 | | -algorithm for Delaunay triangulation. It is designed as a fast drop-in triangulation backend for |
13 | | -`adaptive`, while remaining useful as a standalone computational geometry package for 2D, 3D, and |
14 | | -higher-dimensional point sets. |
| 9 | +## Performance |
| 10 | + |
| 11 | +### Standalone triangulation (incremental insertion) |
| 12 | +| Case | Rust | Python | Speedup | |
| 13 | +|---|---:|---:|---:| |
| 14 | +| 2D, 1K pts | 38.5 ms | 668 ms | **17×** | |
| 15 | +| 2D, 5K pts | 260 ms | 8,547 ms | **33×** | |
| 16 | +| 3D, 500 pts | 133 ms | 5,571 ms | **42×** | |
| 17 | + |
| 18 | +### LearnerND integration (end-to-end, `ring_of_fire` 2D) |
| 19 | +| N pts | Learner2D (scipy) | LearnerND (Python) | LearnerND (Rust) | |
| 20 | +|---|---:|---:|---:| |
| 21 | +| 1,000 | 0.34 s | 0.91 s | **0.23 s** | |
| 22 | +| 2,000 | 1.17 s | 1.80 s | **0.38 s** | |
| 23 | +| 5,000 | 6.99 s | 4.57 s | **0.99 s** | |
| 24 | + |
| 25 | +LearnerND + Rust is **5× faster** than LearnerND + Python, and **7× faster** than Learner2D at 5K points. |
15 | 26 |
|
16 | 27 | ## Installation |
17 | 28 |
|
18 | 29 | ```bash |
19 | 30 | pip install adaptive-triangulation |
20 | 31 | ``` |
21 | 32 |
|
22 | | -## Quick Start |
| 33 | +Requires a Rust toolchain for building from source. Pre-built wheels are available for common platforms via CI. |
| 34 | + |
| 35 | +## Quick start |
23 | 36 |
|
24 | 37 | ```python |
25 | | -import adaptive_triangulation as at |
26 | | -import numpy as np |
27 | | - |
28 | | -# 2D triangulation |
29 | | -points = [[0, 0], [1, 0], [0, 1], [1, 1], [0.5, 0.5]] |
30 | | -tri = at.Triangulation(points) |
31 | | -print(f"Simplices: {tri.simplices}") |
32 | | - |
33 | | -# Incremental point insertion |
34 | | -deleted, added = tri.add_point((0.25, 0.75)) |
35 | | -print(f"Deleted simplices: {deleted}") |
36 | | -print(f"Added simplices: {added}") |
37 | | - |
38 | | -# Circumsphere queries |
39 | | -for simplex in tri.simplices: |
40 | | - center, radius = tri.circumscribed_circle(simplex) |
41 | | - print(simplex, center, radius) |
42 | | - |
43 | | -# Works in any dimension |
44 | | -points_3d = np.random.rand(10, 3).tolist() |
45 | | -tri3d = at.Triangulation(points_3d) |
46 | | -print(f"3D simplices: {len(tri3d.simplices)}") |
| 38 | +from adaptive_triangulation import Triangulation |
| 39 | + |
| 40 | +# Build a 2D triangulation |
| 41 | +tri = Triangulation([(0, 0), (1, 0), (0, 1), (1, 1)]) |
| 42 | + |
| 43 | +# Insert points incrementally (Bowyer-Watson) |
| 44 | +deleted, added = tri.add_point((0.5, 0.5)) |
| 45 | + |
| 46 | +# Query properties |
| 47 | +print(len(tri.simplices)) # number of triangles |
| 48 | +print(tri.dim) # 2 |
| 49 | +print(tri.reference_invariant()) # True |
47 | 50 | ``` |
48 | 51 |
|
49 | | -Standalone geometry helpers are also available: |
| 52 | +## Usage with adaptive's LearnerND |
| 53 | + |
| 54 | +This is a drop-in replacement for `adaptive`'s built-in triangulation. |
| 55 | +Monkey-patch the module to use Rust triangulation everywhere: |
50 | 56 |
|
51 | 57 | ```python |
52 | 58 | import adaptive_triangulation as at |
| 59 | +from adaptive.learner import learnerND as lnd_mod |
| 60 | +from adaptive.learner.learnerND import LearnerND |
| 61 | + |
| 62 | +# Replace both the class and standalone functions |
| 63 | +lnd_mod.Triangulation = at.Triangulation |
| 64 | +lnd_mod.circumsphere = at.circumsphere |
| 65 | +lnd_mod.simplex_volume_in_embedding = at.simplex_volume_in_embedding |
| 66 | +lnd_mod.point_in_simplex = at.point_in_simplex |
53 | 67 |
|
54 | | -triangle = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]] |
55 | | -center, radius = at.circumsphere(triangle) |
56 | | -inside = at.point_in_simplex([0.25, 0.25], triangle) |
57 | | -area = at.volume(triangle) |
| 68 | +# Now use LearnerND as normal — it's 5× faster |
| 69 | +learner = LearnerND(my_function, bounds=[(-1, 1), (-1, 1)]) |
58 | 70 | ``` |
59 | 71 |
|
60 | | -## Performance |
| 72 | +See [`examples/adaptive_learnernd.py`](examples/adaptive_learnernd.py) for a full working example with timing comparison. |
| 73 | + |
| 74 | +## API |
61 | 75 |
|
62 | | -The table below compares release-mode `adaptive-triangulation` against the Python reference |
63 | | -implementation used in the test suite on the same machine. Each case builds a triangulation from a |
64 | | -minimal seed simplex and inserts the remaining points incrementally. |
65 | | - |
66 | | -| Case | Rust (`maturin develop --release`) | Python reference | Speedup | |
67 | | -| --- | ---: | ---: | ---: | |
68 | | -| 2D, 1,000 points | 38.5 ms | 668.1 ms | 17.4x | |
69 | | -| 2D, 5,000 points | 259.8 ms | 8547.2 ms | 32.9x | |
70 | | -| 3D, 500 points | 133.4 ms | 5570.9 ms | 41.8x | |
71 | | - |
72 | | -Absolute timings will vary by machine, but the release build consistently outperforms the pure |
73 | | -Python reference by a wide margin on construction and incremental insertion workloads. |
74 | | - |
75 | | -## API Reference |
76 | | - |
77 | | -### Module attributes |
78 | | - |
79 | | -- `__version__`: Package version sourced from `Cargo.toml`. |
80 | | - |
81 | | -### `Triangulation` |
82 | | - |
83 | | -- `Triangulation(coords)`: Build an N-dimensional Delaunay triangulation from points in general position. |
84 | | -- `add_point(point, simplex=None, transform=None)`: Insert one point and return `(deleted_simplices, added_simplices)`. |
85 | | -- `add_simplex(simplex)`: Insert a simplex directly into the triangulation state. |
86 | | -- `delete_simplex(simplex)`: Remove a simplex from the triangulation state. |
87 | | -- `locate_point(point)`: Return the simplex containing a query point, or an empty tuple when none is found. |
88 | | -- `get_reduced_simplex(point, simplex, eps=1e-8)`: Reduce a simplex to the smallest face containing the point. |
89 | | -- `circumscribed_circle(simplex, transform=None)`: Compute the circumsphere center and radius for a simplex. |
90 | | -- `point_in_circumcircle(pt_index, simplex, transform=None)`: Check whether a stored vertex lies inside a simplex circumsphere. |
91 | | -- `point_in_cicumcircle(pt_index, simplex, transform=None)`: Legacy alias for `point_in_circumcircle`. |
92 | | -- `point_in_simplex(point, simplex, eps=1e-8)`: Test whether a point lies inside a simplex. |
93 | | -- `volume(simplex)`: Compute the volume of one simplex by vertex index. |
94 | | -- `volumes()`: Return the volumes of all simplices in the triangulation. |
95 | | -- `faces(dim=None, simplices=None, vertices=None)`: Iterate over faces filtered by dimension, simplices, or vertices. |
96 | | -- `containing(face)`: Return the simplices that contain a face. |
97 | | -- `get_vertices(indices)`: Fetch vertex coordinates for a sequence of vertex indices. |
98 | | -- `bowyer_watson(pt_index, containing_simplex=None, transform=None)`: Run one Bowyer-Watson insertion step for an existing vertex. |
99 | | -- `reference_invariant()`: Check internal consistency against the reference invariants used by the tests. |
100 | | -- `vertex_invariant(vertex)`: Compatibility placeholder that currently raises `NotImplementedError`. |
101 | | -- `convex_invariant(vertex)`: Compatibility placeholder that currently raises `NotImplementedError`. |
102 | | -- `vertices`: Vertex coordinates stored by the triangulation. |
103 | | -- `simplices`: Set of simplices represented as tuples of vertex indices. |
104 | | -- `vertex_to_simplices`: Reverse map from vertex index to incident simplices. |
105 | | -- `hull`: Set of vertex indices on the convex hull. |
106 | | -- `dim`: Spatial dimension of the triangulation. |
107 | | -- `default_transform`: Identity metric transform for the current dimension. |
| 76 | +### `Triangulation` class |
| 77 | + |
| 78 | +```python |
| 79 | +tri = Triangulation(coords) # Build from initial points |
| 80 | +tri.add_point(point) # Incremental insertion → (deleted, added) |
| 81 | +tri.locate_point(point) # Find containing simplex |
| 82 | +tri.circumscribed_circle(simplex) # → (center, radius) |
| 83 | +tri.volume(simplex) # Simplex volume |
| 84 | +tri.volumes() # All simplex volumes |
| 85 | +tri.point_in_simplex(point, simplex) # Containment test |
| 86 | +tri.point_in_circumcircle(pt, simplex) # Circumcircle test |
| 87 | +tri.bowyer_watson(pt_index) # Direct Bowyer-Watson |
| 88 | +tri.reference_invariant() # Consistency check |
| 89 | +``` |
| 90 | + |
| 91 | +**Properties:** `vertices`, `simplices`, `vertex_to_simplices`, `hull`, `dim`, `default_transform` |
108 | 92 |
|
109 | 93 | ### Standalone functions |
110 | 94 |
|
111 | | -- `circumsphere(points)`: Compute the circumsphere of a simplex given explicit coordinates. |
112 | | -- `fast_2d_circumcircle(points)`: Fast circumcircle routine specialized for three 2D points. |
113 | | -- `fast_3d_circumsphere(points)`: Fast circumsphere routine specialized for four 3D points. |
114 | | -- `fast_3d_circumcircle(points)`: Alias for the 3D circumsphere helper. |
115 | | -- `point_in_simplex(point, simplex, eps=1e-8)`: Generic point-in-simplex predicate. |
116 | | -- `fast_2d_point_in_simplex(point, simplex, eps=1e-8)`: Fast 2D point-in-triangle predicate. |
117 | | -- `volume(simplex)`: Compute the volume of a simplex from coordinates. |
118 | | -- `simplex_volume_in_embedding(vertices)`: Compute simplex volume in a higher-dimensional embedding space. |
119 | | -- `orientation(face, origin)`: Return the orientation of a face relative to an origin point. |
120 | | -- `fast_norm(point)`: Compute the Euclidean norm of a point. |
| 95 | +```python |
| 96 | +from adaptive_triangulation import ( |
| 97 | + circumsphere, # General circumsphere |
| 98 | + fast_2d_circumcircle, # Optimized 2D |
| 99 | + fast_3d_circumsphere, # Optimized 3D |
| 100 | + point_in_simplex, # Containment test |
| 101 | + volume, # Simplex volume |
| 102 | + simplex_volume_in_embedding, # Volume in embedding space |
| 103 | + orientation, # Face orientation |
| 104 | +) |
| 105 | +``` |
| 106 | + |
| 107 | +## Examples |
| 108 | + |
| 109 | +- [`examples/basic_usage.py`](examples/basic_usage.py) — Core API walkthrough |
| 110 | +- [`examples/adaptive_learnernd.py`](examples/adaptive_learnernd.py) — LearnerND integration with timing |
| 111 | +- [`examples/benchmark_vs_python.py`](examples/benchmark_vs_python.py) — Standalone benchmarks across dimensions |
| 112 | + |
| 113 | +## Development |
| 114 | + |
| 115 | +```bash |
| 116 | +# Build (requires Rust toolchain) |
| 117 | +pip install maturin |
| 118 | +maturin develop --release |
| 119 | + |
| 120 | +# Tests |
| 121 | +cargo test # Rust tests |
| 122 | +python -m pytest tests/ -v # Python tests |
| 123 | + |
| 124 | +# Linting |
| 125 | +pre-commit run --all-files # ruff, mypy, cargo fmt, cargo clippy |
| 126 | +``` |
121 | 127 |
|
122 | 128 | ## License |
123 | 129 |
|
124 | | -BSD-3-Clause, matching the `adaptive` project. See [LICENSE](LICENSE). |
| 130 | +BSD-3-Clause |
0 commit comments