Dolfinx: Mesh refinement

I want to refine my mesh (multiple times) based on the location of the cell. The reason is, that I need to have a fine mesh towards the boundary of the domain. Since my problem is complex, I am using dolfinx.

However, I cannot get it to run. There are some examples for dolfin but I did not spot one for dolfinx. My code so far is. I would really appreciate any help on that.

import dolfinx
import numpy as np
from dolfinx import RectangleMesh
from dolfinx.fem import locate_dofs_geometrical, locate_dofs_topological
from dolfinx.io import XDMFFile
from dolfinx.mesh import locate_entities_boundary, refine

W = 380e-6  # width
H = 180e-6  # height

d_ele = 20e-6
delta = 1e-7

n_W = int(np.ceil(W/d_ele))  # number of elements along W
n_H = int(np.ceil(H/d_ele))  # number of elements along H

# Create mesh
mesh = RectangleMesh(MPI.COMM_WORLD,
                     [np.array([-W/2, -H/2, 0]), np.array([W/2, H/2, 0])],
                     [n_W, n_H],
                     CellType.triangle, dolfinx.cpp.mesh.GhostMode.none)


def inside_delta(xs):
    out = []
    for i in np.arange(xs.shape[1]):
        x, y = xs[0, i], xs[1, i]
        if W/2 - abs(x) <= 30*delta or H/2 - abs(y) <= 30*delta:
            out.append(True)
        else:
            out.append(False)
    return np.asarray(out)

def mark_all(xs):
    return np.ones(xs.shape[1])

# boundary layer
boundaries = [(1, lambda x: inside_delta(x)),
        (0, lambda x: mark_all(x))]

refine_indices, refine_markers = [], []
dim = mesh.topology.dim

for (marker, locator) in boundaries:
    facets = dolfinx.mesh.locate_entities(mesh, dim, locator)
    refine_indices.append(facets)
    refine_markers.append(np.full(len(facets), marker))

refine_tag = dolfinx.MeshTags(mesh, dim,
                              np.array(np.hstack(refine_indices), dtype=np.int8),
                              np.array(np.hstack(refine_markers), dtype=np.int8))

mesh.topology.create_entities(1)
mesh = refine(mesh, refine_tag)

