Mesh: Add differentiable shrinkwrap deformation - #1886
Conversation
CODEOWNERS review mapCurrent for commit ⏳ @loliverhennigh — 10 file(s)
⏳ @megnvidia — 2 file(s)
⏳ @peterdsharpe — 9 file(s)
No CODEOWNER
Comment |
Greptile SummaryAdds a differentiable nearest-surface shrinkwrap deformation.
Important Files Changed
Reviews (1): Last reviewed commit: "Mesh: Add differentiable shrinkwrap defo..." | Re-trigger Greptile |
Signed-off-by: Mehdi Ataei <ataei8@gmail.com>
| ) | ||
| wrapped.square().mean().backward() | ||
|
|
||
| Source points can have shape ``(N, 3)`` or ``(B, N, 3)``. One triangle target |
There was a problem hiding this comment.
| Source points can have shape ``(N, 3)`` or ``(B, N, 3)``. One triangle target | |
| Source points use one of these shapes: | |
| - ``(N, 3)`` | |
| - ``(B, N, 3)`` | |
| One triangle target is shared across the source batches. Keep the following | |
| projection rules in mind: | |
| - A positive ``offset`` follows target face winding. | |
| - A finite ``max_distance`` leaves points at or beyond the cutoff unchanged. | |
There was a problem hiding this comment.
then delete lines 325-327
| unchanged. | ||
|
|
||
| Nearest-face selection, closest-feature changes, and distance gating are | ||
| discrete. Between those transitions, gradients propagate through source |
There was a problem hiding this comment.
| discrete. Between those transitions, gradients propagate through source | |
| discrete. Between those transitions, gradients propagate through the following | |
| values: | |
| - Source points | |
| - Selected target vertices | |
| - Floating-point weights | |
| - A tensor-valued ``offset`` | |
| Target connectivity is not differentiable. |
|
|
||
| Nearest-face selection, closest-feature changes, and distance gating are | ||
| discrete. Between those transitions, gradients propagate through source | ||
| points, selected target vertices, floating point weights, and a tensor-valued |
There was a problem hiding this comment.
| points, selected target vertices, floating point weights, and a tensor-valued |
| Nearest-face selection, closest-feature changes, and distance gating are | ||
| discrete. Between those transitions, gradients propagate through source | ||
| points, selected target vertices, floating point weights, and a tensor-valued | ||
| ``offset``. Target connectivity is not differentiable. |
There was a problem hiding this comment.
| ``offset``. Target connectivity is not differentiable. |
| unsafe coordinate magnitudes or face geometry. | ||
|
|
||
| Shrinkwrap performs data-dependent validation and nearest-face search setup. | ||
| CUDA executions with either backend are not supported inside CUDA Graph |
There was a problem hiding this comment.
| CUDA executions with either backend are not supported inside CUDA Graph | |
| Do not run CUDA executions with either backend inside CUDA Graph capture. | |
|
|
||
| Shrinkwrap performs data-dependent validation and nearest-face search setup. | ||
| CUDA executions with either backend are not supported inside CUDA Graph | ||
| capture. |
There was a problem hiding this comment.
| capture. |
| adjacent faces. Their closest point is the same, but a nonzero face-normal | ||
| offset can differ. | ||
|
|
||
| Float64 targets use the Torch search because Warp searches in float32. Safe |
There was a problem hiding this comment.
| Float64 targets use the Torch search because Warp searches in float32. Safe | |
| Search backend rules differ by dtype and geometry: | |
| - ``float64`` targets use the Torch search because Warp searches in ``float32``. | |
| - Warp searches safe ``float32`` coordinates unchanged. | |
| - Warp falls back to Torch for unsafe coordinate magnitudes or face geometry. |
|
|
||
| Torch provides the reference nearest-face search. Warp accelerates that search | ||
| on CPU and CUDA. Both backends evaluate the selected projection with PyTorch | ||
| in the input dtype. Exact ties at target edges or vertices can select different |
There was a problem hiding this comment.
on the fence about whether dtype should be formatted as dtype or not. Prevailing advice would be to stay consistent within this file, so I will leave that as is.
| and Warp provides first-order GPU kernels. | ||
| - Adds differentiable nearest-surface shrinkwrap through | ||
| `shrinkwrap_points` and `Mesh.shrinkwrap`. Torch provides the reference | ||
| search, NVIDIA Warp accelerates float32 nearest-face queries on CPU and CUDA, |
There was a problem hiding this comment.
| search, NVIDIA Warp accelerates float32 nearest-face queries on CPU and CUDA, | |
| search, NVIDIA Warp accelerates ``float32`` nearest-face queries on CPU and CUDA. |
| - Adds differentiable nearest-surface shrinkwrap through | ||
| `shrinkwrap_points` and `Mesh.shrinkwrap`. Torch provides the reference | ||
| search, NVIDIA Warp accelerates float32 nearest-face queries on CPU and CUDA, | ||
| and both backends replay the selected projection with PyTorch autograd. |
There was a problem hiding this comment.
| and both backends replay the selected projection with PyTorch autograd. | |
| Both backends replay the selected projection with PyTorch autograd. |
| return False | ||
|
|
||
| target_f32 = target_points.detach() | ||
| target_magnitude_is_safe = torch.isfinite(target_f32).all() & ( |
There was a problem hiding this comment.
[bug][P1] This magnitude check does not make Warp's closest-point search numerically safe. Warp's closest_point_to_triangle first forms O(s²) dot products and then products such as d1 * d4 - d3 * d2 (O(s⁴)), so coordinates well below _WARP_SAFE_QUERY_MAGNITUDE can still overflow; the inverse occurs at small scales.
For example, with s = 1e12,
target / s = [
[-4, -4, -4], [5, -5, 4], [1, -2, -4],
[-1, 3, 2], [4, -2, -1], [-4, -2, -5],
]
faces = [[0, 1, 2], [3, 4, 5]]
query / s = [[0, -2, -4]]all as float32, _float32_target_search_is_safe returns true, but Torch selects face 0 while Warp—and therefore the default CUDA path—selects face 1. Measuring the replayed outputs in float64 shows Warp's projection is 2.408× farther from the query. Explicit Warp on CPU reports no hit. In randomized targets admitted by this gate, I saw 2,011/4,608 face mismatches at scale 1e18 and 3,781/4,608 at 1e-10.
Could we either uniformly rescale into a range proven safe for all Warp intermediates, or conservatively fall back to Torch outside verified lower and upper edge-scale bounds? Please add CPU and CUDA regression cases at both ends of the accepted scale range.
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Render selected cap repair on a broad optimized lid dome. |
There was a problem hiding this comment.
This file is effectively a second copy of examples/minimal/mesh/shrinkwrap_solid_surface.py, let's deduplicate.
| @@ -0,0 +1,487 @@ | |||
| # SPDX-FileCopyrightText: Copyright (c) 2023 - 2026 NVIDIA CORPORATION & AFFILIATES. | |||
There was a problem hiding this comment.
This file breaks repo conventions; every other file in this folder is an ipynb.
| @@ -0,0 +1,832 @@ | |||
| # SPDX-FileCopyrightText: Copyright (c) 2023 - 2026 NVIDIA CORPORATION & AFFILIATES. | |||
There was a problem hiding this comment.
This file breaks repo conventions; every other file in this folder is an ipynb.
| path. Because its backward uses atomic accumulation at shared vertices, Warp | ||
| gradient results can have small run-to-run floating-point differences. | ||
|
|
||
| Nearest-Surface Shrinkwrap |
There was a problem hiding this comment.
I doesn't look like shrinkwrap (the operation) is defined here; can we add a definition for users?
Summary
shrinkwrap_pointsandMesh.shrinkwrapMotivation
Mesh optimization workflows often need to conform selected vertices to a surface constraint without discarding the rest of the optimized geometry. This adds a connectivity-preserving projection that can participate in adjoint-based optimization with respect to both source and target coordinates.
Nearest-face selection is discrete. With that selection fixed, the projection is first-order differentiable through the continuous geometry.
API
The tensor-level API is available as
physicsnemo.nn.functional.shrinkwrap_points.Visual examples
Validation
942 passed, 9 skippedacross deformation, shrinkwrap, newly landed deformation-energy, mesh-wrapper, and functional-benchmark tests