Skip to content

Changes to C0 polyhedron tetrahedralization loops#4359

Merged
roystgnr merged 23 commits intolibMesh:develfrom
GiudGiud:PR_poly_fixup
Mar 24, 2026
Merged

Changes to C0 polyhedron tetrahedralization loops#4359
roystgnr merged 23 commits intolibMesh:develfrom
GiudGiud:PR_poly_fixup

Conversation

@GiudGiud
Copy link
Copy Markdown
Contributor

@GiudGiud GiudGiud commented Dec 22, 2025

based on top of the hexagon tiling PR, ignore that content for now! It's in another PR

see companion PR in MOOSE creating a bunch of polys with exodus viz of the tets:
idaholab/moose#32141

Comment thread src/geom/cell_polyhedron.C Outdated
@GiudGiud GiudGiud force-pushed the PR_poly_fixup branch 2 times, most recently from 5990f8c to 45a4142 Compare December 25, 2025 17:39
@GiudGiud GiudGiud changed the title Poly fixups WIP Changes to C0 polyhedron tetrahedralization loops Dec 28, 2025
Comment thread src/geom/cell_c0polyhedron.C Outdated
if (local_tet_quality[j2] == 0 || local_tet_quality[j2] == far_node)
{
nodes_by_geometry.erase(geometry_it);
has_skipped_adding_tets_and_retriangulating = true;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is the saddest part of this PR. If we could still work with a valid surface mesh it would be a lot safer

unfortunately I seem to need this for every planar 4 node face, we always end up with an empty final tet

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

unfortunately I seem to need this for every planar 4 node face, we always end up with an empty final tet

Oh, man - are we getting hit again by the nastiest open secret of tet meshing?

It is not always possible to generate a mesh that has only non-degenerate tets and conforms to an existing triangulation, unless we add at least one "Steiner point" on the volume interior! See this horrifyingly simple example: https://github.com/libMesh/libmesh/blob/devel/tests/geom/elem_test.h#L224-L240 I think this is probably why it's so hard to find a tet mesher that doesn't habitually add interior points and screw up Yaqi's plans: if you know you're sometimes going to need to add points just to make a mesh possible, why hesitate to add points to make a mesh better?

I fear the only completely robust solution for our polyhedra code may be to add a new element (C0IPPolyhedron? I'd hope to come up with a better name...) that adds a single Interior Point, after which getting a nice tetrahedralization becomes trivial.

But in the short run, if you're allowed to swap diagonals then I think you can generally fix the problem with some diagonal swapping.

Is this PR ready for review now that it's no longer marked WIP?

Copy link
Copy Markdown
Contributor Author

@GiudGiud GiudGiud Jan 1, 2026

Choose a reason for hiding this comment

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

Happy New Year!

But in the short run, if you're allowed to swap diagonals then I think you can generally fix the problem with some diagonal swapping.

I'm doing this to extrude polygons into prisms. On the sides I have quads. Are you saying I just need to do something to the diagonals of these (quad) polygons? There's no API for it right now right? It just comes from the ordering of the nodes in the polygon?

I fear the only completely robust solution for our polyhedra code may be to add a new element (C0IPPolyhedron? I'd hope to come up with a better name...) that adds a single Interior Point, after which getting a nice tetrahedralization becomes trivial.

Could we just use the centroid as the interior node? Seems like the tetrahedralization is trivial after doing that? (centroid to every triangle)
The thing is that I am doing this for finite volume sims, so I don't really need extra nodes. In fact I am not even sure I need a tetrahedralization (well except to compute the volume)

Is this PR ready for review now that it's no longer marked WIP?

Certainly ready for a look. if you use my MOOSE PR you can look at the current tetrahedralization of the hexagonal prism in exodus. You should see there is a non-matching edge inside, but is it a problem for the FE math? Certainly is a problem for me when converting the polyhedra to tets in some mesh generators

Copy link
Copy Markdown
Contributor Author

@GiudGiud GiudGiud Jan 1, 2026

Choose a reason for hiding this comment

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

the only completely robust solution for our polyhedra code may be to add a new element (C0IPPolyhedron

What about a C0PolygonalPrism element? We could always triangulate it as the union of triangular prisms, and we could possibly have custom FE that would be worth including in libmesh? "Tensor product" of the 1D line and the C0Polygon

EDIT: well except if we do the FE math with the tets, the 1D line won't really appear in one direction. We'd have to do something different. Maybe we would not use tets at all? Just triangular prism sub-elements

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are you saying I just need to do something to the diagonals of these (quad) polygons? There's no API for it right now right? It just comes from the ordering of the nodes in the polygon?

Correct, correct, and correct (at construction time), I'm afraid. If you retriangulate() after construction time then we'll try to come up with a geometrically nicer triangulation, but you can't really control that without changing the geometry, a no-no in this context. The only way to assign the quad diagonals is to make use of knowing the default triangulation and then use that to choose which of the nodes of the quad you assign to node 0 after constructing it.

Could we just use the centroid as the interior node? Seems like the tetrahedralization is trivial after doing that?

In our mesh generators IMHO the vertex_average would be the best choice; the tetrahedralization for a convex polyhedron is trivial for any interior point choice.

In fact I am not even sure I need a tetrahedralization (well except to compute the volume)

Not directly, but we do use it in the Lagrange shape functions, so anything trying to compute a mapping will scream if it's not there. We might be able to get by without it thanks to having thrown up our hands at the idea of figuring out what a "master" element looks like for an arbitrary polyhedron, though...

What about a C0PolygonalPrism element?

Not crazy, but I think the IP polyhedron will end up being less new work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pinging @jwpeterson since he's had opinions on these names in the original PRs.

Fine with C0PolyhedronP1Plus

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok I ll work on this. Don't think we have funding for this anymore though
I m planning to make it inherit from C0Polyedron. That makes sense right, adding interior points in the derived class

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

or do you prefer an intermediate base class? C0PolyhedronBase and two derived children, the regular one and the P1Plus

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have "Polyhedral/tetrahedral mesh enhancements" in my notes under "FY25-26 NEAMS Tasks"/"Named M2 Tasks", with "Polyhedra: very high priority" even, but I'll ping @lindsayad in case my meeting stenography skills failed me. The biggest goal there (IMHO - no notes on that) was I/O support, but if even a simple extrusion is giving us tetrahedralization problems then we probably can't hope to start reading arbitrary files until we've fixed that.

We probably want a C0PolyhedronBase here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Your notes are spot-on and perfectly timed with my slack NEAMS-MP posts today!

@moosebuild
Copy link
Copy Markdown

moosebuild commented Dec 28, 2025

Job Coverage, step Generate coverage on 1510b5f wanted to post the following:

Coverage

be322b #4359 1510b5
Total Total +/- New
Rate 65.38% 65.46% +0.08% 100.00%
Hits 78029 78165 +136 69
Misses 41320 41242 -78 0

Diff coverage report

Full coverage report

This comment will be updated on new commits.

// with no additional interior point
try
{
// In 3D, this will require nested loops: an outer loop to remove
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

prefer as is or with an indentation (and a big diff)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

comment goes line 400 now

Comment thread src/geom/cell_c0polyhedron.C Outdated
@GiudGiud
Copy link
Copy Markdown
Contributor Author

Going to try unit tests on the FE / quadratures.
My suspicion is that the element with the midnode as I have it has one more dof (as you'd expect) and is not equivalent. This would be the right reason to make it another class?

@GiudGiud GiudGiud force-pushed the PR_poly_fixup branch 2 times, most recently from 156336b to 105e26c Compare March 17, 2026 01:07
Comment thread tests/fe/fe_test.h
@GiudGiud GiudGiud force-pushed the PR_poly_fixup branch 10 times, most recently from c8b481b to 827a736 Compare March 17, 2026 16:21
@@ -93,6 +93,10 @@ Polyhedron::Polyhedron (const std::vector<std::shared_ptr<Polygon>> & sides,
}
}

// Plan room for an extra node so we don't invalidate this->_nodes when we add to
// nodelinks_data in the derived class
_nodelinks_data.reserve(_nodelinks_data.size() + 1);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

could also modify the derived class but this is cheaper?

Copy link
Copy Markdown
Contributor Author

@GiudGiud GiudGiud left a comment

Choose a reason for hiding this comment

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

This grew but I think it's ready for a look now!

@GiudGiud
Copy link
Copy Markdown
Contributor Author

GiudGiud commented Mar 17, 2026

looks like we are not consistently getting the midnode with the cube poly. I ll try a few diagonal changes to see if it stabilizes on civet
on mac I get it consistently
EDIT: was opt vs devel not linux vs arm

Copy link
Copy Markdown
Member

@roystgnr roystgnr left a comment

Choose a reason for hiding this comment

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

This looks practically ready.

Comment thread include/systems/generic_projector.h Outdated
// Treat polyhedron midnode as a vertex
// NOTE: if we start having edge or side nodes on polyhedra, we need to use that +1 offset
// in the edge and side nodes code as well!
for (unsigned int v=0; v != n_vertices + has_poly_midnode; ++v)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's switch to a make_range() here? I think compilers are smart enough to realize we're not doing any const_cast tricks on n_vertices or has_poly_midnode and so we don't need to do that addition on every loop iteration, but nicer to be certain.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm... more importantly: treating the midnode as a vertex means we're going to be interpolating onto it regardless of FEType. That's actually what we want to do anyway for LAGRANGE variables, and the test of FEInterface::n_dofs_at_node(fe_type, elem, 0, add_p_level)) means we should be fine for MONOMIAL and XYZ too, but I could see this biting us in a very subtle way when we try to add higher order or more exotic continuous FE options. Could we add something like libmesh_assert(!has_poly_midnode || fe_type.family == LAGRANGE) above this loop?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done and added

for (unsigned int v=0; v != n_vertices; ++v)
// Treat polyhedron midnode as a vertex
// NOTE: if we start having edge or side nodes on polyhedra, we need to use that +1 offset
// in the edge and side nodes code as well!
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMHO we'll want to number the midnode last (as we do with e.g. Quad9 and Hex27), but that's speculative; this comment is fine as-is and we definitely want some reminder of the issue.

Comment thread include/geom/cell_c0polyhedron.h Outdated
#include "libmesh/libmesh_common.h"
#include "libmesh/cell_polyhedron.h"
#include "libmesh/enum_order.h"
#include "libmesh/node.h"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we get away with a forward declaration of Node? No big deal if not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah we can. changed

Comment thread src/geom/cell_c0polyhedron.C Outdated
bool C0Polyhedron::is_vertex(const unsigned int i) const
{
libmesh_assert (i < this->n_nodes());
libmesh_assert_less (i, this->n_vertices() + _has_mid_elem_node);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

libmesh_assert_less (i, this->n_nodes()) should still work here, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

reverted

*
* Each subelement here is a tetrahedron
*/
virtual std::array<int, 4> subelement_sides_to_poly_sides(unsigned int i) const;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A little more doxygen here? I assume returnval[j] of subelement_sides_to_poly_sides(i) is the polyhedron-local index of the subtet-local index j of subtet i, but we should spell that out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added

std::array<int, 4> sides = {invalid_int, invalid_int, invalid_int, invalid_int};
libmesh_assert(tri_i < this->n_subelements());
const auto & tet_nodes = _triangulation[tri_i];
for (const auto poly_side : make_range(n_sides()))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At some point we should document which of the "you'd think it'd be O(1)" polyhedron APIs is actually O(n_sides()), but I'm pretty sure I committed this sin before you did and we don't need to fix it in this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added a note

// This might not succeed, not every surface triangulation gives a tetrahedralization
// with no additional interior point
// But if it succeeds, it uses less tetrahedra to cut the polyhedron
try
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We probably need something to fix --disable-exceptions builds; I'll add that recipe.

We could just do libmesh_try/libmesh_catch ... which basically ifdef's out the catch block, and gives you code that still works, but only in the cases where no exceptions are thrown.

Perhaps here we should be (manually) ifdef'ing out the try block instead? If a user doesn't have exceptions disabled for some reason, we just put a midpoint node in every poly so we always get a valid (albeit often less efficient) element.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok used that last solution

- add an assert in generic projector to avoid getting surprised with the midnode later
- when disabling exceptions, always create the midnode as midnode need detection relies on exceptions
- try a forward declare for Node
@roystgnr
Copy link
Copy Markdown
Member

I can't tell for sure whether the SIGABRT in vectorpostprocessors/extra_id_integral.functor/missing_vector_name_error is unrelated, but that's pretty much always from something like the test harness, isn't it?

@roystgnr
Copy link
Copy Markdown
Member

Double-free in the --disable-exceptions build. I'll look at that one.

@GiudGiud
Copy link
Copy Markdown
Contributor Author

I think the tests might be hardcoded for a given configuration. I ll amend that

@GiudGiud GiudGiud force-pushed the PR_poly_fixup branch 2 times, most recently from 47ea3a9 to 8262bb7 Compare March 24, 2026 01:10
@roystgnr roystgnr enabled auto-merge March 24, 2026 02:46
@roystgnr roystgnr merged commit 2ecfa0b into libMesh:devel Mar 24, 2026
27 of 28 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in NEAMS MP Backend 26 Mar 24, 2026
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.

5 participants