This is a read only copy of the old FEniCS QA forum. Please visit the new QA forum to ask questions

Output a surface of a Function defined over a 3D mesh.

–1 vote

I would like to extract a surface of a 3D Function to a 2D .xml or .pvd file.

I have a FacetFunction which defines the surfaces.

I could create a 2D mesh from the 3D mesh and create a "dof_map" like structure to relate the 3D mesh indices to 2D mesh indices, but this is quite time consuming due to parsing .xml files.

Here's the source code for converting a 3D mesh to 2D (time consuming due to in1d ftn):

import re
from pylab  import *
from dolfin import *

#===============================================================================
# parse the mesh file :
grid    = open('mesh.xml', 'r')
newGrid = open('bed.xml',  'w')

sv    = '^.*index="([0-9]+)"\sx="(-?[0-9]+\.[0-9]+e?[+-]?[0-9]+)"' + \
                           '\sy="(-?[0-9]+\.[0-9]+e?[+-]?[0-9]+)"' + \
                           '\sz="(-?[0-9]+\.[0-9]+e?[+-]?[0-9]+)"'
pv    = re.compile(sv)

sc    = '^.*tetrahedron\sindex="([0-9]+)"' + \
                       '\sv0="([0-9]+)"' + \
                       '\sv1="([0-9]+)"' + \
                       '\sv2="([0-9]+)"' + \
                       '\sv2="([0-9]+)"'
pc    = re.compile(sc)

ci    = []  # cell index
vi    = []  # vertex index
vx    = []  # vertex x coordinate
vy    = []  # vertex y coordinate
vz    = []  # vertex z coordinate

print "<::::::::PARSING THE 3D XML FILE::::::::>"
for l in grid.readlines():
  mrv = pv.match(l)
  mrc = pc.match(l)
  if mrv != None:
    vi.append(int(mrv.group(1)))
    vx.append(float(mrv.group(2)))
    vy.append(float(mrv.group(3)))
    vz.append(float(mrv.group(4)))
  if mrc != None:
    v0 = int(mrc.group(1))
    v1 = int(mrc.group(2))
    v2 = int(mrc.group(3))
    v3 = int(mrc.group(4))
    v4 = int(mrc.group(5))
    ci.append([v0, v1, v2, v3, v4])
  if mrc == None and mrv == None:
    print "DATA NOT MATCHED :\t", l[:-1]
print "::::END OF GRID FILE::::"
grid.close()

# convert to array (more efficient) :
ci = array(ci)  # cell index
vi = array(vi)  # vertex index
vx = array(vx)  # vertex x coordinate
vy = array(vy)  # vertex y coordinate
vz = array(vz)  # vertex z coordinate

#===============================================================================
# formulate triangles :
# throw out parts that are not on the bed :
bed = where(vz == 0)[0]
n   = len(bed)
vi  = vi[bed] 
vx  = vx[bed] 
vy  = vy[bed] 
vz  = vz[bed] 
ta  = []                # triangles, values are indices to vertices
ti  = []                # cell index, need to make this cell the same as 3D

# iterate though each cell and find any cell with exactly 3 matching vertices 
# on the surface.  Then append a list of each vertex index that matches to the 
# array of triangles.
print "<::::::::FORMULATING TRIANGLES::::::::>"
for c in ci:
  k = in1d(vi, c[1:])
  if sum(k) == 3:
    ta.append(append(c[0], vi[k]))
print "::::FINISHED::::"

ta = array(ta)
m  = len(ta)

#===============================================================================
# create the new 2D .xml file :
print "<::::::::WRITING NEW 2D XML::::::::>"
newGrid.write('<?xml version="1.0" encoding="UTF-8"?>\n\n' + \
              '<dolfin xmlns:dolfin="http://www.fenicsproject.org">\n' + \
              '  <mesh celltype="triangle" dim="2">\n' + \
              '    <vertices size="%i">\n' % n)

for i,x,y,z in zip(vi,vx,vy,vz):
  newGrid.write('      <vertex index="%i" x="%f" y="%f" z="%f"/>\n' 
                % (i,x,y,z))

newGrid.write('    </vertices>\n' + \
              '    <cells size="%i">\n' % m)
for i,v0,v1,v2 in ta:
  newGrid.write('      <triangle index="%i" v0="%i" v1="%i" v2="%i"/>\n' 
                % (i,v0,v1,v2))

newGrid.write('    </cells>\n' + \
              '  </mesh>\n' + \
              '</dolfin>')
print "::::FINISHED::::"

newGrid.close()

Alternatively, I could:
1. Iterate through each vertex on the 3D mesh
2. Determine if it is on the surface I desire
3. If it's on the surface, add its indices and coordinates to a new data structure
4. Interpolate off of this new data structure onto the 2D surface, correcting for edge errors.

Does anyone have a better way?

Here I have asked this previous question which was not worded properly.

asked Nov 24, 2013 by pf4d FEniCS User (2,970 points)

1 Answer

+1 vote
 
Best answer

Your example is way to big for us to follow. That said you can create a boundary mesh from your 3D mesh. Create a Function on that mesh (2D embedded into 3D) and interpolate the original 3D Function onto the boundary Function.

from dolfin import *
mesh = UnitCubeMesh(10,10,10)
V = FunctionSpace(mesh, "CG", 1)
u = Function(V)
u.vector()[:] = 1.5

bmesh = BoundaryMesh(mesh, "exterior")
Vb = FunctionSpace(mesh, "CG", 1)
ub = Function(Vb)
ub.interpolate(u)
File("ub.pvd") << ub
answered Nov 25, 2013 by johanhake FEniCS Expert (22,480 points)
selected Nov 27, 2013 by pf4d

