Mesh.locate_entities_boundary() does not seem to work if importing mesh nodes and cells

Hi,

When I generate a mesh using dolfinx, I can do the following and it generates the right boundary condition:

# Function to mark noslip bc
def noslip_boundary2(x):
    return np.isclose(x[2], z_val)
# No slip boundary condition 2
noslip  = fem.Function(V)
facets2 = mesh.locate_entities_boundary(msh, fdim, noslip_boundary2)
dofs2   = fem.locate_dofs_topological((W.sub(0), V), fdim, facets2)
bc1     = fem.dirichletbc(noslip, dofs2, W.sub(0))

But when I import a mesh using meshio, using the same above two functions does not work. Am I missing a line to generate boundary facets? How is that done?

How can I generate the cell-node connectivity matrix vol_mesh.cells[1].data from msh? (to make a running example)?

vol_mesh = meshio.read('mesh.vtk')

points = vol_mesh.points
cells  = vol_mesh.cells[1].data

c_el = ufl.Mesh(basix.ufl.element("Lagrange", "tetrahedron", 1, shape=(points.shape[1],)))
msh = mesh.create_mesh(MPI.COMM_WORLD, cells, points, c_el)

fdim = msh.topology.dim - 1

msh.topology.create_connectivity(fdim, fdim+1)

Thank you,
Alex

Could you be more specific? Does it throw an error or are you getting an unexpected result?

Either way, we would need a reproducible example with the full error trace to help you further

Hi, sure, I am importing a vtk mesh:

I tried the same code using a box generated by dolfinx and the code works. If I use the mesh I get this for the velocity magnitude:

The boundary conditions should be no slip at the cone and top cylinder (not the top cap) boundary facets, and at the bottom cap (which is not working at all)

Thank you for your time,
Alex

This is the running code:

vol_mesh = meshio.read('mesh.vtk')

points = vol_mesh.points
cells  = vol_mesh.cells[1].data

c_el = ufl.Mesh(basix.ufl.element("Lagrange", "tetrahedron", 1, shape=(points.shape[1],)))
msh = mesh.create_mesh(MPI.COMM_WORLD, cells, points, c_el)

fdim = msh.topology.dim - 1

msh.topology.create_connectivity(fdim, fdim+1)


# Geometry variables
h_array_1 = 0.02 
h_array_2 = 0.035
h_gap     = 0.03


rho_air = fem.Constant(msh, 1.225)
mu_air  = fem.Constant(msh, 1.8e-3)


# Function to mark noslip
def noslip_boundary1(x):
    return np.logical_and(x[2] < h_array_2, x[2] > -h_array_1)


# Function to mark noslip
def noslip_boundary2(x):
    return np.isclose(x[2], (h_array_2 + h_gap))


# the same as old ufl.VectorElement
# the same as old ufl.FiniteElement
P2 = basix.ufl.element("Lagrange", msh.topology.cell_name(), 2, shape=(msh.geometry.dim, ))
P1 = basix.ufl.element("Lagrange", msh.topology.cell_name(), 1)

V, Q = fem.functionspace(msh, P2), fem.functionspace(msh, P1)

# Create the function space
TH = basix.ufl.mixed_element([P2, P1])
W = fem.functionspace(msh, TH)
W0, _ = W.sub(0).collapse()


# No slip boundary condition 1
noslip  = fem.Function(V)
facets1 = mesh.locate_entities_boundary(msh, fdim, noslip_boundary1)
dofs1   = fem.locate_dofs_topological((W.sub(0), V), fdim, facets1)
bc0     = fem.dirichletbc(noslip, dofs1, W.sub(0))

# No slip boundary condition 2
facets2 = mesh.locate_entities_boundary(msh, fdim, noslip_boundary2)
dofs2   = fem.locate_dofs_topological((W.sub(0), V), fdim, facets2)
bc1     = fem.dirichletbc(noslip, dofs2, W.sub(0))

# Since for this problem the pressure is only determined up to a constant, we pin it at a point
zero = fem.Function(Q)
zero.x.array[:] = 0
dofs = fem.locate_dofs_geometrical(
    (W.sub(1), Q), lambda x: np.isclose(x.T, [0.0, 0.0, -h_array_1], atol=0.01).all(axis=1))
bc2 = fem.dirichletbc(zero, dofs, W.sub(1))


