Skip to content

Feature/netgen periodic meshes - #5217

Draft
UZerbinati wants to merge 16 commits into
mainfrom
feature/netgen-periodic-meshes
Draft

Feature/netgen periodic meshes#5217
UZerbinati wants to merge 16 commits into
mainfrom
feature/netgen-periodic-meshes

Conversation

@UZerbinati

@UZerbinati UZerbinati commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

ngsPETSc PR: NGSolve/ngsPETSc#124

Umberto Zerbinati and others added 2 commits June 25, 2026 14:16
When a Netgen mesh carries periodic identifications, ngsPETSc now produces
a vertex-merged (periodic) DMPlex. This finishes the job on the Firedrake
side: the Mesh() netgen branch detects periodicity, builds the intermediate
topology without reordering, and attaches a discontinuous DG1 coordinate
field (MeshGeometry._periodic_coordinates) carrying the un-wrapped geometry
via _postprocess_periodic_mesh, exactly as Firedrake's own periodic meshes.

Each Firedrake cell is matched to its Netgen element by geometry (hashing
the wrapped per-cell coordinates), which is robust to reordering. The DG
coordinate space uses the equispaced variant required by
_set_dg_coordinates. High-order curving of periodic meshes raises
NotImplementedError for now.

Adds regression tests for a periodic square (one and two directions), a
periodic cylinder, and the too-coarse error path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "Periodic Meshes" section to the Netgen integration demo showing how
to declare OCC periodic identifications and import the resulting mesh into
Firedrake. The worked example is the periodic-cylinder ("screw pinch")
reduced model of a tokamak: a cylinder whose two circular ends are
identified, making the axial (toroidal) direction periodic. It labels the
lateral wall, checks the geometry survives the vertex merge (volume 2*pi^2),
and solves a manufactured Helmholtz problem with Dirichlet data on the wall
and periodicity on the ends, demonstrating seam continuity. Also documents
the too-coarse ValueError and the degree==1 limitation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread firedrake/mesh.py
Comment on lines +3488 to +3489
topology_reorder = False if netgen_periodic else reorder
topology = MeshTopology(plex, name=plex.getName(), reorder=topology_reorder,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
topology_reorder = False if netgen_periodic else reorder
topology = MeshTopology(plex, name=plex.getName(), reorder=topology_reorder,
reorder = False if netgen_periodic else reorder
topology = MeshTopology(plex, name=plex.getName(), reorder=reorder,

Comment thread firedrake/mesh.py Outdated
Comment on lines +3058 to +3059
@PETSc.Log.EventDecorator()
def _periodic_coordinates(self, permutation_tol=1e-8):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Move this to netgen.py

Comment thread firedrake/mesh.py
# is finished off with a discontinuous coordinate field below. That field
# is built on the un-reordered topology (as for Firedrake's own periodic
# meshes), so suppress reordering here and reapply it in postprocessing.
netgen_periodic = len(netgen_firedrake_mesh.meshMap.ngMesh.GetIdentifications()) > 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do as the comment says and supress reordering here. Remove the comment, we don't like AI slop

Comment thread firedrake/mesh.py
# discontinuous coordinate field carrying the un-wrapped geometry.
if degree != 1:
raise NotImplementedError(
"High-order curving of periodic netgen meshes is not supported yet."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Support this, I support you

Comment thread firedrake/mesh.py
Comment on lines +3510 to +3522
from firedrake.utility_meshes import _postprocess_periodic_mesh
permutation_tol = netgen_flags.get("permutation_tol", 1e-8)
coordinates = mesh._periodic_coordinates(permutation_tol=permutation_tol)
temp = _postprocess_periodic_mesh(coordinates,
mesh.comm,
distribution_parameters,
reorder,
name,
kwargs.get("distribution_name"),
kwargs.get("permutation_name"))
temp.netgen_mesh = mesh.netgen_mesh
temp.netgen_flags = mesh.netgen_flags
mesh = temp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

create a helper function in netgen.py doing everything here


from netgen.occ import Cylinder, OCCGeometry, Pnt, Z, gp_Trsf, gp_Vec
from netgen.meshing import IdentificationType
from math import pi as PI

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
from math import pi as PI

Comment thread firedrake/mesh.py

@PETSc.Log.EventDecorator()
def _periodic_coordinates(self):
'''Return a discontinuous coordinate field for a periodic netgen mesh.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should not add a netgen-only function to this file.

Comment thread firedrake/mesh.py
# overwriting, so the per-cell matching always sees the interpolated values.
wrapped_fd = data[cell_nodes].real.copy()

for i in range(cell_nodes.shape[0]):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is an antipattern (see AGENTS.md)

@pbrubeck

pbrubeck commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This work should be properly re-implemented on top of #5277 and NGSolve/ngsPETSc#125

Comment thread demos/periodic_meshes/periodic_meshes.py.rst Outdated
…lem. I have rewritten verbose sentences to make the demo clearer and easier to use

@UZerbinati UZerbinati left a comment

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.

@ThighamOxford any chance you would like to take a hit also at tackling the other comments Pablo left ? I'll be thinking about/working on high order :)


- \Delta u = \sin(x), \quad u(0) = u(2 \pi),

on :math:`\Omega = [0, 2 \pi]`. This problem has a trivial nullspace of constants; if we fix the constant to be zero then this problem has an analytical solution of :math:`u(x) = \sin(x)`.

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.

Suggested change
on :math:`\Omega = [0, 2 \pi]`. This problem has a trivial nullspace of constants; if we fix the constant to be zero then this problem has an analytical solution of :math:`u(x) = \sin(x)`.
on :math:`\Omega = [0, 2 \pi]`. This problem has a trivial nullspace of constants, thus if we fix the constant to be zero then this problem has an analytical solution of :math:`u(x) = \sin(x)`.


- \Delta u = (1 + \pi^2) \sin(x) \sin(\pi y), \quad u(0,y) = u(2 \pi, y), \quad u(x, 0) = u(x, 1) = 0,

on :math:`\Omega = [0, 2 \pi] \times [0,1]`. The nullspace is fixed for this problem. The exact solution is :math:`u(x,y) = \sin(x)\sin(\pi y)`.

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.

What do you mean when you say that the nullspace is fixed ? Does the problem have a nullspace ?

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.

The 1D periodic poisson problem with periodic data at the boundary has a nullspace of constants. I then clarify that for this 2D periodic poisson problem the nullspace is fixed due to additional dirichlet boundary conditions on the top and bottom of the box.

:alt: Finite element solution to 2D periodic Poisson problem.

Finite element solution to 2D periodic Poisson problem.

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.

If I remember correctly there are a bunch of features that do not work on extruded meshes, it would be nice to point to this issue, no ? Probably more detail can be found in the documentation for extruded meshes ?

Comment thread demos/periodic_meshes/periodic_meshes.py.rst Outdated

where ``transformation`` is the rigid motion (typically a translation) that maps ``shape_a`` onto ``shape_b``.
Netgen then meshes the two boundaries compatibly and records the vertex pairs; Firedrake consumes them
automatically -- no extra flag on the ``Mesh`` constructor is required.

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.

Suggested change
automatically -- no extra flag on the ``Mesh`` constructor is required.
automatically: no extra flag on the ``Mesh`` constructor is required.

.. warning::

The mesh must contain at least a handful of cells along each periodic direction. If a single cell spans a
whole period, its two ends are identified and the cell collapses; Firedrake then raises a ``ValueError``

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.

Suggested change
whole period, its two ends are identified and the cell collapses; Firedrake then raises a ``ValueError``
whole period, its two ends are identified and the cell collapses. Firedrake then raises a ``ValueError``

whole period, its two ends are identified and the cell collapses; Firedrake then raises a ``ValueError``
asking you to refine along the periodic direction. Here the axis has length :math:`2\pi` and ``maxh=0.4``
gives roughly sixteen cells along it, which is ample. Only ``degree == 1`` periodic meshes are supported
for now.

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.

I need to work on fixing this :)

Because the two end caps have been identified, no boundary markers survive on them: the seam has become an
*interior* set of facets, and the only labelled boundary that remains is the lateral wall. This is what makes
a continuous field wrap around continuously in the ``z``-direction. We can verify the geometry survived the
merge intact --- the volume of the cylinder is :math:`\pi r^2 h = 2\pi^2`: ::

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.

Suggested change
merge intact --- the volume of the cylinder is :math:`\pi r^2 h = 2\pi^2`: ::
merge intact: the volume of the cylinder is :math:`\pi r^2 h = 2\pi^2`: ::


To construct a periodic cylinder of length :math:`2\pi` we identify the two end caps of the cylinder by a translation of :math:`2\pi` along ``z``. ::

from netgen.occ import Cylinder, OCCGeometry, Pnt, Z, gp_Trsf, gp_Vec

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.

I believe there is no need to use gp_Vec tuple should be accepted now (bad Claude...).
Also Translation should be importable without gp_Trsf.

cyl.faces.Max(Z).name = "top"
# Identify the bottom cap with the top cap: a translation of 2*pi along Z
# maps one onto the other, making the axial direction periodic.
cyl.faces.Min(Z).Identify(cyl.faces.Max(Z), "toroidal",

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.

I'm pretty sure there is also a way of identifying surfaces in OCC (at least it is there for CSG3D: see here)that does not require to specify IndentificationType and transformation. Maybe it would be useful to showcase that. Ahh ! Found it here

ThighamOxford and others added 2 commits August 6, 2026 16:02
Co-authored-by: Umberto Zerbinati <umberto.zerbinati@maths.ox.ac.uk>
Co-authored-by: Pablo Brubeck <brubeck@protonmail.com>
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.

3 participants