firedrakeproject / firedrake

Firedrake is an automated system for the portable solution of partial differential equations using the finite element method (FEM)
https://firedrakeproject.org
Other
498 stars 157 forks source link

BoundaryMesh #1282

Open c-abird opened 6 years ago

c-abird commented 6 years ago

Is there currently a way to extract a boundary mesh from a given Mesh object (similar to the BoundaryMesh functionality in FEniCS)? In the best case this would also be possible for the boundary of a subdomain of the mesh.

I want to implement FEM/BEM coupling, so I need both, the coordinates and the connectivity of the boundary mesh. Moreover an efficient way to map the values of functions living on either mesh would be nice. I guess this is somehow connected to #723.

wence- commented 6 years ago

Some of this stuff is available via the exterior facet maps (but it's a bit of work to pull it out). When you say you want the "connectivity" of the boundary mesh, do you want something that behaves like a normal Mesh object, but knows a relationship to its "parent"?

c-abird commented 6 years ago

By connectivity I just mean, how the vertices are connected to triangles. So, something that behaves like a mesh should be alright. I don't need a relationship to the parent mesh. However, I would need some method to pull the data from a Function defined on the full Mesh to a Function defined on the boundary mesh and vice versa (ideally without breaking parallel execution). Could you give me some pointers on how to use exterior facet maps?

c-abird commented 5 years ago

Hi Lawrence,

I managed to put together a basic code for the extraction of a boundary mesh from a given bulk mesh:

import firedrake as fd         
import numpy as np             
from pyop2.datatypes import IntType

mesh = fd.UnitCubeMesh(3,3,3)  
V = fd.FunctionSpace(mesh, "CG", 1) 

# this is adapted from firedrake/dmplex.pyx (boundary_nodes)
facet_dim = mesh.facet_dimension()
mesh_dim = mesh.geometric_dimension()

boundary_dofs = V.finat_element.entity_support_dofs()[facet_dim]

local_nodes = np.empty((len(boundary_dofs), 
                        len(boundary_dofs[0])),         
                       dtype=IntType)                  
for k, v in boundary_dofs.items():
    local_nodes[k, :] = v      

facets = V.mesh().exterior_facets
local_facets = facets.local_facet_dat.data_ro_with_halos

nlocal = local_nodes.shape[1]  
nfacet = facets.set.total_size 

facet_node_list = V.exterior_facet_node_map().values_with_halo

# collect facet data and set up mesh object
facet_data = np.zeros((nfacet, nlocal), dtype=IntType)
for facet in range(nfacet):    
    l = local_nodes[local_facets[facet]]
    facet_data[facet,:] = facet_node_list[facet].take(l)

node_map = np.unique(facet_data.flatten()) 
boundary_facets = np.searchsorted(node_map, facet_data)
boundary_coords = mesh.coordinates.vector().dat.data_ro[node_map]

plex = fd.mesh._from_cell_list(facet_dim, boundary_facets, boundary_coords, mesh.comm)
boundary_mesh = fd.Mesh(plex, dim = mesh_dim, reorder = False)

# create some function on boundary mesh and save it
VB = fd.FunctionSpace(boundary_mesh, "CG", 1)
f = fd.Function(VB).interpolate(fd.Constant(1.))
fd.File("f.pvd").write(f)

However, there are some problems:

Looking at the API of DMPLEX it should be possible to extract a boundary mesh by using DMPlexMarkBoundaryFaces and DMPlexCreateSubmesh. This looks to me like the best way in order to preserve parallel features of the mesh. However, DMPlexCreateSubmesh is apparently deactivated in petsc4py. Are you involved with the DMPLEX development or do you know why this method is not available in Python?

Thanks a lot and best wishes, Claas

dham commented 5 years ago

I'm not sure about the parallel issues. However the situation with orientation is as follows. We use the FEniCS convention that the triangles are oriented by the indices of the vertices, not geometrically. That is baked in to how we do assembly so isn't easily changeable. However for meshes of immersed manifolds, the mesh does carry orientation information which enables the construction of a consistent normal field. I don't believe it is guaranteed to be outward (after all, the immersed mesh might not be the surface of a closed region).

c-abird commented 5 years ago

OK. I understand that the boundary mesh has an attribute cell_orientations, which can be initialized with init_cell_orientations(expr). However, I did not find a way to initialize this properly. Ideally, I would use the FacetNormal of the full mesh to intialize the cell orientations of the boundary mesh, but this doesn't work as an input for init_cell_orientations. Any hints? Thanks, Claas

wence- commented 5 years ago

Hi Claas,

I think you might be able to do something like this:

V = VectorFunctionSpace(mesh, "DGT", 0)
u = TrialFunction(V)
v = TestFunction(V)
fn = Function(V)
solve(u*v*ds == FacetNormal(mesh)*v*ds, fn, solver_parameters={"pc_type": "jacobi"})

Now you have a field on the trace that contains the facet normal on each exterior facet of the original mesh.

You would then need to extract the correct pieces for your boundary mesh (doing something like what you do above to identify the correct facets).

Then you should be able to do

V = VectorFunctionSpace(boundary_mesh, "DG", 0)
fn_boundary = Function(V)
fn_boundary.dat.data[:] = fn.dat.data_ro[magic_permutation]

boundary_mesh.init_cell_orientations(fn_boundary)

It's trivial to wrap up DMPlexCreateSubmesh in petsc4py. This might be helpful, but I suspect you still need to do a lot of this index-permutation dance.

We'd love to do this properly (as you note #723 is relevant), but it's not completely trivial.

wence- commented 5 years ago

I am hopeful that something along these lines will arise out of @ksagiyam's work on submesh support.

ksagiyam commented 5 years ago

Yes. Boundary mesh extraction related to DMPlexCreateSubmesh is under active development. We are planning to then enable Firedrake to solve couplings between domain and boundary PDEs. Extraction of boundary of a boundary should also arise somewhere in this line.

c-abird commented 5 years ago

Thanks for the update. Is there any issue/branch I can follow in order to stay updated on this feature set? Is there a rough timeframe for the development?

wence- commented 5 years ago

Hi @c-abird, @ksagiyam can probably provide more information. It's presently actively being worked on. We have a project tracking various bits here: https://github.com/firedrakeproject/firedrake/projects/1 (if you have particular requirements, you could perhaps add some notes: it would be useful to know what people who want this stuff need).

The relevant branch in Firedrake is, I think, https://github.com/firedrakeproject/firedrake/tree/dmplex-submesh

ksagiyam commented 5 years ago

Hi @c-abird, ability to define functions on arbitrary subdomains (or subdomains of subdomains, etc) itself will probably be available in a day or two on the firedrake/dmplex-submesh branch that Lawrence mentioned. I think I need a few more weeks to enable us to use full-domain function data on a subdomain. We will keep you posted.

Although it might not be very useful at this moment, you can install this version as:

python3 firedrake-install --package-branch firedrake dmplex-submesh --package-branch petsc dmplex-submesh1 --package-branch petsc4py dmplex-submesh

c-abird commented 5 years ago

Hi @ksagiyam, I saw that you worked quite a bit on the dmplex-submesh branch. So I started trying out a few things. When I try to extract a boundary mesh from a given mesh with

mesh = UnitCubeMesh(2,2,2)
boundary_mesh = SubMesh(mesh, "exterior_facets", 1, "facet")

I get

RuntimeError: Facet must have 1 or 2 supports.  Something is wrong.

I this due to work in progress or am I doing something wrong?