Skip to content

Commit 1f8fee3

Browse files
authored
Merge pull request #152 from scientificcomputing/dokken/general_interpolation_matrix
Interpolate cell submesh function onto an facet submesh
2 parents e6e49c0 + a55bba3 commit 1f8fee3

File tree

4 files changed

+370
-41
lines changed

4 files changed

+370
-41
lines changed

src/scifem/interpolation.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
from packaging.version import Version
12
import dolfinx
23
import ufl
34
import numpy as np
45
import numpy.typing as npt
56
from .utils import unroll_dofmap
67

7-
__all__ = ["interpolation_matrix", "prepare_interpolation_data"]
8+
__all__ = ["interpolation_matrix", "prepare_interpolation_data", "interpolate_to_surface_submesh"]
89

910
if dolfinx.has_petsc4py:
1011
from petsc4py import PETSc
@@ -255,3 +256,65 @@ def scatter(
255256
)
256257
A.assemble()
257258
return A
259+
260+
261+
def interpolate_to_surface_submesh(
262+
u_volume: dolfinx.fem.Function,
263+
u_surface: dolfinx.fem.Function,
264+
submesh_facets: npt.NDArray[np.int32],
265+
integration_entities: npt.NDArray[np.int32],
266+
):
267+
"""
268+
Interpolate a function `u_volume` into the function `u_surface`.
269+
Note:
270+
Does not work for DG as no dofs are associated with the facets
271+
Args:
272+
u_volume: Function to interpolate data from
273+
u_surface: Function to interpolate data to
274+
submesh_facets: Cells in facet mesh
275+
integration_entities: Integration entities on the parent mesh
276+
corresponding to the facets in `submesh_facets`
277+
"""
278+
if Version(dolfinx.__version__) < Version("0.10.0"):
279+
raise RuntimeError("interpolate_to_submesh requires dolfinx version 0.10.0 or higher")
280+
V_vol = u_volume.function_space
281+
mesh = V_vol.mesh
282+
V_surf = u_surface.function_space
283+
submesh = V_surf.mesh
284+
ip = V_surf.element.interpolation_points
285+
286+
expr = dolfinx.fem.Expression(u_volume, ip)
287+
288+
mesh.topology.create_connectivity(mesh.topology.dim, submesh.topology.dim)
289+
mesh.topology.create_connectivity(submesh.topology.dim, mesh.topology.dim)
290+
291+
data = expr.eval(mesh, integration_entities)
292+
293+
submesh.topology.create_entity_permutations()
294+
mesh.topology.create_entity_permutations()
295+
cell_info = mesh.topology.get_cell_permutation_info()
296+
ft = V_surf.element.basix_element.cell_type
297+
298+
for i in range(integration_entities.shape[0]):
299+
perm = np.arange(data.shape[1], dtype=np.int32)
300+
V_vol.element.basix_element.permute_subentity_closure_inv(
301+
perm,
302+
cell_info[integration_entities[i, 0]],
303+
ft,
304+
int(integration_entities[i, 1]),
305+
)
306+
data[i] = data[i][perm]
307+
308+
if len(data.shape) == 3:
309+
# Data is now (num_cells, value_size,num_points)
310+
data = data.swapaxes(1, 2)
311+
# Data is now (value_size, num_cells, num_points)
312+
data = data.swapaxes(0, 1)
313+
314+
if expr.value_size == 1:
315+
shaped_data = data.flatten()
316+
else:
317+
shaped_data = data.reshape(expr.value_size, -1)
318+
319+
u_surface._cpp_object.interpolate(shaped_data, submesh_facets)
320+
u_surface.x.scatter_forward()

src/scifem/mesh.py

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import Protocol
1010
from packaging.version import Version
1111

