Extracting surface part of a Function

I have a vector-valued function u defined on the full mesh. Now I want to export just (a part of) the surface of the mesh, including this function u. My attempt so far looks like this, but the result isn’t correct. Do I need some sort of reordering in the LHS of u_sarf.x.array[:] = ... instead of :? Or is
indices = np.ravel([3*parent_dofs + i for i in range(3)], order="F")not correct? BTW, what is the recommended way to go from “dofs” to actual array indices for vector-valued functions?

import numpy as np
from mpi4py import MPI

import dolfinx

msh = dolfinx.mesh.create_box(MPI.COMM_WORLD, [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]], [5, 5, 5])

fdim = msh.topology.dim - 1
msh.topology.create_connectivity(2, 3)
whole_exterior = dolfinx.mesh.exterior_facet_indices(msh.topology)

V = dolfinx.fem.functionspace(msh, ("CG", 1, (3,)))
u = dolfinx.fem.Function(V)

def stuff(x):
    return [np.sin(2.0*np.pi*x[0]), np.cos(2.0*np.pi*x[1]), x[2]]

u.interpolate(stuff)

xdmf_full = dolfinx.io.XDMFFile(MPI.COMM_WORLD, "full.xdmf", "w")
xdmf_full.write_mesh(msh)
xdmf_full.write_function(u)
xdmf_full.close()

submesh, facet_map, vertex_map, _ = dolfinx.mesh.create_submesh(msh, fdim, whole_exterior)
V_sub = dolfinx.fem.functionspace(submesh, ("CG", 1, (3,)))
u_surf = dolfinx.fem.Function(V_sub)

parent_dofs = dolfinx.fem.locate_dofs_topological(V, fdim, whole_exterior)
indices = np.ravel([3*parent_dofs + i for i in range(3)], order="F")

u_surf.x.array[:] = u.x.array[indices]

xdmf_surf = dolfinx.io.XDMFFile(MPI.COMM_WORLD, "surf.xdmf", "w")
xdmf_surf.write_mesh(submesh)
xdmf_surf.write_function(u_surf)

Y-component of full mesh on the left, my attempt of surface mesh on the right.


Hello, dear can you tell me how you are getting this cube??. I have save the file and open it in paraview but not getting your figure( Cube type).

I’ve got a function for this, that hopefully will be merged into scifem eventually:

"""
Interpolate from a parent mesh to a set of exterior facets
Author: Jørgen S. Dokken
SPDX License identifier: MIT
"""

def interpolate_to_submesh(
    u_volume: dolfinx.fem.Function,
    u_surface: dolfinx.fem.Function,
    entity_map: npt.NDArray[np.int32],
):
    """
    Interpolate a function `u_volume` into the function `u_surface`.
    Note:
        Does not work for DG as no dofs are associated with the facets
    Args:
        u_volume: Function to interpolate data from
        u_surface: Function to interpolate data to
        entity_map: Map from cells in the surface mesh to facets in the volume mesh
    """
    V_vol = u_volume.function_space
    mesh = V_vol.mesh
    V_surf = u_surface.function_space
    submesh = V_surf.mesh
    if Version(dolfinx.__version__) > Version("0.9.0"):
        ip = V_surf.element.interpolation_points
    else:
        ip = V_surf.element.interpolation_points()

    expr = dolfinx.fem.Expression(u_volume, ip)

    mesh.topology.create_connectivity(mesh.topology.dim, submesh.topology.dim)
    mesh.topology.create_connectivity(submesh.topology.dim, mesh.topology.dim)
    c_to_e = mesh.topology.connectivity(mesh.topology.dim, submesh.topology.dim)
    e_to_c = mesh.topology.connectivity(submesh.topology.dim, mesh.topology.dim)

    # Pack integration entities, including ghosts
    integration_entities = np.zeros((len(entity_map), 2), dtype=np.int32)
    for i, facet in enumerate(entity_map):
        cells = e_to_c.links(facet)
        if len(cells) > 1:
            raise RuntimeError("Only exterior facets are supported for now")
        facets = c_to_e.links(cells[0])
        local_index = np.flatnonzero(facets == facet)
        assert len(local_index) == 1
        integration_entities[i] = (cells[0], local_index[0])

    # Data is (num_cells, num_points/num_dofs, value_size)
    if Version(dolfinx.__version__) > Version("0.9.0"):
        data = expr.eval(mesh, integration_entities)
    else:
        data = expr.eval(mesh, integration_entities.flatten())

    submesh.topology.create_entity_permutations()
    mesh.topology.create_entity_permutations()
    cell_info = mesh.topology.get_cell_permutation_info()
    ft = V_surf.element.basix_element.cell_type

    for i in range(integration_entities.shape[0]):
        perm = np.arange(data.shape[1], dtype=np.int32)
        try:
            # Can be used once  https://github.com/FEniCS/basix/pull/913
            V_vol.element.basix_element.permute_subentity_closure_inv(
                perm, cell_info[integration_entities[i, 0]], ft, int(integration_entities[i, 1])
            )
        except TypeError:
            entity_info = compute_entity_data(
                V_vol.element, cell_info[integration_entities[i, 0]], ft, integration_entities[i, 1]
            )
            V_vol.element.basix_element.permute_subentity_closure_inv(perm, entity_info, ft)
        data[i] = data[i][perm]

    if len(data.shape) == 3:
        # Data is now (num_cells, value_size,num_points)
        data = data.swapaxes(1, 2)
        # Data is now (value_size, num_cells, num_points)
        data = data.swapaxes(0, 1)

    if expr.value_size == 1:
        shaped_data = data.flatten()
    else:
        shaped_data = data.reshape(expr.value_size, -1)

    u_surface._cpp_object.interpolate(shaped_data, np.arange(len(entity_map), dtype=np.int32))
    u_surface.x.scatter_forward()