Is it possible to specify only part of the mesh though? I have a facet function which defines the part that I need, something like this:

ff   = FacetFunction('size_t', mesh, 0)
for f in facets(mesh):
  if f.normal() <= -1e-3 and f.exterior():
    ff[f] = 1

I use this for integration:

 ds = Measure('ds')[ff] # commenting and extra space for highlight

You can create a CellFunction on the boundary mesh based on your criteria and create a SubMesh of the BoundaryMesh using that function.

from dolfin import *
mesh = UnitSquareMesh(10,10)
V = FunctionSpace(mesh, "CG", 1)
u = Function(V)
u.vector()[:] = 1.5

bmesh = BoundaryMesh(mesh, "exterior")
mapping = bmesh.entity_map(1) 
part_of_boundary = CellFunction("size_t", bmesh, 0)
for cell in cells(bmesh):
    if Facet(mesh, mapping[cell.index()]).normal().x() < 0: # or some other criteria
        part_of_boundary[cell] = 1

submesh_of_boundary = SubMesh(bmesh, part_of_boundary, 1)
Vb = FunctionSpace(submesh_of_boundary, "CG", 1)
ub = Function(V)
ub.interpolate(u)
File("ub.pvd") << ub

Oh wow, that's great!

Just a couple edits for a 3D mesh :

from dolfin import *

mesh = UnitCubeMesh(10,10,10)
V    = FunctionSpace(mesh, "CG", 1) # comment for highlight
u    = Function(V)
u.vector()[:] = 1.5

bmesh   = BoundaryMesh(mesh, "exterior")
mapping = bmesh.entity_map(2) # comment makes highlight work
part_of_boundary = CellFunction("size_t", bmesh, 0) 
for cell in cells(bmesh):
  if Facet(mesh, mapping[cell.index()]).normal().z() < 0:
    part_of_boundary[cell] = 1

submesh_of_boundary = SubMesh(bmesh, part_of_boundary, 1)
Vb = FunctionSpace(submesh_of_boundary, "CG", 1)
ub = Function(Vb)
ub.interpolate(u)
File("ub.pvd") << ub

When running this with my actual mesh the interpolate function cannot find the point and gives this error:

***     https://answers.launchpad.net/dolfin
***
*** Remember to include the error message listed below and, if possible,
*** include a *minimal* running example to reproduce the error.
***
*** -------------------------------------------------------------------------
*** Error:   Unable to evaluate function at point.
*** Reason:  The point is not inside the domain. Consider setting 
***          "allow_extrapolation" to allow extrapolation.
*** Where:   This error was encountered inside Function.cpp.
*** Process: 0
*** -------------------------------------------------------------------------

Setting

parameters['allow_extrapolation'] = True

Results in a segmentation fault.

I'm going to try to iterate through the vertices of the SubMesh Function 'ub' and set them equal to the values of the vertices of the main Mesh Function 'u'. I have a feeling that I'll need some kind of mapping.

So in order to relate the indices of the SubMesh back to the original mesh (where I can pull the data from) I have to use a three-level dofmap with hierarchy

SubMesh --> BoundaryMesh --> Mesh

I have made the function over the 3D surface quadratic in x and y so we can better tell if this works. The reason this is needed is because the line

us.interpolate(u)

works with the CubeMesh, but not with the real mesh (I think because of floating-point error in the spatial extrapolation).

My attempt at implementing this has not worked, perhaps you know of a better way to map the SubMesh back to the original Mesh?

from dolfin import *

mesh = UnitCubeMesh(10,10,10)          # original mesh
mesh.coordinates()[:,0] -= .5          # shift x-coords
mesh.coordinates()[:,1] -= .5          # shift y-coords
V    = FunctionSpace(mesh, "CG", 1)
u    = Function(V)

dfV    = V.dofmap()
dfVmap = dfV.vertex_to_dof_map(mesh)          # mesh dofmap

# apply expression over cube for clearer results :
u_i  = Expression('sqrt(pow(x[0],2) + pow(x[1], 2))')
u.interpolate(u_i)

File('u.pvd') << u                            # output function to check

bmesh  = BoundaryMesh(mesh, "exterior")       # surface boundary mesh
Vb     = FunctionSpace(bmesh, "CG", 1)        # surface function space

dfVb    = Vb.dofmap()            
dfVbmap = dfVb.vertex_to_dof_map(bmesh)       # bmesh dofmap

cellmap  = bmesh.entity_map(2)
part_of_boundary = CellFunction("size_t", bmesh, 0)
for c in cells(bmesh):
  if Facet(mesh, cellmap[c.index()]).normal().z() < 0:
    part_of_boundary[c] = 1

submesh = SubMesh(bmesh, part_of_boundary, 1) # subset of surface mesh
Vs      = FunctionSpace(submesh, "CG", 1)     # submesh function space

us      = Function(Vs)                        # desired function
#us.interpolate(u)         # this works for UnitCube, not "real" mesh

dfVs    = Vs.dofmap()
dfVsmap = dfVs.vertex_to_dof_map(submesh)     # submesh dofmap

u_a  = u.vector().array()                     # array form of original ftn
us_a = us.vector().array()                    # array form of desired ftn

for v in vertices(submesh):
  i = v.index()                               # index of submesh
  us_a[i] = u_a[dfVmap[dfVbmap[dfVsmap[i]]]]  # relate back to original mesh

us.vector().set_local(us_a)                   # apply the array to ftn
File("ub.pvd") << us                          # not correct 
...