def body_force_fun(x):
    F_max    = 2.0e-2
    z_offset = 0.02

    r_xy = np.sqrt(x[0]**2 + x[1]**2)

    a = 0.005
    max_val = a * np.exp(-0.5)
    Fr_val  = np.exp(-r_xy**2/(2*a**2)) / max_val
        
    b = 0.005
    Fz_val = np.exp(-(x[2]-z_offset)**2/(2*b**2))
    F_mag  = F_max * Fr_val * Fz_val
      
    # -ve sign to get clockwise rotation from array top
    return -F_mag * np.vstack((x[1], -x[0], np.zeros_like(x[2])))
        
Force_per_unit_vol = fem.Function(V)
Force_per_unit_vol.interpolate(lambda x: body_force_fun(x))


# Collect Dirichlet boundary conditions
bcs = [bc0, bc1, bc2]

# Define variational problem
w = fem.Function(W)

(u, p) = ufl.split(w)
(v, q) = ufl.TestFunctions(W)


# Tentative velocity step

# https://github.com/bragostin/Fenics/blob/main/HTC_SquareDuct_Steady_Neumann_Minimal_Pub.py

# https://math.stackexchange.com/questions/1952588/
# weak-form-of-steady-navier-stokes-equations-with-special-boundary-condition

F = rho_air*inner(grad(u)*u, v)*dx + \
    mu_air*inner(grad(u), grad(v))*dx + \
    inner(Force_per_unit_vol, v)*dx - \
    inner(p, div(v))*dx - \
    div(u)*q*dx

dw = ufl.TrialFunction(W)
dF = ufl.derivative(F, w, dw)

problem = NonlinearProblem(F, w, bcs=bcs, J=dF)

solver = NewtonSolver(MPI.COMM_WORLD, problem)
solver.convergence_criterion = "incremental"
solver.rtol   = 1e-6
#solver.max_it = 2
solver.report = True
solver.error_on_nonconvergence = False


# Custom solver options (not needed for code readibility but makes code 9x faster)
opts = PETSc.Options()  # type: ignore
ksp = solver.krylov_solver
option_prefix = ksp.getOptionsPrefix()
opts[f"{option_prefix}ksp_type"] = "preonly"
opts[f"{option_prefix}pc_type"] = "lu"
opts[f"{option_prefix}pc_factor_mat_solver_type"] = "mumps"
ksp.setFromOptions()


# Compute the solution
log.set_log_level(log.LogLevel.INFO)
start = time.time()
solver.solve(w)
end   = time.time()

print('\n')
print(msh.topology.index_map(msh.topology.dim).size_local)
print((end - start) / 60)


# Split the mixed solution and collapse
u = w.sub(0).collapse()
p = w.sub(1).collapse()


# Write the solution to file
with io.XDMFFile(MPI.COMM_WORLD, "pressure.xdmf", "w") as pfile_xdmf:
    p.x.scatter_forward()
    pfile_xdmf.write_mesh(msh)
    pfile_xdmf.write_function(p)

with io.XDMFFile(MPI.COMM_WORLD, "velocity.xdmf", "w") as ufile_xdmf:
    u.x.scatter_forward()
    P1 = basix.ufl.element("Lagrange", msh.basix_cell(), 1, shape=(msh.geometry.dim,))
    u1 = fem.Function(fem.functionspace(msh, P1))
    u1.interpolate(u)
    ufile_xdmf.write_mesh(msh)
    ufile_xdmf.write_function(u1)
    

with io.XDMFFile(MPI.COMM_WORLD, "body_force.xdmf", "w") as ufile_xdmf:
    Force_per_unit_vol.x.scatter_forward()
    P1 = basix.ufl.element("Lagrange", msh.basix_cell(), 1, shape=(msh.geometry.dim,))
    u1 = fem.Function(fem.functionspace(msh, P1))
    u1.interpolate(Force_per_unit_vol)
    ufile_xdmf.write_mesh(msh)
    ufile_xdmf.write_function(u1)

I sorted it out, I was not considering the meshing program is not placing the facets very accurately on the surfaces, the marking functions should be as follows:

mark_eps = 2.0e-3

# Function to mark noslip
def noslip_boundary1(x):
    return np.logical_and(x[2] < (h_array_2 + mark_eps), np.sqrt(x[0]**2 + x[1]**2) > (r2-mark_eps))

# Function to mark noslip
def noslip_boundary2(x):
    return np.isclose(x[2], (h_array_2 + h_gap), mark_eps)