# in order to export it you have to change dtype=np.int8 to dtype=np.int32
with XDMFFile(mesh.mpi_comm(), "mesh.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)

I could partly resolve my problem by myself. I thought I need to mark every cell; even the once which should not be refined. But this is not needed.

import dolfinx
import numpy as np
from dolfinx import RectangleMesh
from dolfinx.fem import locate_dofs_geometrical, locate_dofs_topological
from dolfinx.io import XDMFFile
from dolfinx.mesh import locate_entities_boundary, refine

W = 380e-6  # width
H = 180e-6  # height

d_ele = 20e-6
delta = 1e-6

n_W = int(np.ceil(W/d_ele))  # number of elements along W
n_H = int(np.ceil(H/d_ele))  # number of elements along H

# Create mesh
mesh = RectangleMesh(MPI.COMM_WORLD,
                     [np.array([-W/2, -H/2, 0]), np.array([W/2, H/2, 0])],
                     [n_W, n_H],
                     CellType.triangle, dolfinx.cpp.mesh.GhostMode.none)


def inside_delta(xs):
    out = []
    for i in np.arange(xs.shape[1]):
        x, y = xs[0, i], xs[1, i]
        if W/2 - abs(x) <= 30*delta or H/2 - abs(y) <= 30*delta:
            out.append(True)
        else:
            out.append(False)
    return np.asarray(out)

# boundary layer
boundaries = [(1, lambda x: inside_delta(x))]

for _ in np.arange(10):
    refine_indices, refine_markers = [], []
    dim = mesh.topology.dim

    for (marker, locator) in boundaries:
        facets = dolfinx.mesh.locate_entities(mesh, dim, locator)
        refine_indices.append(facets)
        refine_markers.append(np.full(len(facets), marker))

    refine_tag = dolfinx.MeshTags(mesh, dim,
                                 np.array(np.hstack(refine_indices), dtype=np.int8),
                                  np.array(np.hstack(refine_markers), dtype=np.int8))

   mesh.topology.create_entities(1)
   mesh = refine(mesh, refine_tag)

# in order to export it you have to change dtype=np.int8 to dtype=np.int32
with XDMFFile(mesh.mpi_comm(), "mesh.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)

However, since I want to do that process repeatedly I get now the following error code already in the 2nd loop

[0]PETSC ERROR: ------------------------------------------------------------------------
[0]PETSC ERROR: Caught signal number 11 SEGV: Segmentation Violation, probably memory access out of range
[0]PETSC ERROR: Try option -start_in_debugger or -on_error_attach_debugger
[0]PETSC ERROR: or see https://www.mcs.anl.gov/petsc/documentation/faq.html#valgrind
[0]PETSC ERROR: or try http://valgrind.org on GNU/linux and Apple Mac OS X to find memory corruption errors
[0]PETSC ERROR: configure using --with-debugging=yes, recompile, link, and run
[0]PETSC ERROR: to get more information on the crash.
[0]PETSC ERROR: Run with -malloc_debug to check if memory corruption is causing the crash.
application called MPI_Abort(MPI_COMM_WORLD, 50176059) - process 0
[unset]: write_line error; fd=-1 buf=:cmd=abort exitcode=50176059
:
system msg for write_line failure : Bad file descriptor

I appreciate any help regarding this issue.

This indeed looks like a bug. I’ll have a closer look tomorrow.

1 Like

So the issue is that

is wrong. here, only the markers should be int8, not the indices. Note that you should also sort the indices, to ensure that meshtags works as it should. I’ve added a fully working code below:


import numpy as np
from dolfinx import RectangleMesh, MeshTags
from dolfinx.io import XDMFFile
from dolfinx.mesh import refine, locate_entities
from dolfinx.cpp.mesh import GhostMode, CellType
from mpi4py import MPI


W = 380e-6  # width
H = 180e-6  # height

d_ele = 20e-6
delta = 1e-6


n_W = int(np.ceil(W/d_ele))  # number of elements along W
n_H = int(np.ceil(H/d_ele))  # number of elements along H

# Create mesh
mesh = RectangleMesh(MPI.COMM_WORLD,
                     [np.array([-W/2, -H/2, 0]), np.array([W/2, H/2, 0])],
                     [n_W, n_H],
                     CellType.triangle, GhostMode.none)


def inside_delta(xs):
    out = []
    for i in np.arange(xs.shape[1]):
        x, y = xs[0, i], xs[1, i]
        if W/2 - abs(x) <= 30*delta or H/2 - abs(y) <= 30*delta:
            out.append(True)
        else:
            out.append(False)
    return np.asarray(out)


def mark_all(xs):
    return np.ones(xs.shape[1])


# boundary layer
boundaries = [(1, lambda x: inside_delta(x))]

for _ in np.arange(5):
    refine_indices, refine_markers = [], []
    dim = mesh.topology.dim
    for (marker, locator) in boundaries:
        facets = locate_entities(mesh, dim, locator)
        refine_indices.append(facets)
        refine_markers.append(np.full(len(facets), marker))

    # Only markers should be int8
    refine_indices = np.array(np.hstack(refine_indices), dtype=np.int32)
    refine_markers = np.array(np.hstack(refine_markers), dtype=np.int8)
    # Indices sent into meshtags should be sorted
    sort = np.argsort(refine_indices)
    refine_tag = MeshTags(
        mesh, dim, refine_indices[sort], refine_markers[sort])

    out_tag = MeshTags(
        mesh, dim, refine_indices[sort], np.asarray(refine_markers[sort], dtype=np.int32))

    with XDMFFile(mesh.mpi_comm(), f"mesh_{_}.xdmf", "w") as xdmf:
        xdmf.write_mesh(mesh)
        xdmf.write_meshtags(out_tag)

    mesh.topology.create_entities(1)
    mesh = refine(mesh, refine_tag)

with XDMFFile(mesh.mpi_comm(), f"mesh_{_+1}.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
3 Likes

The posted fixed example by @dokken no longer works, because the dolfinx API has changed.

The following code works with newer dolfinx versions (was tested with dolfinx 0.5.1)

import numpy as np
import pyvista
from dolfinx.mesh import CellType, create_rectangle, locate_entities, refine
from dolfinx.plot import create_vtk_mesh
from mpi4py import MPI

W = 380e-6  # width
H = 180e-6  # height

d_ele = 20e-6
delta = 1e-6


n_W = int(np.ceil(W / d_ele))  # number of elements along W
n_H = int(np.ceil(H / d_ele))  # number of elements along H

# Create mesh
mesh = create_rectangle(
    MPI.COMM_WORLD,
    points=((-W / 2, -H / 2), (W / 2, H / 2)),
    n=(n_W, n_H),
    cell_type=CellType.triangle,
)


def inside_delta(xs):
    # refine around the boundary
    return np.logical_or(
        W / 2 - abs(xs[0, :]) <= 30 * delta,
        H / 2 - abs(xs[1, :]) <= 30 * delta,
    )


for _ in np.arange(5):
    dim = mesh.topology.dim
    edges = locate_entities(mesh, dim - 1, inside_delta)
    mesh.topology.create_entities(1)
    mesh = refine(mesh, edges, redistribute=False)

    vtkdata = create_vtk_mesh(mesh, mesh.topology.dim)
    plotter = pyvista.Plotter()
    grid = pyvista.UnstructuredGrid(*vtkdata)
    actor = plotter.add_mesh(grid, show_edges=True)
    plotter.view_xy()
    plotter.show()

and it generates plots of the refined mesh

4 Likes

Hello, I’ve been trying to use this code with a mesh brought in from gmsh, and then solving a basic Poisson problem on it, and I run an issue about “Entity-to-cell connectivity has not been computed”, I after a lot of searching, it’s not clear what needs doing to actually use the refined mesh along with prior facet markers from gmsh. I know I can refine the mesh again in gmsh, but in the future, I want to create adaptive meshing within fenicsx based on solution/gradients/etc.

Here’s a MWE that I am using to work through this:

#!/usr/bin/env python

from dolfinx import mesh
from dolfinx import fem
from dolfinx.fem.petsc import LinearProblem
from dolfinx.io import XDMFFile, gmshio
from dolfinx.fem import functionspace, dirichletbc, Function
from dolfinx.plot import vtk_mesh as create_vtk_mesh

import pyvista

import numpy as np

from petsc4py import PETSc
import ufl
from ufl import div, dx, grad, inner, dot

from mpi4py import MPI
from basix.ufl import element, mixed_element

import gmsh

# Size of mesh
H = 1.0
L = 1.0
# Size of obstacle
r = 0.05
# location of obstacle
c_x = c_y = 0.5
# Domain dimension
gdim = 2

# Create mesh
gmsh.initialize()

if MPI.COMM_WORLD.rank == 0:
    # Create as rectangular domain with hole in the center
    rectangle = gmsh.model.occ.addRectangle(0, 0, 0, L, H, tag=1)
    obstacle = gmsh.model.occ.addDisk(c_x, c_y, 0, r, r)

    # "Fluid" domain
    fluid = gmsh.model.occ.cut([(gdim, rectangle)], [(gdim, obstacle)])
    fluid_marker = 1
    gmsh.model.occ.synchronize()

    volumes = gmsh.model.getEntities(dim=gdim)
    assert (len(volumes) == 1)
    gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], fluid_marker)
    gmsh.model.setPhysicalName(volumes[0][0], fluid_marker, "Fluid")

    # There will be an "inlet" on the left side, walls, and the cylindrical obstacle
    inlet_marker, wall_marker, obstacle_marker = 2, 3, 4
    inflow, walls, obstacle = [], [], []

    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 / 2, H, 0]) or np.allclose(center_of_mass, [L / 2, 0, 0]) or np.allclose(center_of_mass, [L, H/2, 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, obstacle, obstacle_marker)
    gmsh.model.setPhysicalName(1, obstacle_marker, "Obstacle")

    # Improve mesh near obstacle
    res_min = r / 3
    res_max = H / 12
    distance_field = gmsh.model.mesh.field.add("Distance")
    gmsh.model.mesh.field.setNumbers(distance_field, "EdgesList", obstacle)
    threshold_field = gmsh.model.mesh.field.add("Threshold")
    gmsh.model.mesh.field.setNumber(threshold_field, "IField", distance_field)
    gmsh.model.mesh.field.setNumber(threshold_field, "LcMin", res_min)
    gmsh.model.mesh.field.setNumber(threshold_field, "LcMax", res_max)
    gmsh.model.mesh.field.setNumber(threshold_field, "DistMin", r)
    gmsh.model.mesh.field.setNumber(threshold_field, "DistMax", 5 * r)
    min_field = gmsh.model.mesh.field.add("Min")
    gmsh.model.mesh.field.setNumbers(min_field, "FieldsList", [threshold_field])
    gmsh.model.mesh.field.setAsBackgroundMesh(min_field)

    gmsh.model.mesh.generate(gdim)
    #gmsh.write('2Dcylinder.msh')

# converge gmsh model to doflinx mesh
msh, _, ft = gmshio.model_to_mesh(gmsh.model, MPI.COMM_WORLD, 0, gdim=gdim)

# Function define where mesh will be refined (outer edges)
def outer_edges(x):
    delta = 0.1
    return np.logical_or(np.logical_or(x[0] <= delta, x[0] >= L - delta),
                         np.logical_or(x[1] <= delta, x[1] >= H - delta))

# Mesh Refinement in FEniCSx (twice just to make it clear)
for n in range(2):
    dim = msh.topology.dim
    edges = mesh.locate_entities(msh, dim-1, outer_edges)
    msh.topology.create_entities(1)
    msh = mesh.refine(msh, edges, redistribute=False)
    msh.topology.create_connectivity(msh.topology.dim, 0)

# Visualize refined mesh
vtkdata = create_vtk_mesh(msh, msh.topology.dim)
plotter = pyvista.Plotter()
grid = pyvista.UnstructuredGrid(*vtkdata)
actor = plotter.add_mesh(grid, show_edges=True)
plotter.view_xy()
plotter.show()

# Set up and solve basic Poisson problem
V = functionspace(msh, ('Lagrange', 1))

# Dirichlet boundary condition at "inlet"
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(2))
bc_inlet = fem.dirichletbc(PETSc.ScalarType(1), dofs, V)

# Walls
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(3))
bc_walls = fem.dirichletbc(noslip, dofs)

# Obstacle
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(4))
bc_obstacle = fem.dirichletbc(noslip, dofs)

bcs = [bc_inlet, bc_walls, bc_obstacle]

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = Function(V)