12+
1213
__all__ = [
1314
"create_entity_markers",
1415
"transfer_meshtags_to_submesh",
@@ -195,14 +196,9 @@ def extract_submesh(
195196
# Accumulate all entities, including ghosts, for the specfic set of tagged entities
196197
edim = entity_tag.dim
197198
mesh.topology.create_connectivity(edim, mesh.topology.dim)
198-
emap = mesh.topology.index_map(entity_tag.dim)
199-
marker = dolfinx.la.vector(emap)
200199
tags_as_arr = np.asarray(tags, dtype=entity_tag.values.dtype)
201200
all_tagged_indices = np.isin(entity_tag.values, tags_as_arr)
202-
marker.array[entity_tag.indices[all_tagged_indices]] = 1
203-
marker.scatter_reverse(dolfinx.la.InsertMode.add)
204-
marker.scatter_forward()
205-
entities = np.flatnonzero(marker.array)
201+
entities = entity_tag.indices[all_tagged_indices]
206202
# Extract submesh
207203
submesh, cell_map, vertex_map, node_map = dolfinx.mesh.create_submesh(mesh, edim, entities)
208204

@@ -261,7 +257,14 @@ def find_interface(
261257

262258
# Compute intersecting facets
263259
interface_facets = np.intersect1d(facets0, facets1)
264-
return reverse_mark_entities(facet_map, interface_facets)
260+
261+
reverse_mark_entities(facet_map, interface_facets)
262+
263+
topology.create_connectivity(tdim - 1, tdim)
264+
f_to_c = topology.connectivity(tdim - 1, tdim)
265+
num_cells_per_facet = f_to_c.offsets[interface_facets + 1] - f_to_c.offsets[interface_facets]
266+
is_interface = interface_facets[num_cells_per_facet == 2]
267+
return is_interface
265268

266269

267270
def compute_subdomain_exterior_facets(
@@ -324,7 +327,9 @@ def compute_subdomain_exterior_facets(
324327

325328

326329
def compute_interface_data(
327-
cell_tags: dolfinx.mesh.MeshTags, facet_indices: npt.NDArray[np.int32]
330+
cell_tags: dolfinx.mesh.MeshTags,
331+
facet_indices: npt.NDArray[np.int32],
332+
include_ghosts: bool = False,
328333
) -> npt.NDArray[np.int32]:
329334
"""
330335
Compute interior facet integrals that are consistently ordered according to the `cell_tags`,
@@ -336,6 +341,8 @@ def compute_interface_data(
336341
cell_tags: MeshTags that must contain an integer marker for all cells adjacent
337342
to the `facet_indices`
338343
facet_indices: List of facets (local index) that are on the interface.
344+
include_ghosts: If `True` integration entities will include facets that are ghosts on
345+
the current process. This is for instance useful for interpolation on interior facets.
339346
Returns:
340347
The integration data.
341348
"""
@@ -346,12 +353,44 @@ def compute_interface_data(
346353
else:
347354
fdim = cell_tags.dim - 1
348355
integration_args = (fdim,)
349-
idata = dolfinx.cpp.fem.compute_integration_domains(
350-
dolfinx.fem.IntegralType.interior_facet,
351-
cell_tags.topology,
352-
facet_indices,
353-
*integration_args,
354-
)
356+
if include_ghosts:
357+
if len(facet_indices) == 0:
358+
return np.empty((0, 4), dtype=np.int32)
359+
f_to_c = cell_tags.topology.connectivity(cell_tags.topology.dim - 1, cell_tags.topology.dim)
360+
c_to_f = cell_tags.topology.connectivity(cell_tags.topology.dim, cell_tags.topology.dim - 1)
361+
362+
# Extract the cells connected to each facet.
363+
# Assumption is that there can only be two cells per facet, and should always be
364+
# two cells per facet.
365+
num_cells_per_facet = f_to_c.offsets[facet_indices + 1] - f_to_c.offsets[facet_indices]
366+
assert np.all(num_cells_per_facet == 2), "All facets must be interior facets."
367+
facet_pos = np.vstack([f_to_c.offsets[facet_indices], f_to_c.offsets[facet_indices] + 1]).T
368+
cells = f_to_c.array[facet_pos].flatten()
369+
# Extract facets connected to all cells
370+
# Assumption is that all cells have the same number of facets
371+
num_facets_per_cell = c_to_f.offsets[1:] - c_to_f.offsets[:-1]
372+
assert all(
373+
num_facets_per_cell[cells.flatten()] == num_facets_per_cell[cells.flatten()[0]]
374+
), "Cells must have facets."
375+
facets = np.vstack(
376+
[
377+
c_to_f.array[c_to_f.offsets[cells.flatten()] + i]
378+
for i in range(num_facets_per_cell[cells.flatten()[0]])
379+
]
380+
).T
381+
# Repeat facet indices twice to be able to do vectorized match
382+
rep_fi = np.repeat(facet_indices, 2)
383+
indicator = facets == rep_fi[:, None]
384+
_row, local_pos = np.nonzero(indicator)
385+
assert np.unique(_row).shape[0] == len(_row)
386+
idata = np.vstack([cells, local_pos]).T.reshape(-1, 4)
387+
else:
388+
idata = dolfinx.cpp.fem.compute_integration_domains(
389+
dolfinx.fem.IntegralType.interior_facet,
390+
cell_tags.topology,
391+
facet_indices,
392+
*integration_args,
393+
)
355394
ordered_idata = idata.reshape(-1, 4).copy()
356395
switch = cell_tags.values[ordered_idata[:, 0]] > cell_tags.values[ordered_idata[:, 2]]
357396
if True in switch:

tests/test_interpolation.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from mpi4py import MPI
2+
from packaging.version import Version
23
import dolfinx
34
import scifem.interpolation
45
import pytest
@@ -203,3 +204,114 @@ def test_discrete_curl(degree, use_petsc, cell_type):
203204
w_ref.x.scatter_forward()
204205

205206
np.testing.assert_allclose(w.x.array, w_ref.x.array, rtol=1e-10, atol=1e-11)
207+
208+
209+
@pytest.mark.parametrize("degree", [1, 2, 3])
210+
@pytest.mark.parametrize("family", ["Lagrange"])
211+
@pytest.mark.skipif(
212+
Version(dolfinx.__version__) < Version("0.10.0"), reason="Requires DOLFINx version >0.10.0"
213+
)
214+
def test_interpolate_to_interface_submesh(family, degree):
215+
# Create a unit square
216+
comm = MPI.COMM_WORLD
217+
domain = dolfinx.mesh.create_unit_square(
218+
comm, 48, 48, ghost_mode=dolfinx.mesh.GhostMode.shared_facet
219+
)
220+
221+
# Split unit square in two subdomains
222+
cell_map = domain.topology.index_map(domain.topology.dim)
223+
num_cells_local = cell_map.size_local + cell_map.num_ghosts
224+
markers = np.full(num_cells_local, 1, dtype=np.int32)
225+
markers[
226+
dolfinx.mesh.locate_entities(domain, domain.topology.dim, lambda x: x[0] <= 0.5 + 1e-14)
227+
] = 2
228+
ct = dolfinx.mesh.meshtags(
229+
domain, domain.topology.dim, np.arange(num_cells_local, dtype=np.int32), markers
230+
)
231+
232+
# Create submesh for each subdomain
233+
omega_e, e_to_parent, _, _, _ = scifem.mesh.extract_submesh(domain, ct, (1,))
234+
omega_i, i_to_parent, _, _, _ = scifem.mesh.extract_submesh(domain, ct, (2,))
235+
236+
# Compute submesh for the interface between omega_e and omega_i
237+
interface_facets = scifem.mesh.find_interface(ct, (1,), (2,))
238+
ft = dolfinx.mesh.meshtags(
239+
domain,
240+
domain.topology.dim - 1,
241+
interface_facets,
242+
np.full(interface_facets.shape, 1, dtype=np.int32),
243+
)
244+
245+
gamma, gamma_to_parent, _, _, _ = scifem.mesh.extract_submesh(domain, ft, 1)
246+
247+
num_facets_local = (
248+
gamma.topology.index_map(gamma.topology.dim).size_local
249+
+ gamma.topology.index_map(gamma.topology.dim).num_ghosts
250+
)
251+
gamma_to_parent_map = gamma_to_parent.sub_topology_to_topology(
252+
np.arange(num_facets_local, dtype=np.int32), inverse=False
253+
)
254+
255+
# Create functions on each subdomain
256+
def fe(x):
257+
return x[0] + x[1] ** degree
258+
259+
def fi(x):
260+
return np.sin(x[0]) + np.cos(x[1])
261+
262+
Ve = dolfinx.fem.functionspace(omega_e, (family, degree))
263+
ue = dolfinx.fem.Function(Ve)
264+
ue.interpolate(fe)
265+
ue.x.scatter_forward()
266+
Vi = dolfinx.fem.functionspace(omega_i, (family, degree))
267+
ui = dolfinx.fem.Function(Vi)
268+
ui.interpolate(fi)
269+
ui.x.scatter_forward()
270+
271+
# Compute ordered integration entities on the interface
272+
interface_integration_entities = scifem.compute_interface_data(
273+
ct, facet_indices=gamma_to_parent_map, include_ghosts=True
274+
)
275+
mapped_entities = interface_integration_entities.copy()
276+
277+
# For each submesh, get the relevant integration entities
278+
parent_to_e = e_to_parent.sub_topology_to_topology(
279+
np.arange(num_cells_local, dtype=np.int32), inverse=True
280+
)
281+
parent_to_i = i_to_parent.sub_topology_to_topology(
282+
np.arange(num_cells_local, dtype=np.int32), inverse=True
283+
)
284+
mapped_entities[:, 0] = parent_to_e[interface_integration_entities[:, 0]]
285+
mapped_entities[:, 2] = parent_to_i[interface_integration_entities[:, 2]]
286+
assert np.all(mapped_entities[:, 0] >= 0)
287+
assert np.all(mapped_entities[:, 2] >= 0)
288+
289+
# Create two functions on the interface submesh
290+
Q = dolfinx.fem.functionspace(gamma, (family, degree))
291+
qe = dolfinx.fem.Function(Q, name="qe")
292+
qi = dolfinx.fem.Function(Q, name="qi")
293+
294+
# Interpolate volume functions (on submesh) onto all cells of the interface submesh
295+
scifem.interpolation.interpolate_to_surface_submesh(
296+
ue, qe, np.arange(len(gamma_to_parent_map), dtype=np.int32), mapped_entities[:, :2]
297+
)
298+
qe.x.scatter_forward()
299+
scifem.interpolation.interpolate_to_surface_submesh(
300+
ui, qi, np.arange(len(gamma_to_parent_map), dtype=np.int32), mapped_entities[:, 2:]
301+
)
302+
qi.x.scatter_forward()
303+
304+
# Compute the difference between the two interpolated functions
305+
I = dolfinx.fem.Function(Q, name="i")
306+
I.x.array[:] = qe.x.array - qi.x.array
307+
308+
reference = dolfinx.fem.Function(Q)
309+
reference.interpolate(lambda x: fe(x) - fi(x))
310+
311+
qe_ref = dolfinx.fem.Function(Q)
312+
qe_ref.interpolate(fe)
313+
qi_ref = dolfinx.fem.Function(Q)
314+
qi_ref.interpolate(fi)
315+
np.testing.assert_allclose(qe.x.array, qe_ref.x.array)
316+
np.testing.assert_allclose(qi.x.array, qi_ref.x.array)
317+
np.testing.assert_allclose(I.x.array, reference.x.array, rtol=1e-14, atol=1e-14)

0 commit comments

Comments
 (0)