Skip to content

Commit b2bcc5a

Browse files
authored
Merge pull request #24 from scientificcomputing/dokken/reconstruct-mesh
Add mesh reconstruction command for curving/uncurving
2 parents ac91abc + 7d47845 commit b2bcc5a

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

src/io4dolfinx/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
read_point_data,
3232
)
3333
from .snapshot import snapshot_checkpoint
34+
from .utils import reconstruct_mesh
3435

3536
meta = metadata("io4dolfinx")
3637
__version__ = meta["Version"]
@@ -62,4 +63,5 @@
6263
"get_backend",
6364
"write_cell_data",
6465
"write_point_data",
66+
"reconstruct_mesh",
6567
]

src/io4dolfinx/utils.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414

1515
from mpi4py import MPI
1616

17+
import basix.ufl
1718
import dolfinx
1819
import numpy as np
1920
import numpy.typing as npt
21+
import ufl
2022
from packaging.version import Version
2123

2224
__all__ = [
@@ -27,6 +29,7 @@
2729
"unroll_dofmap",
2830
"compute_insert_position",
2931
"unroll_insert_position",
32+
"reconstruct_mesh",
3033
]
3134

3235

@@ -196,3 +199,62 @@ def compute_dofmap_pos(
196199
local_cell[indicator] = cell_indicator[markers].reshape(-1)
197200
dof_pos[indicator] = local_indices[markers].reshape(-1)
198201
return local_cell, dof_pos
202+
203+
204+
def reconstruct_mesh(mesh: dolfinx.mesh.Mesh, coordinate_element_degree: int) -> dolfinx.mesh.Mesh:
205+
"""
206+
Make a copy of a mesh and potentially change the element of the coordinate element.
207+
208+
Note:
209+
The topology is shared with the original mesh but the geometry is reconstructed.
210+
211+
Args:
212+
mesh: Mesh to reconstruct
213+
coordinate_element_degree: Degree to use for coordinate element
214+
215+
Returns:
216+
The new mesh
217+
218+
"""
219+
# Extract cell properties
220+
ud = mesh.ufl_domain()
221+
assert ud is not None
222+
c_el = ud.ufl_coordinate_element()
223+
family = c_el.family_name
224+
lvar = c_el.lagrange_variant
225+
ct = c_el.cell_type
226+
227+
# Create new UFL element
228+
new_c_el = basix.ufl.element(
229+
family,
230+
ct,
231+
coordinate_element_degree,
232+
shape=(mesh.geometry.dim,),
233+
lagrange_variant=lvar,
234+
dtype=mesh.geometry.x.dtype,
235+
)
236+
237+
# Extract new node coordinates
238+
V_tmp = dolfinx.fem.functionspace(mesh, new_c_el)
239+
gdim = mesh.geometry.dim
240+
x = V_tmp.tabulate_dof_coordinates()[:, :gdim]
241+
242+
# Create new geoemtry
243+
geom_imap = V_tmp.dofmap.index_map
244+
geom_dofmap = V_tmp.dofmap.list
245+
num_nodes_local = geom_imap.size_local + geom_imap.num_ghosts
246+
original_input_indices = geom_imap.local_to_global(np.arange(num_nodes_local, dtype=np.int32))
247+
coordinate_element = dolfinx.fem.coordinate_element(
248+
mesh.topology.cell_type, coordinate_element_degree, lvar, dtype=mesh.geometry.x.dtype
249+
)
250+
# Could use create_geometry here when things are fixed
251+
geom = dolfinx.mesh.Geometry(
252+
type(mesh.geometry._cpp_object)(
253+
geom_imap, geom_dofmap, coordinate_element._cpp_object, x, original_input_indices
254+
)
255+
)
256+
257+
# Create new mesh
258+
new_top = mesh.topology
259+
cpp_mesh = type(mesh._cpp_object)(mesh.comm, new_top._cpp_object, geom._cpp_object)
260+
return dolfinx.mesh.Mesh(cpp_mesh, ufl.Mesh(new_c_el))

tests/test_mesh.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from mpi4py import MPI
2+
3+
import dolfinx
4+
import numpy as np
5+
import pytest
6+
import ufl
7+
8+
from io4dolfinx import reconstruct_mesh
9+
10+
11+
@pytest.mark.parametrize("dtype", [np.float32, np.float64])
12+
@pytest.mark.parametrize("degree", [2, 3])
13+
@pytest.mark.parametrize("R", [0.1, 1, 10])
14+
def test_curve_mesh(degree, dtype, R):
15+
N = 8
16+
mesh = dolfinx.mesh.create_rectangle(
17+
MPI.COMM_WORLD,
18+
[[-1, -1], [1, 1]],
19+
[N, N],
20+
diagonal=dolfinx.mesh.DiagonalType.crossed,
21+
dtype=dtype,
22+
)
23+
org_area = dolfinx.fem.form(1 * ufl.dx(domain=mesh), dtype=dtype)
24+
25+
curved_mesh = reconstruct_mesh(mesh, degree)
26+
27+
def transform(x):
28+
u = R * x[0] * np.sqrt(1 - (x[1] ** 2 / (2)))
29+
v = R * x[1] * np.sqrt(1 - (x[0] ** 2 / (2)))
30+
return np.asarray([u, v])
31+
32+
curved_mesh.geometry.x[:, : curved_mesh.geometry.dim] = transform(curved_mesh.geometry.x.T).T
33+
34+
area = dolfinx.fem.form(1 * ufl.dx(domain=curved_mesh), dtype=dtype)
35+
circumference = dolfinx.fem.form(1 * ufl.ds(domain=curved_mesh), dtype=dtype)
36+
37+
computed_area = curved_mesh.comm.allreduce(dolfinx.fem.assemble_scalar(area), op=MPI.SUM)
38+
computed_circumference = curved_mesh.comm.allreduce(
39+
dolfinx.fem.assemble_scalar(circumference), op=MPI.SUM
40+
)
41+
42+
tol = 10 * np.finfo(dtype).eps
43+
assert np.isclose(computed_area, np.pi * R**2, atol=tol)
44+
assert np.isclose(computed_circumference, 2 * np.pi * R, atol=tol)
45+
46+
linear_mesh = reconstruct_mesh(curved_mesh, 1)
47+
linear_area = dolfinx.fem.form(1 * ufl.dx(domain=linear_mesh), dtype=dtype)
48+
49+
recovered_area = linear_mesh.comm.allreduce(
50+
dolfinx.fem.assemble_scalar(linear_area), op=MPI.SUM
51+
)
52+
53+
# Curve original mesh
54+
mesh.geometry.x[:, : mesh.geometry.dim] = transform(mesh.geometry.x.T).T
55+
ref_area = mesh.comm.allreduce(dolfinx.fem.assemble_scalar(org_area), op=MPI.SUM)
56+
assert np.isclose(recovered_area, ref_area, atol=tol)

0 commit comments

Comments
 (0)