a = dot(grad(u), grad(v)) * dx
L = f * v * dx

# Solve
problem = LinearProblem(a, L, bcs = bcs, petsc_options={'ksp_type': 'preonly', 'pc_type': 'lu'})
uh = problem.solve()

# Write to file
with XDMFFile(MPI.COMM_WORLD, "data2D.xdmf", "w") as ufile_xdmf:
    uh.x.scatter_forward()
    uh.name = 'Solution'
    ufile_xdmf.write_mesh(msh)
    ufile_xdmf.write_function(uh)

Change this to

msh.topology.create_connectivity(msh.topology.dim-1, msh.topology.dim)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(2))

Please see several other topics for the exact same error:

Thanks for the quick reply, Dokken. I did see those posts, but they did not leave me with an understanding of what create_connectivity(dim0, dim1) actually does, or if calling this function preserves facet markers from gmshio.model_to_mesh. I’m inferring that for dim0=1,dim1=2, connectivity is set up from (1-D) facets to their (2-D) cells, but maybe I’m wrong. Are more create_connectivity calls required to connect vertices to facets, or to cells?

At any rate, after placing your corrected code in mine, I see the results shown below the amended code below. It seems the facet tags for tag 2 – which was originally on the left-edge of the domain, as in the first picture – are now scattered throughout the domain.

#!/usr/bin/env python

from dolfinx import mesh
from dolfinx import fem
from dolfinx.fem.petsc import LinearProblem
from dolfinx.io import XDMFFile, gmshio
from dolfinx.fem import functionspace, dirichletbc, Function
from dolfinx.plot import vtk_mesh as create_vtk_mesh

import pyvista

import numpy as np

from petsc4py import PETSc
import ufl
from ufl import div, dx, grad, inner, dot

from mpi4py import MPI
from basix.ufl import element, mixed_element

import gmsh

# Size of mesh
H = 1.0
L = 1.0
# Size of obstacle
r = 0.05
# location of obstacle
c_x = c_y = 0.5
# Domain dimension
gdim = 2

# Create mesh
gmsh.initialize()

if MPI.COMM_WORLD.rank == 0:
    # Create as rectangular domain with hole in the center
    rectangle = gmsh.model.occ.addRectangle(0, 0, 0, L, H, tag=1)
    obstacle = gmsh.model.occ.addDisk(c_x, c_y, 0, r, r)

    # "Fluid" domain
    fluid = gmsh.model.occ.cut([(gdim, rectangle)], [(gdim, obstacle)])
    fluid_marker = 1
    gmsh.model.occ.synchronize()

    volumes = gmsh.model.getEntities(dim=gdim)
    assert (len(volumes) == 1)
    gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], fluid_marker)
    gmsh.model.setPhysicalName(volumes[0][0], fluid_marker, "Fluid")

    # There will be an "inlet" on the left side, walls, and the cylindrical obstacle
    inlet_marker, wall_marker, obstacle_marker = 2, 3, 4
    inflow, walls, obstacle = [], [], []

    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 / 2, H, 0]) or np.allclose(center_of_mass, [L / 2, 0, 0]) or np.allclose(center_of_mass, [L, H/2, 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, obstacle, obstacle_marker)
    gmsh.model.setPhysicalName(1, obstacle_marker, "Obstacle")

    # Improve mesh near obstacle
    res_min = r / 3
    res_max = H / 12
    distance_field = gmsh.model.mesh.field.add("Distance")
    gmsh.model.mesh.field.setNumbers(distance_field, "EdgesList", obstacle)
    threshold_field = gmsh.model.mesh.field.add("Threshold")
    gmsh.model.mesh.field.setNumber(threshold_field, "IField", distance_field)
    gmsh.model.mesh.field.setNumber(threshold_field, "LcMin", res_min)
    gmsh.model.mesh.field.setNumber(threshold_field, "LcMax", res_max)
    gmsh.model.mesh.field.setNumber(threshold_field, "DistMin", r)
    gmsh.model.mesh.field.setNumber(threshold_field, "DistMax", 5 * r)
    min_field = gmsh.model.mesh.field.add("Min")
    gmsh.model.mesh.field.setNumbers(min_field, "FieldsList", [threshold_field])
    gmsh.model.mesh.field.setAsBackgroundMesh(min_field)

    gmsh.model.mesh.generate(gdim)
    #gmsh.write('2Dcylinder.msh')

# converge gmsh model to doflinx mesh
msh, _, ft = gmshio.model_to_mesh(gmsh.model, MPI.COMM_WORLD, 0, gdim=gdim)

# Function define where mesh will be refined (outer edges)
def outer_edges(x):
    delta = 0.1
    return np.logical_or(np.logical_or(x[0] <= delta, x[0] >= L - delta),
                         np.logical_or(x[1] <= delta, x[1] >= H - delta))

# Mesh Refinement in FEniCSx (twice just to make it clear)
for n in range(2):
    dim = msh.topology.dim
    edges = mesh.locate_entities(msh, dim-1, outer_edges)
    msh.topology.create_entities(1)
    msh = mesh.refine(msh, edges, redistribute=False)
    msh.topology.create_connectivity(msh.topology.dim-1, msh.topology.dim)

# Visualize refined mesh
vtkdata = create_vtk_mesh(msh, msh.topology.dim)
plotter = pyvista.Plotter()
grid = pyvista.UnstructuredGrid(*vtkdata)
actor = plotter.add_mesh(grid, show_edges=True)
plotter.view_xy()
plotter.show()

# Set up and solve basic Poisson problem
V = functionspace(msh, ('Lagrange', 1))

# Dirichlet boundary condition at "inlet"
msh.topology.create_connectivity(msh.topology.dim-1, msh.topology.dim)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(2))
bc_inlet = fem.dirichletbc(PETSc.ScalarType(1), dofs, V)

# Walls
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(3))
bc_walls = fem.dirichletbc(noslip, dofs)

# Obstacle
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(4))
bc_obstacle = fem.dirichletbc(noslip, dofs)

bcs = [bc_inlet, bc_walls, bc_obstacle]

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = Function(V)

a = dot(grad(u), grad(v)) * dx
L = f * v * dx

# Solve
problem = LinearProblem(a, L, bcs = bcs, petsc_options={'ksp_type': 'preonly', 'pc_type': 'lu'})
uh = problem.solve()

# Write to file
with XDMFFile(MPI.COMM_WORLD, "data2D.xdmf", "w") as ufile_xdmf:
    uh.x.scatter_forward()
    uh.name = 'Solution'
    ufile_xdmf.write_mesh(msh)
    ufile_xdmf.write_function(uh)

Original (unrefined mesh)

Solution after mesh refinement:

See section 6 (page 14) of DOLFINx: The next generation FEniCS problem solving environment
and let me know if this clarifies the first part.

It is unrelated to that.

You can create the connectivity between any d0,d1, where d0, d1 in [0, …, mesh.topology.dim]
However, Most are not needed, and are a bit costly to compute.

You need to use the function refine_plaza and transfer_meshtags as done in

API
https://docs.fenicsproject.org/dolfinx/v0.8.0/python/generated/dolfinx.mesh.html#dolfinx.mesh.refine_plaza
https://docs.fenicsproject.org/dolfinx/v0.8.0/python/generated/dolfinx.mesh.html#dolfinx.mesh.transfer_meshtag

Beautiful, it’s all there. Thanks a ton Dokken, I really appreciate your time here.

