Repository navigation
Closed
Labels
? - Needs TriageNeed team to review and classifyNeed team to review and classifybugSomething isn't workingSomething isn't working
Description
Version
2.3.0a0 (commit 7e646b4)
On which installation method(s) does this occur?
Source
Describe the issue
Several primitives in physicsnemo.mesh.primitives wind their cells the opposite way from the rest of the library.
The convention elsewhere is clear:
- A cell of a
Mesh[n, n]has a positive signed volume,det(v1 - v0, ..., vn - v0) > 0.validate(check_inverted_cells=True)flags every other cell as inverted. sphere_volume,cylinder_volumeandlumpy_ballcallorient_positiveto enforce this.- A closed
Mesh[2, 3]has outward normals, as insphere_icosahedral,sphere_uv,cube_surface,cylinderandlumpy_sphere. - The
cube_surfacedocstring states this convention: "counter-clockwise winding when viewed from outside". - A triangle in the xy plane wound counterclockwise has normal +z, as
basic.single_triangle_3dshows.
I loaded every primitive with its default arguments and measured the orientation. These disagree with the convention:
| Primitive | Measurement | Cause |
|---|---|---|
planar.unit_square |
8 of 8 cells inverted | planar/unit_square.py:61-62 |
planar.rectangle |
72 of 72 cells inverted | planar/rectangle.py:72-73 |
planar.structured_grid |
200 of 200 cells inverted | planar/structured_grid.py:87-88 |
planar.l_shape |
150 of 150 cells inverted | planar/l_shape.py:88-89, 113-114 |
procedural.perturbed_grid |
173 of 200 inverted, 27 positive | Inherits structured_grid. The default perturbation_scale=0.05 folds 27 cells. |
volumes.cube_volume |
375 of 750 inverted (24 of 48 at subdivisions=2) |
volumes/cube_volume.py:90-95. Patterns [0, 1, 5, 7], [0, 2, 3, 7] and [0, 4, 6, 7] are odd permutations. |
surfaces.torus |
Normals point into the tube. Mean curvature is -1.588, signed volume -1.751. | surfaces/torus.py:86-87 |
surfaces.hemisphere |
All 840 normals point toward the center | surfaces/hemisphere.py:81-82 |
surfaces.disk, surfaces.plane |
Normals are -z for a mesh in the xy plane | surfaces/disk.py:78-85, 102-103, surfaces/plane.py:107-108 |
pyvista_datasets.tetbeam |
200 of 200 cells inverted | The PyVista data is inverted too. The wrapper does not call orient_positive. |
The torus signed volume should be +2π²Rr² = +1.777 for the defaults.
These primitives agree with the convention:
basic: allplanar:annulus_2d,circle_2d,equilateral_triangle,regular_polygonsurfaces:cone,cube_surface,cylinder,cylinder_open,icosahedron_surface,octahedron_surface,sphere_icosahedral,sphere_uv,tetrahedron_surfacevolumes:cylinder_volume,tetrahedron_volumeprocedural:lumpy_ball,lumpy_spherepyvista_datasets:hexbeam,ant,cow
Two related observations:
curves.circle_2dandcurves.ellipse_2drun counterclockwise. The 2D edge normal rotates the tangent counterclockwise, so their normals point inward.volumes.sphere_volumehas 4 of 1164 cells with negative volume. Each has |V| < 1.5e-12, so these are slivers fromdelaunay_3d, not a winding error. PR Mesh: fix degenerate and overlapping cells in primitive generators #2129 replaces that construction.
Expected: every primitive follows the convention above. Actual: the primitives in the table load reversed. Tests and examples built on them see inverted cells, inward normals and negative curvature.
Minimum reproducible example
python - <<'EOF'
from physicsnemo.mesh.primitives import planar, surfaces, volumes
sq = planar.unit_square.load(subdivisions=1)
print("unit_square inverted:", sq.validate(check_inverted_cells=True)["n_inverted_cells"], "of", sq.n_cells)
cube = volumes.cube_volume.load(subdivisions=2)
print("cube_volume inverted:", cube.validate(check_inverted_cells=True)["n_inverted_cells"], "of", cube.n_cells)
for name in ["sphere_icosahedral", "torus"]:
m = getattr(surfaces, name).load()
print(name, "mean curvature:", round(m.mean_curvature_vertices.mean().item(), 3))
EOFRelevant log output
unit_square inverted: 8 of 8
cube_volume inverted: 24 of 48
sphere_icosahedral mean curvature: 1.0
torus mean curvature: -1.588Environment details
+ Environment location: Bare-metal, CPU only (CUDA_VISIBLE_DEVICES unset)
+ Python 3.13.8, torch 2.13.0+cu130, tensordict 0.15, warp 1.17.0Metadata
Metadata
Assignees
Labels
? - Needs TriageNeed team to review and classifyNeed team to review and classifybugSomething isn't workingSomething isn't working
Activity
dhvani7014 commentedon Oct 19, 2026
Hi @peterdsharpe, I'd like to work on this unless you're already planning to as part of the mesh audit series. I'd fix the winding in each listed generator (and call orient_positive in the tetbeam wrapper), with a per-primitive test that checks validate(check_inverted_cells=True) and outward normals. I'll rebase around #2129 for hemisphere.py. Is that okay?
peterdsharpe commentedon Oct 20, 2026
Hi @dhvani7014 - thanks for asking! At the time, we have plans to fix this ourselves so PRs are not needed on this at the moment.