Skip to content

Mesh: differentiable dense displacement and sparse control-point morphing#1786

Merged
mehdiataei merged 15 commits into
NVIDIA:mainfrom
mehdiataei:morphing
Jul 9, 2026
Merged

Mesh: differentiable dense displacement and sparse control-point morphing#1786
mehdiataei merged 15 commits into
NVIDIA:mainfrom
mehdiataei:morphing

Conversation

@mehdiataei

Copy link
Copy Markdown
Collaborator

PhysicsNeMo Pull Request

Description

Adds differentiable mesh morphing to physicsnemo.nn.functional and the mesh API:

  • displace_points / Mesh.displace: dense per-point displacement with an optional scalar amount and per-point bool/float weights.
  • morph_points / Mesh.morph / DomainMesh.morph: sparse control-point morphing via a compactly supported Wendland-C2 Shepard field with a stationary background. DomainMesh.morph evaluates one field over the interior and all boundaries so coincident points stay coincident.

Both ops support float32/float64, unbatched and batched inputs, and first-order gradients through all tensor inputs (points, controls, displacements, radii, amount, weights). Auto dispatch uses Torch on CPU and Warp on CUDA. The Warp dense-displacement backend is opt-in only (Torch is default everywhere) and exists for backend parity and benchmarking.

Tests cover analytic derivatives, gradcheck on both backends, opcheck, CUDA-graph capture, and torch.compile fullgraph. Includes API docs, a tutorial section, and benchmark registry entries.

Checklist

Checklist

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.

@copy-pr-bot

copy-pr-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds differentiable mesh morphing to PhysicsNeMo via two new operations: displace_points/Mesh.displace for dense per-point displacement and morph_points/Mesh.morph/DomainMesh.morph for sparse control-point morphing using a compactly-supported Wendland-C2 Shepard field. The implementation supports float32/float64, batched and unbatched inputs, and first-order gradients through all differentiable inputs.

  • Torch and Warp backends: The Torch backend uses numerically robust chunked computation (log-space stabilization near controls, scaled Shepard quotient) and activation checkpointing. The Warp backend registers custom ops for CUDA Graph compatibility.
  • API layers: Functional ops (displace_points, morph_points) surface through Mesh.displace, Mesh.morph, and DomainMesh.morph with flexible input resolution (raw tensors or point_data keys). DomainMesh.morph concatenates all components into one shared field evaluation to guarantee coincident boundary-interior points stay coincident.

Important Files Changed

Filename Overview
physicsnemo/nn/functional/geometry/morphing/_torch_impl.py Core Torch backend for dense displacement and compact Shepard morphing. Log-space stabilization, pairwise chunking with activation checkpointing, and mathematically verified Shepard quotient.
physicsnemo/nn/functional/geometry/morphing/_warp_impl/op.py Warp custom-op integration with registered autograd, AOT-compatible backward ops, and CUDA Graph-safe scoped stream launches.
physicsnemo/nn/functional/geometry/morphing/_warp_impl/kernels.py Warp CUDA kernels for forward and backward passes. shepard_point_backward zero-initializes grad_points before accumulation.
physicsnemo/nn/functional/geometry/morphing/morphing.py FunctionSpec-based dispatch layer. MorphPoints overrides dispatch to prefer Warp on CUDA; DisplacePoints always uses Torch.
physicsnemo/nn/functional/geometry/morphing/_utils.py Input normalization and validation. Thorough checks on shapes, dtypes, devices, and scalar domains.
physicsnemo/mesh/domain_mesh.py DomainMesh.morph concatenates all component point clouds for a shared morph field. Missing spatial-dimension check in the pre-loop validation.
physicsnemo/mesh/mesh.py Adds Mesh.displace and Mesh.morph delegation methods with comprehensive docstrings.
physicsnemo/mesh/transformations/geometric.py Functional displace and morph wrappers, point_data key resolver, and cache-invalidating mesh helper.
test/nn/functional/geometry/test_morphing.py Comprehensive test suite: analytical gradients, gradcheck, opcheck, CUDA graph capture, and torch.compile fullgraph.
test/mesh/transformations/test_mesh_morphing.py Mesh-level tests for displace and morph including DomainMesh coincidence guarantee.

Reviews (1): Last reviewed commit: "Mesh: differentiable dense displacement ..." | Re-trigger Greptile

Comment thread physicsnemo/nn/functional/geometry/deform/_torch_impl.py
Comment thread physicsnemo/mesh/domain_mesh.py Outdated
@peterdsharpe

Copy link
Copy Markdown
Collaborator

Review in-progress! Found a few edge-case bugs; added failing tests to demonstrate. (Will follow up shortly with comments)