If anyone is curious where my code landed for gmsh model → refinement in fenicsx (v0.8):

#!/usr/bin/env python

from dolfinx import mesh
from dolfinx import fem
from dolfinx.fem.petsc import LinearProblem
from dolfinx.io import XDMFFile, gmshio
from dolfinx.fem import functionspace, dirichletbc, Function
from dolfinx.plot import vtk_mesh as create_vtk_mesh

import pyvista

import numpy as np

from petsc4py import PETSc
import ufl
from ufl import div, dx, grad, inner, dot

from mpi4py import MPI
from basix.ufl import element, mixed_element

import gmsh

# Size of mesh
H = 1.0
L = 1.0
# Size of obstacle
r = 0.05
# location of obstacle
c_x = c_y = 0.5
# Domain dimension
gdim = 2

# Create mesh
gmsh.initialize()

if MPI.COMM_WORLD.rank == 0:
    # Create as rectangular domain with hole in the center
    rectangle = gmsh.model.occ.addRectangle(0, 0, 0, L, H, tag=1)
    obstacle = gmsh.model.occ.addDisk(c_x, c_y, 0, r, r)

    # "Fluid" domain
    fluid = gmsh.model.occ.cut([(gdim, rectangle)], [(gdim, obstacle)])
    fluid_marker = 1
    gmsh.model.occ.synchronize()

    volumes = gmsh.model.getEntities(dim=gdim)
    assert (len(volumes) == 1)
    gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], fluid_marker)
    gmsh.model.setPhysicalName(volumes[0][0], fluid_marker, "Fluid")

    # There will be an "inlet" on the left side, walls, and the cylindrical obstacle
    inlet_marker, wall_marker, obstacle_marker = 2, 3, 4
    inflow, walls, obstacle = [], [], []

    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 / 2, H, 0]) or np.allclose(center_of_mass, [L / 2, 0, 0]) or np.allclose(center_of_mass, [L, H/2, 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, obstacle, obstacle_marker)
    gmsh.model.setPhysicalName(1, obstacle_marker, "Obstacle")

    # Improve mesh near obstacle
    res_min = r / 3
    res_max = H / 12
    distance_field = gmsh.model.mesh.field.add("Distance")
    gmsh.model.mesh.field.setNumbers(distance_field, "EdgesList", obstacle)
    threshold_field = gmsh.model.mesh.field.add("Threshold")
    gmsh.model.mesh.field.setNumber(threshold_field, "IField", distance_field)
    gmsh.model.mesh.field.setNumber(threshold_field, "LcMin", res_min)
    gmsh.model.mesh.field.setNumber(threshold_field, "LcMax", res_max)
    gmsh.model.mesh.field.setNumber(threshold_field, "DistMin", r)
    gmsh.model.mesh.field.setNumber(threshold_field, "DistMax", 5 * r)
    min_field = gmsh.model.mesh.field.add("Min")
    gmsh.model.mesh.field.setNumbers(min_field, "FieldsList", [threshold_field])
    gmsh.model.mesh.field.setAsBackgroundMesh(min_field)

    gmsh.model.mesh.generate(gdim)
    #gmsh.write('2Dcylinder.msh')

# converge gmsh model to doflinx mesh
msh, _, ft = gmshio.model_to_mesh(gmsh.model, MPI.COMM_WORLD, 0, gdim=gdim)
print(ft)

# Function define where mesh will be refined (outer edges)
def outer_edges(x):
    delta = 0.1
    return np.logical_or(np.logical_or(x[0] <= delta, x[0] >= L - delta),
                         np.logical_or(x[1] <= delta, x[1] >= H - delta))

# Mesh Refinement in FEniCSx
dim = msh.topology.dim

for n in range(2):
    edges = mesh.locate_entities(msh, dim-1, outer_edges)
    msh.topology.create_entities(dim-1)
    f_to_c = msh.topology.connectivity(dim-1, dim)

    facet_indices = []

    for f in range(msh.topology.index_map(dim-1).size_local):
        if len(f_to_c.links(f)) == 1:
            facet_indices += [f]

    meshtag = mesh.meshtags(
            msh,
            dim-1,
            np.array(facet_indices, dtype=np.int32),
            ft.values,
            )

    fine_msh, parent_cell, parent_facet = mesh.refine_plaza(msh, edges, False, mesh.RefinementOption.parent_cell_and_facet)
    fine_msh.topology.create_entities(dim-1)
    new_ft = mesh.transfer_meshtag(meshtag, fine_msh, parent_cell, parent_facet)
    fine_msh.topology.create_connectivity(fine_msh.topology.dim-1, fine_msh.topology.dim)

    # Replacing old mesh with new refined mesh and meshtags
    msh = fine_msh
    ft = new_ft

# Visualize refined mesh
vtkdata = create_vtk_mesh(msh, msh.topology.dim)
plotter = pyvista.Plotter()
grid = pyvista.UnstructuredGrid(*vtkdata)
actor = plotter.add_mesh(grid, show_edges=True)
plotter.view_xy()
plotter.show()

# Set up and solve basic Poisson problem
V = functionspace(msh, ('Lagrange', 1))

# Dirichlet boundary condition at "inlet"
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(2))
bc_inlet = fem.dirichletbc(PETSc.ScalarType(1), dofs, V)

# Walls
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(3))
bc_walls = fem.dirichletbc(noslip, dofs)

# Obstacle
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(4))
bc_obstacle = fem.dirichletbc(noslip, dofs)

bcs = [bc_inlet, bc_walls, bc_obstacle]

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = Function(V)

a = dot(grad(u), grad(v)) * dx
L = f * v * dx

# Solve
problem = LinearProblem(a, L, bcs = bcs, petsc_options={'ksp_type': 'preonly', 'pc_type': 'lu'})
uh = problem.solve()

# Write to file
with XDMFFile(MPI.COMM_WORLD, "data2D.xdmf", "w") as ufile_xdmf:
    uh.x.scatter_forward()
    uh.name = 'Solution'
    ufile_xdmf.write_mesh(msh)
    ufile_xdmf.write_function(uh)
1 Like

