Find the coordinate of DOFs on a second-order mesh

How can I correctly obtain the coordinates associated with degrees of freedom (DOFs) when using a second-order mesh in Dolfinx?

I couldn’t find support for periodic boundary conditions in Dolfinx, so I attempted to manually modify the PETSc matrix assembled by Dolfinx. This requires knowing the coordinates of the DOFs to pair the DOFs on the left boundary with those on the right boundary. However, this approach has become quite messy.

I have created an example that demonstrates that using V.tabulate_dof_coordinates() does not seem to correctly retrieve the coordinates of the DOFs when using a second-order mesh. In this example, I attempt to get the DOFs on the left boundary, and print the DOFs I’ve got using a function. The resulting image is strange.

import gmsh
import numpy as np
from dolfinx.io import gmshio, XDMFFile
from mpi4py import MPI
import dolfinx
import basix

affix = "./result_test_move/"
r = 0.125
L = 1
H = L
def create_split_mesh(c_x, c_y):
    gmsh.initialize()
    gdim = 2
    mesh_comm = MPI.COMM_SELF
    model_rank = 0
    if mesh_comm.rank == model_rank:
        rectangle = gmsh.model.occ.addRectangle(0, 0, 0, L, L, tag=1)
        obstacle = gmsh.model.occ.addDisk(c_x, c_y, 0, r, r)

        p1 = gmsh.model.occ.add_point(L / 2, 0, 0)
        p2 = gmsh.model.occ.add_point(L / 2, L, 0)
        l1 = gmsh.model.occ.add_line(p1, p2)

        gmsh.model.occ.synchronize()


    fluid_marker = 1
    if mesh_comm.rank == model_rank:
        volumes = gmsh.model.getEntities(dim=gdim)
        gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], fluid_marker)
        gmsh.model.setPhysicalName(volumes[0][0], fluid_marker, "Fluid")

    inlet_marker, outlet_marker, wall_marker, obstacle_marker = 2, 3, 4, 5
    inflow, outflow, walls, obstacle = [], [], [], []
    if mesh_comm.rank == model_rank:
        boundaries = gmsh.model.getBoundary(volumes, oriented=False)
        for boundary in boundaries:
            center_of_mass = gmsh.model.occ.getCenterOfMass(boundary[0], boundary[1])
            if np.allclose(center_of_mass, [0, H / 2, 0]):
                inflow.append(boundary[1])
            elif np.allclose(center_of_mass, [L, H / 2, 0]):
                outflow.append(boundary[1])
            elif np.allclose(center_of_mass, [L / 2, H, 0]) or np.allclose(center_of_mass, [L / 2, 0, 0]):
                walls.append(boundary[1])
            else:
                obstacle.append(boundary[1])
        gmsh.model.addPhysicalGroup(1, walls, wall_marker)
        gmsh.model.setPhysicalName(1, wall_marker, "Walls")
        gmsh.model.addPhysicalGroup(1, inflow, inlet_marker)
        gmsh.model.setPhysicalName(1, inlet_marker, "Inlet")
        gmsh.model.addPhysicalGroup(1, outflow, outlet_marker)
        gmsh.model.setPhysicalName(1, outlet_marker, "Outlet")
        gmsh.model.addPhysicalGroup(1, obstacle, obstacle_marker)
        gmsh.model.setPhysicalName(1, obstacle_marker, "Obstacle")

    if mesh_comm.rank == model_rank:
        gmsh.model.mesh.embed(1, obstacle, 2, rectangle)
        # gmsh.model.mesh.embed(1, [l1], 2, rectangle)
        gmsh.model.occ.fragment([(2, rectangle)], [(1, l1)])

        gmsh.option.setNumber("Mesh.MeshSizeMin", r / 4)
        gmsh.option.setNumber("Mesh.MeshSizeMax", r / 3)

        gmsh.option.setNumber("Mesh.Algorithm", 8)
        gmsh.model.mesh.generate(gdim)
        gmsh.model.mesh.setOrder(2)
    
    msh, _, _ = dolfinx.io.gmshio.model_to_mesh(gmsh.model, mesh_comm, model_rank, gdim=gdim)

    gmsh.finalize()
    return msh

msh0 = create_split_mesh(0.25, 0.4)

V_element = basix.ufl.element("CG", msh0.basix_cell(), 2, shape=(2,))
V = dolfinx.fem.functionspace(msh0, V_element)

dof_coord = V.tabulate_dof_coordinates()[:,:2]
def pbs_target(x):
    return x[:,0] < 0 + 1e-6
target = pbs_target(dof_coord).nonzero()[0].astype(np.int32)

f_in = dolfinx.fem.Function(V)
f_in.x.array[target] = 10000
with XDMFFile(MPI.COMM_WORLD, f"{affix}solution.xdmf", "w") as xdmf:
    xdmf.write_mesh(msh0)
    xdmf.write_function(f_in)

Periodic boundary conditions is supported using the extension DOLFINx_MPC:

See for instance:

For your specific problem, you haven’t mapped the degrees of freedom by their block size.
If you look at the shape of dof_coord,

you will realize that it is half the size of u.x.array.
This is because we have that dof (2i, 2i+1) share a dof coordinate in DOLFINx,
i.e.


def pbs_target2(x):
    return x[0] < 0 + 1e-6


dofs = dolfinx.fem.locate_dofs_geometrical(V, pbs_target2)
bs = V.dofmap.bs
unrolled_dofs = np.empty(len(dofs)*bs,dtype=np.int32)
for (i, dof) in enumerate(dofs):
    for b in range(bs):
        unrolled_dofs[i*bs+b] = dof*bs+b


f_in = dolfinx.fem.Function(V)
f_in.x.array[unrolled_dofs] = 10000
with XDMFFile(MPI.COMM_WORLD, f"{affix}solution.xdmf", "w") as xdmf:
    xdmf.write_mesh(msh0)
    xdmf.write_function(f_in)

will give you the correct result

1 Like

Wow, that helps a lot. Thank you for your prompt, informative, and precise reply.