Comment thread physicsnemo/nn/functional/geometry/morphing/_utils.py Outdated
Comment thread physicsnemo/mesh/domain_mesh.py Outdated
Comment thread physicsnemo/nn/functional/geometry/deform/_torch_impl.py
Comment thread physicsnemo/mesh/transformations/geometric.py Outdated
Comment thread physicsnemo/mesh/transformations/geometric.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/_warp_impl/op.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/_warp_impl/op.py Outdated
Comment thread physicsnemo/mesh/domain_mesh.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/_torch_impl.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/morphing.py Outdated

@peterdsharpe peterdsharpe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up to the review above with the chunked-backward analysis (the earlier review went out without its summary, so it's included here).

Summary of the full review — 15 inline comments: 6 correctness items, 1 dispatch-diagnostics item, 8 maintainability items. I pushed two commits (ce5463b, b9d6d5b) adding failing regression tests that pin down the confirmed correctness items; CI is expected to be red on exactly these seven until they're addressed:

  • test_torch_control_chunked_gradients_match_unchunked_on_ties — cross-chunk tie gradient corruption (comment below; one-line fix suggested)
  • test_torch_compile_rejects_invalid_python_scalars_like_eager
  • test_infinite_tensor_radius_agrees_across_backends
  • test_mesh_point_data_path_through_leaf_has_actionable_diagnostic
  • test_mesh_displace_rejects_weights_resolving_to_tensordict
  • test_mesh_morph_rejects_non_tensor_control_points
  • test_domain_morph_clones_domain_global_data

Overall this is a very carefully engineered piece of numerics — the overflow-safe normalized distances and the detached-reference compensation algebra are impressive. The correctness items are all in the seams (chunk boundaries, compile-vs-eager, backend parity, API-boundary validation) rather than the core math.

Comment thread physicsnemo/nn/functional/geometry/morphing/_torch_impl.py Outdated
Comment thread test/nn/functional/geometry/test_point_deform.py
Comment thread physicsnemo/mesh/transformations/geometric.py Outdated
@coreyjadams

Copy link
Copy Markdown
Collaborator

Heads up @mehdiataei : there is a change coming to how we wrap warp streams in physicsnemo, seen here: #1771

tl;dr - we will have a small custom wrapper to replace ScopedStream. If your pr merges before #1771 we may want to patch it after. Want to make sure you're aware. You can read the summary in #1771 to understand the motivation but it's stream safety issues.

@peterdsharpe peterdsharpe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second batch: API/design review (the first batch covered correctness). The implementation quality here is genuinely high — strict validation with actionable errors, radius required with no scale-presuming default, displacements-not-destinations stated everywhere, and the raw Shepard field evaluator deliberately kept private (easy to add later, impossible to remove). The comments below are all about the public contract, since that's the part we maintain forever; five of them carry GitHub suggestions that can be applied directly.

Summary of asks, roughly by weight:

  1. Keep DisplacePoints/MorphPoints out of the top-level nn.functional namespace (subpackage export matches every other spec class).
  2. Drop amount (linearity makes it strictly redundant) and rename weightspoint_weights.
  3. Stub the Mesh.displace/Mesh.morph method docstrings per the translate convention — the four full copies are already drifting.
  4. Drop the Warp backend for DisplacePoints — auto-dispatch never selects it, and a fused axpy doesn't earn ~8 kernels of permanent contract.
  5. Curate the new transformations package exports deliberately (include or exclude rotation_matrix/scale_matrix; absolute imports like the siblings).
  6. Add kernel="wendland_c2" to the morph APIs to name the algorithm at call sites and reserve the extension point.

One open question rather than an ask: DomainMesh.morph exists but DomainMesh.displace doesn't — the weights-as-common-key mechanism would transfer directly, so if the asymmetry is intentional (dense per-component displacement doesn't share a world-space field), a line in the DomainMesh docs saying so would preempt the feature request.

Comment thread physicsnemo/nn/functional/__init__.py Outdated
Comment thread physicsnemo/nn/functional/__init__.py Outdated
Comment thread physicsnemo/mesh/transformations/__init__.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/morphing.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/morphing.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/morphing.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/morphing.py Outdated
Comment thread physicsnemo/mesh/mesh.py Outdated
Comment thread physicsnemo/mesh/mesh.py Outdated
Comment thread docs/api/mesh/transformations.rst Outdated
Comment thread docs/api/mesh/transformations.rst Outdated
@mehdiataei

Copy link
Copy Markdown
Collaborator Author

Thanks @peterdsharpe for the review! All addressed. Please take a look.

@mehdiataei
mehdiataei requested a review from peterdsharpe July 8, 2026 02:39

