Add Tutorial 9: mesh generation (stacked on #1800, #1801)#1802
Draft
peterdsharpe wants to merge 60 commits into
Draft
Add Tutorial 9: mesh generation (stacked on #1800, #1801)#1802peterdsharpe wants to merge 60 commits into
peterdsharpe wants to merge 60 commits into
Conversation
6 tasks
1d6eb81 to
e422022
Compare
6 tasks
b622db8 to
6d48224
Compare
Mesh-native interior filling: a closed codimension-one boundary Mesh in (2D edge loops; any order/orientation, holes, multiple components, and nested islands resolved automatically), a quality volume Mesh out. Engine: from-scratch constrained Delaunay triangulation (Bowyer-Watson + Sloan segment recovery), even-odd hole removal, Ruppert refinement, and optional bound-preserving ODT smoothing. Contract: input vertices are preserved bit-identically (leading rows, input order), boundary facets are only ever subdivided, every output triangle meets the guaranteed minimum-angle bound, and identical inputs give bitwise-identical outputs. Provenance ships on the output as point_data (boundary_marker, source_point). The contract is dimension-generic; n=3 raises NotImplementedError pending exact boundary recovery. Includes 58 tests, API docs, and tutorial 8 updates.
Dimension-generic simplex mesh generation for implicit domains
{x : phi(x) < 0} — SDFs, level sets, neural fields — in 2D/3D/ND on CPU
or CUDA, in pure PyTorch tensor ops.
Pipeline: Kuhn-lattice initialization, validity-gated ODT smoothing with
boundary projection, quality-greedy bistellar flips (Radon-partition
formulation; no exact arithmetic, pure torch), pinch splitting and boundary-pancake peeling.
Structurally robust: every update is gated and the budget fixed, so a
valid, watertight mesh is always returned; difficult inputs degrade
quality (reported in diagnostics), never existence. A coverage guard
raises when the domain has features below the target edge length instead
of dropping them silently; feature_points pins sharp corners exactly via
topological insertion. refit_mesh_to_implicit re-projects the boundary
onto phi=0 with graph-preserving Newton steps at fixed topology, so
d(mesh)/d(shape parameters) flows to autograd. Measured on an RTX 4090:
738k tets end-to-end in ~6-10 s, valid, 2.7% slivers.
Includes 68 tests and API docs with a 'choosing a
mesher' comparison.
…utorial # Conflicts: # CHANGELOG.md
A visual, executable guide to the mesh-generation tools: exact-boundary filling with provenance (gear/star/island domain), the minimum-angle guarantee verified across resolutions, implicit-domain galleries (CSG, raw level sets, near-tangent unions), corner pinning with feature_points, a 3D tet-meshed shell cutaway, and differentiable meshing (dV/dr through the mesh vs the analytic perimeter). Also lists mesh generation as a first-class workflow in the mesh docs overview.
d481b9b to
8371ebb
Compare
for more information, see https://pre-commit.ci
Restores the pre-commit config to main's version: the ci-skip for import-linter is repo-wide infrastructure and belongs to the maintainers (pre-commit.ci cannot run language-system hooks, so that check will fail until addressed upstream; the import contract remains enforced by the 'Run pre-commit hooks' GitHub Action).
Restores the pre-commit config to main's version: the ci-skip for import-linter is repo-wide infrastructure and belongs to the maintainers (pre-commit.ci cannot run language-system hooks, so that check will fail until addressed upstream; the import contract remains enforced by the 'Run pre-commit hooks' GitHub Action).
Restores the pre-commit config to main's version: the ci-skip for import-linter is repo-wide infrastructure and belongs to the maintainers (pre-commit.ci cannot run language-system hooks, so that check will fail until addressed upstream; the import contract remains enforced by the 'Run pre-commit hooks' GitHub Action).
…/peterdsharpe/physicsnemo into psharpe/mesh-generation-tutorial
- fill_interior raises a descriptive ValueError for a boundary Mesh with zero edges (previously an opaque RuntimeError from torch.cat). - Loop-nesting containment now probes with polygon_interior_point rather than each loop's first vertex, which could lie exactly on another loop's edge where the even-odd test is arbitrary.
- _coverage_gap processes probe rows in chunks of 256, bounding the distance-matrix allocation that could exceed GPU memory on fine meshes. - sdf_polygon_2d now honors the documented (..., d) implicit-function contract by flattening and restoring leading batch dimensions. - Fix a doctest that built a one-element tuple instead of the tensor.
fill_interior no longer claims keys in the user-owned point_data namespace by default; boundary_marker and source_point are attached only when provenance=True. Tests assert the default output has empty point_data; tutorial 8 and docs updated.
for more information, see https://pre-commit.ci
- Crossing loops that land in different containment components now raise ValueError (bbox-prefiltered pairwise segment intersection); previously they silently produced a double-covered mesh. Regression test added. - A boundary whose loops all sit at odd containment depth (coincident duplicates) raises a descriptive error instead of a bare torch.cat one. - Docstring: the bit-identical guarantee is scoped to vertices referenced by an edge; removed references to a module that does not exist yet. - Loop nesting now reuses the engine's vectorized _points_in_polygon (one call per loop, 13x faster at 400 loops) and loop extraction uses plain Python adjacency (41x faster at 100k edges); private helpers annotated. - New tests: crossing loops, coincident duplicates, device round-trip, and the boundary-subdivision contract (output boundary edges lie on input segments; total length equals the input perimeter).
- Coverage guard judges projection convergence in DISTANCE units (|phi|/|grad phi| < 0.05h) instead of raw |phi|, scales its probe count with the box-to-h ratio (capped), and returns +inf — a guard failure — when no probe converges; a quantized/noisy level set can no longer silently disable it. Regression test with float-quantized phi. - pin_feature_points gains vertex-coincidence and on-facet-split cases, and tents are half-space-tested against the owner cell so they can never overlap the mesh (previously undetectable). Tests added. - phi-unit thresholds (erosion, peel, escape band) are normalized by the sampled gradient magnitude near the zero set: phi = c*sdf now behaves identically for any c > 0, matching the documented contract. Scale regression test at c = 1e-3, 1, 1e3. - Escape-band cache fully refreshed every reconnect_every iterations, bounding drift staleness. - q_p01 diagnostic uses kthvalue (torch.quantile hard-fails above 2^24 cells); flip volume-conservation tolerance is dtype-aware (a fixed 1e-9 rejected ~11% of legitimate float32 flips); flip priorities are float64 with a deterministic index tie-break. - Typed overloads for full_output (Literal[True/False]); pinned vertex indices returned in diagnostics; dead code removed; bounds-tightness memory note documented.
…/peterdsharpe/physicsnemo into psharpe/mesh-generation-tutorial
Mesh generation gets its own dedicated tutorial (part 3 of this series), where fill_interior and the implicit-domain mesher can be taught side-by-side with the choosing-between-them framing; a generation section inside the I/O tutorial was off-topic and would duplicate it.
In 2D both Radon classes have size k_share, so identifying the current configuration by class size always resolved the tie to the negative SVD sign class -- whose sign is arbitrary -- and proposed the identity retriangulation (zero gain, silently rejected) for about half of all improving 2-2 flips, deterministically per geometry: affected quads never improved regardless of seed. Identify the target class by membership instead -- the sub-simplex shared by all current cells, whose vertices generate the new cells by removal -- keeping the Radon sign pattern purely as a validity filter. This also covers the 4D 3-3 tie; 3D decisions are unchanged (the sign rule and the membership rule provably coincide when the class sizes differ, and end-to-end 3D quality/flip counts are identical). Found by adversarial review on NVIDIA#1801 (242/477 improving diagonal flips silently missed in the reviewer's harness; 0/477 after this change).
A zero-length segment (repeated consecutive vertex, common in real polygon data) made the point-to-segment parameter t = 0/0 = NaN, and the min over segments then poisoned EVERY query point -- downstream, mesh_implicit_domain misdiagnosed the polygon as a NaN neural field. Clamp the denominator so a degenerate segment reduces to distance-to-point; collinear vertices were already handled. Found by adversarial audit of NVIDIA#1801.
…rd failures Fixes for the adversarial review findings on NVIDIA#1801, plus hardening found while stress-testing the fixes: - Box-touching domains silently detached from the box faces: face vertices Newton-projected onto the only zero set the raw phi has (the interior one), so a "box minus obstacle" external-flow domain lost 10-48% of its volume with zero face vertices left and the coverage guard blind to it. The meshed set is now explicitly {phi < 0} intersected with the box: phi is clipped by the box's own SDF (scaled into phi's gradient units), making the faces first-class zero set, and face vertices are pinned per-coordinate so the box's own edges and corners stay exact instead of chamfering at the h scale. refit_mesh_to_implicit gains an optional bounds= argument for the same case. Measured: half-plane area 1.740 -> 1.992 of 2.0 (residual is domain-corner rounding, closable with feature_points); box-minus-disk 2D loss 9.9% -> 0.0%; box-minus-sphere 3D 25% -> 0.1%; 733k-tet GPU benchmark unchanged in wall time and quality. - The coverage guard blessed unmeasurable input: probes landing in a NaN phi region (a neural field queried outside its training range) were silently dropped from the gap max, reporting near-perfect coverage of a half-missing domain, and a gradient-free phi (step function) took the no-gradient early return of 0.0 for a staircase boundary. Failure to measure is now failure: NaN at any probe and no-gradient-with-a-sign-change both return +inf, and a Monte-Carlo volume cross-check (from the probe signs; census always 65536 probes) catches covered-but-hollow meshes -- the two-sided complement to the zero-set gap. Stalled projections are retried at 4x iterations, then dropped rather than failed: a probe oscillating in a positive CSG dead zone (e.g. between an obstacle and the box) certifies nothing and hides nothing, and hard-failing on it rejects in-contract composites. - The validity gate classified NaN cell volumes as good (every comparison is False for NaN), letting a NaN projection target replace a valid vertex with the guard disabled. - Boundary projections that fail to land near the zero set now anchor their vertex: with a discontinuous or plateaued phi the Newton step strands vertices in regions with no zero set, and the unanchored staircase then creeps outward. Erosion uses the unclipped phi -- the lattice conforms to the box exactly, and the box term spuriously eroded whole face layers whenever ceil-rounding made the lattice spacing smaller than nominal h. - Peeling can expose a boundary pinched at a vertex, which the ridge-pairing manifoldness check cannot see (every boundary ridge still pairs); every peel is now followed by a pinch split, keeping the documented closed-manifold contract.
megnvidia
requested changes
Jul 10, 2026
…utorial # Conflicts: # CHANGELOG.md
Applied here since NVIDIA#1800 already merged: tessellation.rst restructured per suggestion (title-case heading, shorter sentences, guarantee list, and the 'hard program' phrasing clarified to 'a substantially harder problem that requires its own implementation effort'), module docstring sentence structure, and the CHANGELOG entry rewording.
torch.cdist's matmul path computes |x - y| as sqrt(x^2 + y^2 - 2xy), whose cancellation returns ~1e-8 for IDENTICAL points; the compute-mode heuristics changed across torch versions, so the feature-point exact-interpolation assertions passed on torch 2.12 locally and failed on 2.15 in CI (off by exactly 2^-27). The mesher itself is unaffected -- its coincidence detection already used direct subtraction, and its argmin uses of cdist are residue-tolerant. Assertions tightened to 1e-12 now that the measurement is exact.
megnvidia
requested changes
Jul 13, 2026
Collaborator
Author
|
/ok to test d9c5bd8 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PhysicsNeMo Pull Request
Description
Note
Stacked PR — draft until its bases merge. This branch is stacked on #1800 (
tessellation.fill_interior) and #1801 (generate.mesh_implicit_domain); their diffs appear here until they land. Once both merge, this rebases to a docs-only diff (one tutorial notebook + README + docs-overview edits). Review only the last commit (Add Tutorial 9) until then.Part 3 of 3. Adds
examples/minimal/mesh/tutorial_9_mesh_generation.ipynb: a visual, executable guide to the mesh-generation tools and how to choose between them.Mesh)feature_points(side-by-side with the default rounding)Checklist
Dependencies
No new dependencies.
Review Process
All PRs are reviewed by the PhysicsNeMo team before merging.
Depending on which files are changed, GitHub may automatically assign a maintainer for review.
We are also testing AI-based code review tools (e.g., Greptile), which may add automated comments with a confidence score.
This score reflects the AI’s assessment of merge readiness and is not a qualitative judgment of your work, nor is
it an indication that the PR will be accepted / rejected.
AI-generated feedback should be reviewed critically for usefulness.
You are not required to respond to every AI comment, but they are intended to help both authors and reviewers.
Please react to Greptile comments with 👍 or 👎 to provide feedback on their accuracy.