Thank you for taking the time, this is much more involved than I realised. For me it triggers the TypeError, which is expected I guess, but I don’t know where compute_entity_data is from, so I can’t import it.

Here is an implementation of that function

import numpy as np
from mpi4py import MPI
from dolfinx import mesh, fem
import dolfinx
import numpy.typing as npt
import basix
from packaging.version import Version


def compute_entity_data(
    element,
    cell_info: int,
    entity_type: basix.CellType,
    entity_index: int,
) -> int:
    """
    Compute entity permutation data on a sub entity of the mesh.

    Note:
        This can be removed once https://github.com/FEniCS/basix/pull/913 is merged.
    Args:
        element: The element to compute permutations for
        cell_info: Permutation data for the cell
        entity_type: The type of the entity
        entity_index: The local index of the entity (relative to the cell)
    Returns:
        entity_info: Permutation data for the entity
    """
    ct = element.basix_element.cell_type
    tdim = len(basix.cell.topology(ct)) - 1
    num_entity_dofs = len(element.basix_element.entity_dofs[2])
    face_start = 3 * num_entity_dofs if tdim == 3 else 0
    entity_info = 0
    edim = len(basix.cell.topology(entity_type)) - 1
    if edim == 0:
        pass
    elif edim == 1:
        entity_info = cell_info >> (face_start + entity_index) & 1
    elif edim == 2:
        entity_info = cell_info >> (3 * entity_index) & 7

    else:
        raise ValueError(f"Unsupported entity {entity_type}")
    return entity_info

It runs now, but still doesn’t give me the correct result. I’m assuming the entity map I have to pass in is the entity map returned by create_submesh, which I’ve called facet_map here. Is that correct? I’m using dolfinx 0.9.0.

import numpy as np
from mpi4py import MPI

import dolfinx

from interpolate_submesh import interpolate_to_submesh

msh = dolfinx.mesh.create_box(MPI.COMM_WORLD, [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]], [5, 5, 5])

fdim = msh.topology.dim - 1
msh.topology.create_connectivity(2, 3)
whole_exterior = dolfinx.mesh.exterior_facet_indices(msh.topology)

V = dolfinx.fem.functionspace(msh, ("CG", 1, (3,)))
u = dolfinx.fem.Function(V)

def stuff(x):
    return [np.sin(2.0*np.pi*x[0]), np.cos(2.0*np.pi*x[1]), x[2]]

u.interpolate(stuff)

xdmf_full = dolfinx.io.XDMFFile(MPI.COMM_WORLD, "full.xdmf", "w")
xdmf_full.write_mesh(msh)
xdmf_full.write_function(u)
xdmf_full.close()

submesh, facet_map, vertex_map, _ = dolfinx.mesh.create_submesh(msh, fdim, whole_exterior)
V_sub = dolfinx.fem.functionspace(submesh, ("CG", 1, (3,)))
u_surf = dolfinx.fem.Function(V_sub)

interpolate_to_submesh(u, u_surf, facet_map)

xdmf_surf = dolfinx.io.XDMFFile(MPI.COMM_WORLD, "surf.xdmf", "w")
xdmf_surf.write_mesh(submesh)
xdmf_surf.write_function(u_surf)

This is due to a bug on v0.9.0, that has been fixed on the nightly branch.
You can test this with for instance: ghcr.io/fenics/dolfinx/dolfinx:nightly