I can’t find out the problem, I run similar code to fesolutions but the mesh.transfer_meshtag don’t return any facets.
the code should create 2 sphere and take the intersection between them, and I need to refine the mesh around a point of the inner sphere to solve it more precisely

    import gmsh
    import numpy as np
    from dolfinx import io, mesh
    from mpi4py import MPI

    gmsh.initialize()
    gmsh.model.add("spherical_shell")

    # Create the inner sphere (volume)
    inner_sphere = gmsh.model.occ.addSphere(0, 0, 0, a)
    gmsh.model.occ.synchronize()

    # Create the outer sphere (volume)
    outer_sphere = gmsh.model.occ.addSphere(0, 0, 0, R)
    gmsh.model.occ.synchronize()

    # Create the spherical shell by cutting the inner sphere from the outer sphere
    shell = gmsh.model.occ.cut([(3, outer_sphere)], [(3, inner_sphere)])
    gmsh.model.occ.synchronize()

    # Get the volume tag of the spherical shell
    shell_volume = shell[0][0][1]

    # Get the surfaces of the spherical shell
    shell_surfaces = gmsh.model.occ.getEntities(dim=2)
    gmsh.model.occ.synchronize()

    # Identify inner and outer boundary surfaces
    inner_surface = None
    outer_surface = None
    for (dim, tag) in shell_surfaces:
        # Compute the surface area (mass) of each surface
        mass = gmsh.model.occ.getMass(dim, tag)
        if np.isclose(mass, 4 * np.pi * a ** 2, rtol=0.01):
            inner_surface = tag
        elif np.isclose(mass, 4 * np.pi * R ** 2, rtol=0.01):
            outer_surface = tag

    # Ensure that the surfaces were identified
    if inner_surface is None or outer_surface is None:
        gmsh.finalize()
        raise RuntimeError("Could not identify inner or outer surfaces.")

    shell_volume_tag = 1
    inner_boundary_tag = 2
    outer_boundary_tag = 3

    # Add physical groups
    gmsh.model.addPhysicalGroup(3, [shell_volume], tag=shell_volume_tag)  # Spherical shell volume
    gmsh.model.setPhysicalName(3, 1, "ShellVolume")

    gmsh.model.addPhysicalGroup(2, [inner_surface], tag=inner_boundary_tag)  # Inner boundary
    gmsh.model.setPhysicalName(2, 1, "InnerBoundary")

    gmsh.model.addPhysicalGroup(2, [outer_surface], tag=outer_boundary_tag)  # Outer boundary
    gmsh.model.setPhysicalName(2, 2, "OuterBoundary")

    gmsh.model.occ.synchronize()

    # Set mesh sizes on the surfaces
    gmsh.model.mesh.setSize(
        gmsh.model.getBoundary([(2, inner_surface)], recursive=True),
        inner_mesh_size
    )
    gmsh.model.mesh.setSize(
        gmsh.model.getBoundary([(2, outer_surface)], recursive=True),
        outer_mesh_size
    )

    # Mesh generation
    gmsh.model.mesh.generate(3)

    # Convert to DOLFINx mesh
    msh, cell_tags, facet_tags = io.gmshio.model_to_mesh(
        gmsh.model, MPI.COMM_WORLD, 0, gdim=3
    )
    gmsh.finalize()

    # ------------------------------
    # Refinement in DOLFINx
    # ------------------------------
    dim = msh.topology.dim

    # Create connectivity for edges (dim-2 to dim-1)
    msh.topology.create_connectivity(dim - 1, dim)  # Create facet-cell connectivity
    msh.topology.create_connectivity(dim - 2, dim)  # Create edge-facet connectivity (for refinement)

    # Define the receptor area function
    def receptor_point(x):
        dist_squared = (x[0] - receptor_center[0])**2 + (x[1] - receptor_center[1])**2 + (x[2] - receptor_center[2])**2
        return dist_squared < receptor_radius**2

    for n in range(2):  # Refine twice (you can change this based on your needs)
        # Locate edges near the receptor
        edges = mesh.locate_entities(msh, dim - 1, receptor_point)

        # Ensure mesh has connectivity between facets and cells
        msh.topology.create_entities(dim - 1)
        f_to_c = msh.topology.connectivity(dim - 1, dim)

        facet_indices = []

        # Find facets on the boundary (those connected to only one cell)
        for f in range(msh.topology.index_map(dim - 1).size_local):
            if len(f_to_c.links(f)) == 1:
                facet_indices.append(f)

        # Create meshtag for refinement
        meshtag = mesh.meshtags(
            msh,
            dim - 1,
            np.array(facet_indices, dtype=np.int32),
            facet_tags.values,
        )

        # Debug: Check meshtag contents before refinement
        print("Meshtag values before refinement:", meshtag.values)

        # Refine the mesh using mesh.refine_plaza
        fine_msh, parent_cell, parent_facet = mesh.refine_plaza(
            msh, edges, redistribute=False
        )
        fine_msh.topology.create_entities(dim - 1)

        # Transfer meshtags to the refined mesh
        new_facet_tags = mesh.transfer_meshtag(meshtag, fine_msh, parent_cell, parent_facet)

        # Debug: Check if new_facet_tags contains expected values
        print("New facet tags after refinement:", new_facet_tags.values)

        # Check if new_facet_tags is empty
        if len(new_facet_tags.values) == 0:
            print("Warning: No facet tags were transferred after refinement!")

        fine_msh.topology.create_connectivity(fine_msh.topology.dim - 1, fine_msh.topology.dim)

        # Replace the old mesh with the refined one
        msh = fine_msh
        facet_tags = new_facet_tags

    return msh, cell_tags, facet_tags

by the way, I also tried the distance field method but it couldn’t finish running (defined a point on the inner sphere and tried to create the field around it) - maybe I need to do something different because it on 3D shape?

Your code is not reproducible, as you haven’t defined a or inner_mesh_size or outer_mesh_size or receptor_center or receptor_radius
and you have a trailing return indicating that this is part of some larger function.

Secondly, if your mesh is 3D, then dim-1=1, which is not equal to the dimension of edges, meaning that

is wrong, as this should be
edges = mesh.locate_entities(msh, 1, receptor_point)

For further help, please make an effort in making the problem reproducible.

You also need to use a RefineMentOption, i.e.

should be

 fine_msh, parent_cell, parent_facet = mesh.refine_plaza(
        msh, edges, redistribute=False, option=mesh.RefinementOption.parent_cell_and_facet
    )

Hello, I am back. I found that I could use gmsh tags with mesh refinement in serial, but when I try to run the process in parallel, I find the following error:

    return MeshTags(ftype(mesh.topology, dim, np.asarray(entities, dtype=np.int32), values))
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Indices and values arrays must have same size.

This does not happen when I use dolfinx meshes and indicate boundaries with simple functions. Any clue what’s going on? A MWE using a simple gmsh mesh is shown below.

#!/usr/bin/env python

from dolfinx import mesh
from dolfinx import fem
from dolfinx.fem.petsc import LinearProblem
from dolfinx.io import VTXWriter, gmshio
from dolfinx.fem import functionspace, dirichletbc, Function, Expression
from dolfinx.plot import vtk_mesh as create_vtk_mesh

import pyvista

import numpy as np

from petsc4py import PETSc
import ufl
from ufl import div, dx, grad, inner, dot

from mpi4py import MPI
from basix.ufl import element, mixed_element

import gmsh

# Size of mesh
H = 1.0
L = 1.0

# Domain dimension
gdim = 2

# Create mesh
gmsh.initialize()

if MPI.COMM_WORLD.rank == 0:
    # Create as rectangular domain with hole in the center
    rectangle = gmsh.model.occ.addRectangle(0, 0, 0, L, H, tag=1)

    # "Fluid" domain
    fluid_marker = 1
    gmsh.model.occ.synchronize()

    volumes = gmsh.model.getEntities(dim=gdim)
    assert (len(volumes) == 1)
    gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], fluid_marker)
    gmsh.model.setPhysicalName(volumes[0][0], fluid_marker, "Fluid")

    # There will be an "inlet" on the left side, and walls otherwise
    inlet_marker, wall_marker = 2, 3
    inflow, walls = [], []

    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 / 2, H, 0]) or np.allclose(center_of_mass, [L / 2, 0, 0]) or np.allclose(center_of_mass, [L, H/2, 0]):
            walls.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")

    # Typical element size
    res_max = H / 11

    gmsh.option.setNumber("Mesh.MeshSizeMax", res_max)

    gmsh.model.mesh.generate(gdim)

# converge gmsh model to doflinx mesh
msh, _, ft = gmshio.model_to_mesh(gmsh.model, MPI.COMM_WORLD, 0, gdim=gdim)