@peterdsharpe peterdsharpe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great - thank you for addressing those issues! Just a few last things to flag, in addition to the unresolved threads (one on the f32/f64 kernel deduplication, and one on the dispatch-fallback duplication), adding just a few more cleanup things.

We should probably add a DomainMesh tutorial showing off some of these cool morph features, but let's save that for a follow-up PR - this one is already beefy and lands the numerical core. Looking forward to having this merged!

Comment thread physicsnemo/nn/functional/geometry/morphing/_warp_impl/kernels.py Outdated
Comment thread physicsnemo/nn/functional/geometry/morphing/_warp_impl/kernels.py Outdated
Comment thread physicsnemo/nn/functional/geometry/deform/deform.py
Comment thread test/nn/functional/geometry/test_morphing.py Outdated
Comment thread test/nn/functional/geometry/test_morphing.py Outdated
mehdiataei and others added 6 commits July 8, 2026 12:01
…hing (Torch + Warp)

Signed-off-by: Mehdi Ataei <ataei8@gmail.com>
Each test captures a confirmed defect found while reviewing the
dense-displacement / control-point morphing implementation:

- torch.compile silently skips the Python-scalar radius/amount
  validation that eager execution enforces, producing wrong geometry
  instead of the eager ValueError
- the Torch and Warp backends return opposite geometry for a tensor
  radius containing +inf (zero vs. full displacement)
- point_data paths that descend into a leaf tensor surface raw
  tensordict internals instead of the designed KeyError diagnostic
- a weights key resolving to a nested TensorDict silently produces a
  Mesh whose points attribute is a TensorDict rather than a tensor
- Mesh.morph reports AttributeError instead of a descriptive TypeError
  for non-tensor control points, unlike DomainMesh.morph
- DomainMesh.morph aliases the source domain's global_data instead of
  cloning it like every other DomainMesh operation
A query point exactly equidistant from two controls in different
control chunks receives wrong point/control/radius gradients from the
chunked eager path: the reference selection in
_compact_shepard_query_chunk uses strict '<' while the differentiable
running minimum uses torch.minimum, whose backward splits the gradient
50/50 across exact ties, breaking the detached-reference compensation
algebra. Unchunked, Warp, and central finite differences all agree;
the chunked path is the outlier. Forward values and displacement
gradients match, so the corruption is silent.
Signed-off-by: Mehdi Ataei <ataei8@gmail.com>
Signed-off-by: Mehdi Ataei <ataei8@gmail.com>
Signed-off-by: Mehdi Ataei <ataei8@gmail.com>
mehdiataei and others added 2 commits July 8, 2026 13:06
Signed-off-by: Mehdi Ataei <ataei8@gmail.com>
Auto-dispatch now resolves the device-based default and delegates to
FunctionSpec.dispatch instead of duplicating its explicit-implementation
branch. When CUDA inputs fall back to Torch because Warp is unavailable,
the standard one-time RuntimeWarning from _warn_fallback now fires
instead of silently selecting the slower backend; the dispatch test
asserts the warning on the fallback path.
Rewrite the float32/float64 kernel twins as generic Warp kernels
(typing.Any annotations with type()-derived scalar constants) and
instantiate both precisions with wp.overload, so one numerical
definition serves both dtypes by construction. The exported kernel
names and launch signatures are unchanged, so op.py needs no edits.

The only behavioral cleanup is the no-active-control scan: instead of
comparing against per-precision maximum-float sentinels, the reference
minimum initializes to 2.0 (active distances always satisfy q < 1) and
reference_index == -1 is the authoritative no-active flag. Forward
values, auxiliaries, and gradients are unchanged; the full parity,
gradcheck, opcheck, and CUDA-graph suites pass on both precisions.

@peterdsharpe peterdsharpe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! Thanks for working through the review process!

@peterdsharpe

Copy link
Copy Markdown
Collaborator

/ok to test 8e29ea8

Signed-off-by: Mehdi Ataei <ataei8@gmail.com>
@mehdiataei

Copy link
Copy Markdown
Collaborator Author

/ok to test 4e6cb98

@mehdiataei
mehdiataei enabled auto-merge July 9, 2026 16:57

@loliverhennigh loliverhennigh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm

@ktangsali ktangsali left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mehdiataei

Copy link
Copy Markdown
Collaborator Author

/ok to test b5f56d3

Signed-off-by: Mehdi Ataei <ataei8@gmail.com>
@mehdiataei

Copy link
Copy Markdown
Collaborator Author

/ok to test 24a9348

@mehdiataei
mehdiataei added this pull request to the merge queue Jul 9, 2026
Merged via the queue into NVIDIA:main with commit 423c7ac Jul 9, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants