{"id":50958287,"url":"https://github.com/python-adaptive/adaptive-triangulation","last_synced_at":"2026-06-18T10:02:49.924Z","repository":{"id":349289521,"uuid":"1201735322","full_name":"python-adaptive/adaptive-triangulation","owner":"python-adaptive","description":"Fast Delaunay triangulation in Rust with Python bindings (PyO3). Drop-in replacement for adaptive's Triangulation class — 5-99× faster.","archived":false,"fork":false,"pushed_at":"2026-06-10T19:33:53.000Z","size":353,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T20:15:54.734Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/python-adaptive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-05T04:45:41.000Z","updated_at":"2026-06-10T19:33:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/python-adaptive/adaptive-triangulation","commit_stats":null,"previous_names":["python-adaptive/adaptive-triangulation"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/python-adaptive/adaptive-triangulation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-adaptive%2Fadaptive-triangulation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-adaptive%2Fadaptive-triangulation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-adaptive%2Fadaptive-triangulation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-adaptive%2Fadaptive-triangulation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/python-adaptive","download_url":"https://codeload.github.com/python-adaptive/adaptive-triangulation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-adaptive%2Fadaptive-triangulation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34485169,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-18T10:02:49.229Z","updated_at":"2026-06-18T10:02:49.914Z","avatar_url":"https://github.com/python-adaptive.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# adaptive-triangulation\n\n[![PyPI version](https://img.shields.io/pypi/v/adaptive-triangulation.svg)](https://pypi.org/project/adaptive-triangulation/)\n[![Python versions](https://img.shields.io/pypi/pyversions/adaptive-triangulation.svg)](https://pypi.org/project/adaptive-triangulation/)\n[![CI](https://github.com/python-adaptive/adaptive-triangulation/actions/workflows/ci.yml/badge.svg)](https://github.com/python-adaptive/adaptive-triangulation/actions)\n[![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](LICENSE)\n\nFast N-dimensional Delaunay triangulation in Rust with Python bindings (PyO3).\nDrop-in replacement for [adaptive](https://github.com/python-adaptive/adaptive)'s `Triangulation` class — **30-300× faster** standalone, **~3.3×** end-to-end in `LearnerND` (where adaptive's own Python code dominates).\nUsed automatically by adaptive ≥ 1.5 when installed.\n\n## Performance\n\nMeasured with the scripts in [`examples/`](examples/) against adaptive 1.5.0, best of 3.\nAbsolute times are machine-dependent; the ratios are representative.\n\n### Standalone triangulation (incremental insertion)\n| Case | Rust | Python | Speedup |\n|---|---:|---:|---:|\n| 2D, 1K pts | 18 ms | 730 ms | **41×** |\n| 2D, 5K pts | 138 ms | 14,442 ms | **105×** |\n| 3D, 500 pts | 32 ms | 3,037 ms | **94×** |\n| 3D, 2K pts | 150 ms | 44,531 ms | **297×** |\n\n### LearnerND integration (end-to-end, `ring_of_fire` 2D)\n| N pts | Learner2D (scipy) | LearnerND (Python) | LearnerND (Rust) |\n|---|---:|---:|---:|\n| 1,000 | 0.23 s | 0.50 s | **0.16 s** |\n| 2,000 | 0.90 s | 1.01 s | **0.32 s** |\n| 5,000 | 5.69 s | 2.57 s | **0.79 s** |\n\nLearnerND + Rust is **3.3× faster** than LearnerND + Python, and **7× faster** than Learner2D at 5K points.\nThe end-to-end ratio is smaller than the standalone one because adaptive's own Python-side loss machinery dominates once the triangulation is fast.\n\n### Batched LearnerND APIs (not yet wired into adaptive)\n\n`simplices_containing` and `default_loss` move two of the remaining `LearnerND` Python hot loops into Rust.\nWired in the way a future adaptive release would use them ([`examples/learnernd_batched_apis.py`](examples/learnernd_batched_apis.py)), they add **1.17×** (2D, 3000 pts) to **1.40×** (3D, 1500 pts) on top of the table above, while sampling identical points.\n\n## Installation\n\n```bash\npip install adaptive-triangulation\n```\n\nRequires a Rust toolchain for building from source.\nPre-built wheels are available for common platforms via CI.\n\n## Quick start\n\n```python\nfrom adaptive_triangulation import Triangulation\n\n# Build a 2D triangulation\ntri = Triangulation([(0, 0), (1, 0), (0, 1), (1, 1)])\n\n# Insert points incrementally (Bowyer-Watson)\ndeleted, added = tri.add_point((0.5, 0.5))\n\n# Query properties\nprint(len(tri.simplices))     # number of triangles\nprint(tri.dim)                # 2\nprint(tri.reference_invariant())  # True\n```\n\n## Usage with adaptive's LearnerND\n\nSince adaptive 1.5.0 this package is detected and used automatically — no code changes needed:\n\n```bash\npip install \"adaptive[rust]\"\n```\n\nPer learner, the backend can be selected explicitly with `LearnerND(..., triangulation_backend=\"auto\" | \"python\" | \"rust\")`, or globally with the `ADAPTIVE_TRIANGULATION_BACKEND` environment variable.\n\nFor adaptive \u003c 1.5.0, monkey-patch the module instead:\n\n```python\nimport adaptive_triangulation as at\nfrom adaptive.learner import learnerND as lnd_mod\nfrom adaptive.learner.learnerND import LearnerND\n\n# Replace both the class and standalone functions\nlnd_mod.Triangulation = at.Triangulation\nlnd_mod.circumsphere = at.circumsphere\nlnd_mod.simplex_volume_in_embedding = at.simplex_volume_in_embedding\nlnd_mod.point_in_simplex = at.point_in_simplex\n\n# Now use LearnerND as normal — including neighbor-aware losses\n# like curvature_loss_function()\nlearner = LearnerND(my_function, bounds=[(-1, 1), (-1, 1)])\n```\n\nSee [`examples/adaptive_learnernd.py`](examples/adaptive_learnernd.py) for a full working example with timing comparison.\n\n## API\n\n### `Triangulation` class\n\n```python\ntri = Triangulation(coords)           # Build from initial points\ntri.add_point(point)                   # Incremental insertion → (deleted, added)\ntri.locate_point(point)                # Find containing simplex\ntri.circumscribed_circle(simplex)      # → (center, radius)\ntri.volume(simplex)                    # Simplex volume\ntri.volumes()                          # All simplex volumes\ntri.point_in_simplex(point, simplex)   # Containment test\ntri.simplices_containing(point)        # All simplices containing a point, in one call\n                                       # instead of a point_in_simplex loop; pass a known\n                                       # containing simplex via simplex=... to skip the\n                                       # locate step, or restrict with candidates=...\ntri.point_in_circumcircle(pt, simplex) # Circumcircle test\ntri.bowyer_watson(pt_index)            # Direct Bowyer-Watson\ntri.get_opposing_vertices(simplex)     # Facet neighbours' opposite vertices\ntri.get_simplices_attached_to_points(simplex)  # Facet-sharing neighbours\ntri.reference_invariant()              # Consistency check\n```\n\n**Properties:** `vertices`, `simplices`, `vertex_to_simplices`, `hull`, `dim`, `default_transform`\n\n### Standalone functions\n\n```python\nfrom adaptive_triangulation import (\n    circumsphere,              # General circumsphere\n    fast_2d_circumcircle,      # Optimized 2D\n    fast_3d_circumsphere,      # Optimized 3D\n    point_in_simplex,          # Containment test\n    volume,                    # Simplex volume\n    simplex_volume_in_embedding,  # Volume in embedding space\n    default_loss,              # LearnerND's default loss (embedded simplex volume),\n                               # signature-compatible with loss_per_simplex\n    orientation,               # Face orientation\n)\n```\n\n## Examples\n\n- [`examples/basic_usage.py`](examples/basic_usage.py) — Core API walkthrough\n- [`examples/adaptive_learnernd.py`](examples/adaptive_learnernd.py) — LearnerND integration with timing\n- [`examples/benchmark_vs_python.py`](examples/benchmark_vs_python.py) — Standalone benchmarks across dimensions\n\n## Robustness on degenerate input\n\nPoint sets that mix widely separated coordinate scales force sliver simplices that no floating-point predicate can handle reliably.\nUnlike the Python reference (which can corrupt its state on such input), this implementation validates every insertion before mutating: a cavity that cannot be re-triangulated is first repaired with exact predicates (Shewchuk's, via the [`robust`](https://crates.io/crates/robust) crate), and if even that fails the insertion raises with the triangulation untouched, so callers can skip the point and continue.\nWell-conditioned inputs behave identically to the reference.\nThe full policy is documented in [`src/tolerances.rs`](src/tolerances.rs).\n\n## Development\n\n```bash\n# Build (requires Rust toolchain)\npip install maturin\nmaturin develop --release\n\n# Tests\ncargo test                    # Rust tests\npython -m pytest tests/ -v    # Python tests\n\n# Linting\npre-commit run --all-files    # ruff, mypy, cargo fmt, cargo clippy\n```\n\n## License\n\nBSD-3-Clause\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpython-adaptive%2Fadaptive-triangulation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpython-adaptive%2Fadaptive-triangulation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpython-adaptive%2Fadaptive-triangulation/lists"}