# -------------------------------------------------------------------------
# ----------------------- COARSE FE SOLVE ---------------------------------
# -------------------------------------------------------------------------

# Set up and solve basic Poisson problem
V = functionspace(msh, ('Lagrange', 1))

# Dirichlet boundary condition at "inlet"
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(2))
bc_inlet = fem.dirichletbc(PETSc.ScalarType(1), dofs, V)

# Walls
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(3))
bc_walls = fem.dirichletbc(noslip, dofs)

bcs = [bc_inlet, bc_walls]

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = Function(V)

a = dot(grad(u), grad(v)) * dx
L = f * v * dx

# Solve
problem = LinearProblem(a, L, bcs = bcs, petsc_options={'ksp_type': 'preonly', 'pc_type': 'lu'})
uh = problem.solve()


# -------------------------------------------------------------------------
# --------------------- ADAPTIVE REFINEMENT -------------------------------
# -------------------------------------------------------------------------

# Mesh Refinement in FEniCSx
# Use current mesh level to locate cells worth refining
Vg = functionspace(msh, ("DG", 0))
F = Expression(ufl.sqrt(ufl.dot(ufl.grad(uh), ufl.grad(uh))),
        Vg.element.interpolation_points()
        )
grad_norm = Function(Vg)
grad_norm.interpolate(F)

order = np.argsort(grad_norm.x.array)
cell_index = order[-int(0.3*order.size):-1]

msh.topology.create_connectivity(1,2)
edges = mesh.compute_incident_entities(msh.topology, cell_index.astype(np.int32), 2, 1)

# gmsh refinement to keep facet markers
dim = msh.topology.dim
msh.topology.create_entities(dim-1)
f_to_c = msh.topology.connectivity(dim-1, dim)

facet_indices = []

for f in range(msh.topology.index_map(dim-1).size_local):
    if len(f_to_c.links(f)) == 1:
        facet_indices += [f]

meshtag = mesh.meshtags(
        msh,
        dim-1,
        np.array(facet_indices, dtype=np.int32),
        ft.values,
        )

fine_msh, parent_cell, parent_facet = mesh.refine_plaza(msh, edges, True, mesh.RefinementOption.parent_cell_and_facet)
fine_msh.topology.create_entities(dim-1)

new_ft = mesh.transfer_meshtag(meshtag, fine_msh, parent_cell, parent_facet)
fine_msh.topology.create_connectivity(fine_msh.topology.dim-1, fine_msh.topology.dim)

# Replacing old mesh with new refined mesh and meshtags for when this is 
# done in a loop for multiple refinements
msh = fine_msh
ft = new_ft

# Visualize refined mesh
if MPI.COMM_WORLD.rank == 0:
    vtkdata = create_vtk_mesh(msh, msh.topology.dim)
    plotter = pyvista.Plotter()
    grid = pyvista.UnstructuredGrid(*vtkdata)
    actor = plotter.add_mesh(grid, show_edges=True)
    plotter.view_xy()
    plotter.show()

# Set up and solve basic Poisson problem on refined mesh
V = functionspace(msh, ('Lagrange', 1))

# Dirichlet boundary condition at "inlet"
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(2))
bc_inlet = fem.dirichletbc(PETSc.ScalarType(1), dofs, V)

# Walls
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(3))
bc_walls = fem.dirichletbc(noslip, dofs)

bcs = [bc_inlet, bc_walls]

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = Function(V)

a = dot(grad(u), grad(v)) * dx
L = f * v * dx

# Solve
problem = LinearProblem(a, L, bcs = bcs, petsc_options={'ksp_type': 'preonly', 'pc_type': 'lu'})
uh = problem.solve()

# Write to file
data_fname = 'data2D_gmsh_refine.bp'
with VTXWriter(MPI.COMM_WORLD, data_fname, [uh]) as xf:
    xf.write(0.0)

Ok, I figured out a bit of what was going on. When I transferred the meshtags with

meshtag = mesh.meshtags(
        msh,
        dim-1,
        np.array(facet_indices, dtype=np.int32),
        ft.values,
        )

I could have just used ft.indices instead of gathering up the facet_indices the hard way.

However, this brought me to the actual issue. When I use redistribute=True in refine_plaza, which I feel is appropriate given the goal of parallel processing, each rank will have a different set of cells than what the meshtags were based on, and things will no longer match up. So, now I need a new meshtag that is appropriate for the refined and redistributed cells.

@dokken , I feel like you’re the only one deep enough in the code to know what needs doing. I’m assuming I’ll need to use the parent_cell, parent_facet outputs from refine_plaza, find the facet_indices/facet_values associated with those cells/facets, create a new meshtag (post-refinement), and use that with transfer_meshtag.

Am I on the right path here? I haven’t seen anyone else do this kind of thing, and even the test_refinement.py code you linked to earlier did not test re-partitioning with transferring meshtags, and I did confirm that it breaks in the same way my code does when redistribute=True.

Thanks!

I’ve thought about this a bit more, and while the task I just posted is still a thing, my true problem is actually a bit more complicated…

Right now, I believe the code identifies the top 30% highest gradient magnitude cells in each rank, and then refines those cells. In this case, redistribution is not actually necessary, perhaps, as each rank will end up with the same kind of refinement.

However, the true problem that I, and I would think most folks doing this, would actually want to do, is to find the top 30% (or whatever fraction) cells across all ranks, and then refine those. In this case, there could be some MPI communication to view the entire global list of gradient magnitudes, get the top 30%, and then refine those cells within.

So, I guess now my task is to find out how I can use Allgather (or something) to analyze the global gradient magnitudes and their global cell indices, then pass that to relevant ranks, refine, and redistribute.

However, this does still lead me to the issue in the prior post, where refinement + redistribute leaves a mismatch between facet_indices and facet_markers.

EDIT:

Ok, I’ve solved this sub-task of refining based on a global list of gradient magnitudes, though it is probably not efficient (code below). There is probably a nice way to do it using the IndexMap functionality built into dolfinx, but I believe this works and gets back to the main task of getting meshtags for refined + redistributed meshes.

On this, I am still not sure what to do.

EDIT 2:

The parent_cell and parent_facet outputs seem to be the cells after partitioning, which means the ft values from the original gmshio.model_to_mesh are not helpful from this data.

It is looking like there’s a good reason I have seen no other code using gmsh meshtags with refinement + redistribute… is it even possible right now to do load-balancing mesh refinement while preserving meshtags?

#!/usr/bin/env python

from dolfinx import mesh
from dolfinx import fem
from dolfinx.fem.petsc import LinearProblem
from dolfinx.io import VTXWriter, gmshio
from dolfinx.fem import functionspace, dirichletbc, Function, Expression
from dolfinx.plot import vtk_mesh as create_vtk_mesh

import pyvista

import numpy as np

from petsc4py import PETSc
import ufl
from ufl import div, dx, grad, inner, dot

from mpi4py import MPI
from basix.ufl import element, mixed_element

import gmsh

comm = MPI.COMM_WORLD

if comm.rank == 0:
    print('=========================================', flush=True)
    print('   Setting up and solving new problem ', flush=True)
    print('=========================================', flush=True)

# Size of mesh
H = 1.0
L = 1.0

# Domain dimension
gdim = 2

# Create mesh
gmsh.initialize()
gmsh.option.setNumber("General.Terminal",0)

if comm.rank == 0:
    print('Generating mesh', flush=True)
    # Create as rectangular domain with hole in the center
    rectangle = gmsh.model.occ.addRectangle(0, 0, 0, L, H, tag=1)

    # "Fluid" domain
    fluid_marker = 1
    gmsh.model.occ.synchronize()

    volumes = gmsh.model.getEntities(dim=gdim)
    assert (len(volumes) == 1)
    gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], fluid_marker)
    gmsh.model.setPhysicalName(volumes[0][0], fluid_marker, "Fluid")

    # There will be an "inlet" on the left side, walls, and the cylindrical obstacle
    inlet_marker, wall_marker = 2, 3
    inflow, walls = [], []

    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 / 2, H, 0]) or np.allclose(center_of_mass, [L / 2, 0, 0]) or np.allclose(center_of_mass, [L, H/2, 0]):
            walls.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")

    # Improve mesh near obstacle
    res_max = H / 11

    gmsh.option.setNumber("Mesh.MeshSizeMax", res_max)

    gmsh.model.mesh.generate(gdim)

# converge gmsh model to doflinx mesh
msh, _, ft = gmshio.model_to_mesh(gmsh.model, comm, 0, gdim=gdim)

comm.Barrier()

# -------------------------------------------------------------------------
# ----------------------- COARSE FE SOLVE ---------------------------------
# -------------------------------------------------------------------------

# Set up and solve basic Poisson problem
if comm.rank == 0:
    print('Setting up function space', flush=True)

V = functionspace(msh, ('Lagrange', 1))

# Dirichlet boundary condition at "inlet"
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(2))
bc_inlet = fem.dirichletbc(PETSc.ScalarType(1), dofs, V)

# Walls
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(3))
bc_walls = fem.dirichletbc(noslip, dofs)

bcs = [bc_inlet, bc_walls]

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = Function(V)

a = dot(grad(u), grad(v)) * dx
L = f * v * dx

# Solve
if comm.rank == 0:
    print('Coarse solve', flush=True)

problem = LinearProblem(a, L, bcs = bcs, petsc_options={'ksp_type': 'preonly', 'pc_type': 'lu'})
uh = problem.solve()


# -------------------------------------------------------------------------
# --------------------- ADAPTIVE REFINEMENT -------------------------------
# -------------------------------------------------------------------------

if comm.rank == 0:
    print('Adative refinement', flush=True)

# Mesh Refinement in FEniCSx
# Use current mesh level to locate cells worth refining
Vg = functionspace(msh, ("DG", 0))
F = Expression(ufl.sqrt(ufl.dot(ufl.grad(uh), ufl.grad(uh))),
        Vg.element.interpolation_points()
        )
grad_norm = Function(Vg)
grad_norm.interpolate(F)

# Local process gradient magnitudes
local_grad_mag = grad_norm.x.array

# BEGIN IDENTIFICATION OF GLOBAL TOP 30% GRADIENT MAG CELLS
# Task: identify the top 30% gradient magnitude cells across
#       all ranks, and pass their local cell indices to a
#       local mesh refinment

# Create a list filled with local process rank value
local_rank_list = np.full(local_grad_mag.shape, comm.rank)
# Create a list with local process cell indices
local_cell_list = list(range(local_grad_mag.shape[0]))

# Gather all gradient magnitudes, with their rank list
# and local cell index list
global_grad_mag = np.hstack(comm.allgather(local_grad_mag))
global_rank_list = np.hstack(comm.allgather(local_rank_list))
global_local_cell_list = np.hstack(comm.allgather(local_cell_list))

# Determine the top 30% gradient magnitude cell indices (global)
order = np.argsort(global_grad_mag)
global_cell_index = order[-int(0.3*order.size):-1]

# Determine which ranks these top 30% cells belong to
global_rank_list = global_rank_list[global_cell_index]

# Determine the local cell indices within those ranks
global_local_cell_index = global_local_cell_list[global_cell_index]

# For each local process, identify which (global) indices to 
# pay attention to and refine
target_index = np.where(np.array(global_rank_list) == comm.rank)[0]

# Pick out the local cell index for those global indices
local_cell_index = global_local_cell_index[target_index]

# END GLOBAL TOP 30% IDENTIFICATION PROCESS

msh.topology.create_connectivity(1,2)

edges = mesh.compute_incident_entities(msh.topology, local_cell_index.astype(np.int32), 2, 1)

# gmsh refinement to keep facet markers
dim = msh.topology.dim
msh.topology.create_entities(dim-1)
msh.topology.create_entities(1)

meshtag = mesh.meshtags(
        msh,
        dim-1,
        ft.indices,
        ft.values,
        )

if comm.rank == 0:
    print('Refine plaza', flush=True)

fine_msh, parent_cell, parent_facet = mesh.refine_plaza(msh, edges, False, mesh.RefinementOption.parent_cell_and_facet)
fine_msh.topology.create_entities(dim-1)

if comm.rank == 0:
    print('Transfer meshtags', flush=True)

new_ft = mesh.transfer_meshtag(meshtag, fine_msh, parent_cell, parent_facet)

if comm.rank == 0:
    print('Create connectivity', flush=True)

fine_msh.topology.create_connectivity(fine_msh.topology.dim-1, fine_msh.topology.dim)

# Replacing old mesh with new refined mesh and meshtags for when this is 
# done in a loop for multiple refinements
msh = fine_msh
ft = new_ft

# Visualize refined mesh
'''
if comm.rank == 0:
    vtkdata = create_vtk_mesh(msh, msh.topology.dim)
    plotter = pyvista.Plotter()
    grid = pyvista.UnstructuredGrid(*vtkdata)
    actor = plotter.add_mesh(grid, show_edges=True)
    plotter.view_xy()
    plotter.show()
'''

if comm.rank == 0:
    print('Set up refined mesh function space', flush=True)

# Set up and solve basic Poisson problem
V = functionspace(msh, ('Lagrange', 1))

# Dirichlet boundary condition at "inlet"
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(2))
bc_inlet = fem.dirichletbc(PETSc.ScalarType(1), dofs, V)

# Walls
noslip = Function(V)
dofs = fem.locate_dofs_topological((V), msh.topology.dim-1, ft.find(3))
bc_walls = fem.dirichletbc(noslip, dofs)

bcs = [bc_inlet, bc_walls]

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = Function(V)

a = dot(grad(u), grad(v)) * dx
L = f * v * dx

# Solve
if comm.rank == 0:
    print('Solving refined problem', flush=True)

problem = LinearProblem(a, L, bcs = bcs, petsc_options={'ksp_type': 'preonly', 'pc_type': 'lu'})
uh = problem.solve()

# Write to file
data_fname = 'data2D_gmsh_refine_global.bp'
with VTXWriter(comm, data_fname, [uh]) as xf:
    xf.write(0.0)

if comm.rank == 0:
    print(f'Solution saved to file as {data_fname}', flush=True)

Final post here, in case anyone wants to create a mesh via gmsh and then do adaptive mesh refinement + load balancing while making use of mesh tags in gmsh in the future: as far as I can tell, the only way to do this right now is to have an unfortunate I/O stage where you write/read a refined mesh that was created without redistribution. The mesh read will then be distributed (I think), and you can read in meshtags with that read.

For example (after refinement to a fine mesh fine_msh with facet tags fine_ft):

# Create temporary xdmf file to store mesh with meshtags
tmp_msh_fname = 'tmp_msh.xdmf'
fine_msh.name = 'mesh'
fine_ft.name = 'facets'
with XDMFFile(comm, tmp_msh_fname, 'w') as xdmf:
    xdmf.write_mesh(fine_msh)
    xdmf.write_meshtags(fine_ft, fine_msh.geometry)

# Read to new (distributed) mesh, "msh"
with XDMFFile(comm, tmp_msh_fname, 'r') as xdmf:
    msh = xdmf.read_mesh(name='mesh', ghost_mode=mesh.GhostMode.none)
    msh.topology.create_connectivity(msh.topology.dim - 1, msh.topology.dim)
    msh.topology.create_entities(dim-1)
    ft = xdmf.read_meshtags(msh, name='facets')

I’ll try to pay attention to dolfinx/fenicsx in the future to see if this can be avoided, or, if anyone else has bright ideas, do please let me know.

The limitations of the built in refinement has been highlighted in: Pass graph partitioner to `mesh::refine` by garth-wells · Pull Request #3444 · FEniCS/dolfinx · GitHub and is hopefully going to be addressed in a future release.

1 Like

Thank you very much dokken :slight_smile:
It fixed my problem.
For future questions, the correct and working code is here:

import numpy as np
from mpi4py import MPI
import gmsh
import ufl
from dolfinx import fem, io, plot, mesh
from dolfinx.fem import dirichletbc, locate_dofs_geometrical, FunctionSpace, Constant
from dolfinx.fem.petsc import LinearProblem
from ufl import TrialFunction, TestFunction, grad, inner, ds, dx
import pyvista
from dolfinx.plot import vtk_mesh as create_vtk_mesh

a = 1.0
R = 10.0
inner_mesh_size = 0.05
outer_mesh_size = 1.0
receptor_center = (0.0, 0.0, 1.0)
receptor_radius = 0.1

import gmsh
import numpy as np
from dolfinx import io, mesh
from mpi4py import MPI

gmsh.initialize()
gmsh.model.add("spherical_shell")

# Create the inner sphere (volume)
inner_sphere = gmsh.model.occ.addSphere(0, 0, 0, a)
gmsh.model.occ.synchronize()

# Create the outer sphere (volume)
outer_sphere = gmsh.model.occ.addSphere(0, 0, 0, R)
gmsh.model.occ.synchronize()


# Create the spherical shell by cutting the inner sphere from the outer sphere
shell = gmsh.model.occ.cut([(3, outer_sphere)], [(3, inner_sphere)])
gmsh.model.occ.synchronize()

# gmsh.model.occ.dilate(gmsh.model.occ.getEntities(), 0.0, 0.0, 0.0, 1.0, 1.0, 3.0)

# Get the volume tag of the spherical shell
shell_volume = shell[0][0][1]

# Get the surfaces of the spherical shell
shell_surfaces = gmsh.model.occ.getEntities(dim=2)
gmsh.model.occ.synchronize()

# # Identify inner and outer boundary surfaces
# inner_surface = None
# outer_surface = None
# for (dim, tag) in shell_surfaces:
#     # Compute the surface area (mass) of each surface
#     mass = gmsh.model.occ.getMass(dim, tag)
#     if np.isclose(mass, 4 * np.pi * a ** 2, rtol=0.01):
#         inner_surface = tag
#     elif np.isclose(mass, 4 * np.pi * R ** 2, rtol=0.01):
#         outer_surface = tag

# # Ensure that the surfaces were identified
# if inner_surface is None or outer_surface is None:
#     gmsh.finalize()
#     raise RuntimeError("Could not identify inner or outer surfaces.")

shell_volume_tag = 1
inner_boundary_tag = 2
outer_boundary_tag = 3

# Add physical groups
gmsh.model.addPhysicalGroup(3, [shell_volume], tag=shell_volume_tag)  # Spherical shell volume
gmsh.model.setPhysicalName(3, 1, "ShellVolume")

gmsh.model.addPhysicalGroup(2, [inner_sphere], tag=inner_boundary_tag)  # Inner boundary
gmsh.model.setPhysicalName(2, 1, "InnerBoundary")

gmsh.model.addPhysicalGroup(2, [outer_sphere], tag=outer_boundary_tag)  # Outer boundary
gmsh.model.setPhysicalName(2, 2, "OuterBoundary")

gmsh.model.occ.synchronize()

# Set mesh sizes on the surfaces
gmsh.model.mesh.setSize(
    gmsh.model.getBoundary([(2, inner_sphere)], recursive=True),
    inner_mesh_size
)
gmsh.model.mesh.setSize(
    gmsh.model.getBoundary([(2, outer_sphere)], recursive=True),
    outer_mesh_size
)

# Mesh generation
gmsh.model.mesh.generate(3)

# Convert to DOLFINx mesh
msh, cell_tags, facet_tags = io.gmshio.model_to_mesh(
    gmsh.model, MPI.COMM_WORLD, 0, gdim=3
)
gmsh.finalize()

vtkdata = create_vtk_mesh(msh, msh.topology.dim)
plotter = pyvista.Plotter()
grid = pyvista.UnstructuredGrid(*vtkdata)
actor = plotter.add_mesh(grid, show_edges=True)
plotter.view_xy()
plotter.show()

# ------------------------------
# Refinement in DOLFINx
# ------------------------------
dim = msh.topology.dim

# Create connectivity for edges (dim-2 to dim-1)
msh.topology.create_connectivity(dim - 1, dim)  # Create facet-cell connectivity
msh.topology.create_connectivity(dim - 2, dim)  # Create edge-facet connectivity (for refinement)


# Define the receptor area function
def receptor_point(x):
    dist_squared = (x[0] - receptor_center[0]) ** 2 + (x[1] - receptor_center[1]) ** 2 + (
                x[2] - receptor_center[2]) ** 2
    return dist_squared < receptor_radius ** 2


for n in range(2):  # Refine twice (you can change this based on your needs)
    # Locate edges near the receptor
    edges = mesh.locate_entities(msh, 1, receptor_point)

    # Ensure mesh has connectivity between facets and cells
    msh.topology.create_entities(dim - 1)
    f_to_c = msh.topology.connectivity(dim - 1, dim)

    facet_indices = []

    # Find facets on the boundary (those connected to only one cell)
    for f in range(msh.topology.index_map(dim - 1).size_local):
        if len(f_to_c.links(f)) == 1:
            facet_indices.append(f)

    # Create meshtag for refinement
    meshtag = mesh.meshtags(
        msh,
        dim - 1,
        np.array(facet_indices, dtype=np.int32),
        facet_tags.values,
    )

    # Refine the mesh using mesh.refine_plaza
    fine_msh, parent_cell, parent_facet = mesh.refine_plaza(
        msh, edges, redistribute=False, option=mesh.RefinementOption.parent_cell_and_facet
    )
    fine_msh.topology.create_entities(dim - 1)

    # Transfer meshtags to the refined mesh
    new_facet_tags = mesh.transfer_meshtag(meshtag, fine_msh, parent_cell, parent_facet)

    fine_msh.topology.create_connectivity(fine_msh.topology.dim - 1, fine_msh.topology.dim)

    # Replace the old mesh with the refined one
    msh = fine_msh
    facet_tags = new_facet_tags

    vtkdata = create_vtk_mesh(msh, msh.topology.dim)
    plotter = pyvista.Plotter()
    grid = pyvista.UnstructuredGrid(*vtkdata)
    actor = plotter.add_mesh(grid, show_edges=True)
    plotter.view_xy()
    plotter.show()

1 Like