How to use fixed displacement conditions on one vertex of an externally imported grid

I’m really sorry, through yesterday’s questioning I have already managed to apply fixed displacement conditions on one vertex of a built-in grid:https://fenicsproject.discourse.group/t/in-dolfinx-how-to-apply-a-fixed-displacement-condition-to-a-vertex/14886/2?u=french_fries
The reason why I used a built-in grid in the previous post was to present the problem more simply, but when I applied the solution to an externally imported grid, it didn’t seem to work, apart from the grid I didn’t make any other modifications, here is my code (applying fixed displacement conditions on the center of a circle located at the origin):

import numpy as np
from ufl import (tr, grad, Identity, TrialFunction, TestFunction, lhs, rhs, inner, dx)
from dolfinx import (mesh, io, default_scalar_type, fem)
from dolfinx.fem import functionspace, dirichletbc
from dolfinx.fem.petsc import LinearProblem
from mpi4py import MPI
from pathlib import Path
from dolfinx.io import gmshio

# Create mesh
domain, cell_markers, facet_markers = gmshio.read_from_msh("test.msh", MPI.COMM_WORLD, gdim=2)

def center(x):
    return np.isclose(x[0], 0) & np.isclose(x[1], 0)

E = 50e3
nu = 0.2
mu = E/2/(1+nu)
lmbda = E*nu/(1+nu)/(1-2*nu)
alpha = 1e-5

def eps(v):
    return 0.5 * (grad(v) + grad(v).T)
def sigma(v, dT):
    return (lmbda*tr(eps(v))- alpha*(3*lmbda+2*mu)*dT)*Identity(2) + 2.0*mu*eps(v)

VU = functionspace(domain, ("CG", 1, (domain.geometry.dim, )))
dU = TrialFunction(VU)
U_ = TestFunction(VU)
Wint = inner(sigma(dU, 1000), eps(U_)) * dx
aM = lhs(Wint)
LM = rhs(Wint)

fdim = 0
boundary_vertices = mesh.locate_entities_boundary(domain, fdim, center)
print(boundary_vertices)
u_D = np.array([0, 0], dtype = default_scalar_type)
bc = dirichletbc(u_D, fem.locate_dofs_topological(VU, fdim, boundary_vertices), VU)

# Compute solution
problem = LinearProblem(aM, LM, bcs=[bc], petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
Uf = problem.solve()

This is the geo file (a circular domain divided into four parts, ensuring the center falls on a vertex):

// Gmsh project created on Tue May 21 09:56:22 2024
SetFactory("OpenCASCADE");

r = 7.81;
Point(1) = {0, 0, 0, 1.0};
Point(2) = {0, r, 0, 1.0};
Point(3) = {r, 0, 0, 1.0};
Point(4) = {0, -r, 0, 1.0};
Point(5) = {-r, 0, 0, 1.0};

Circle(1) = {2, 1, 3};
Circle(2) = {3, 1, 4};
Circle(3) = {4, 1, 5};
Circle(4) = {5, 1, 2};
Line(5) = {1, 2};
Line(6) = {1, 3};
Line(7) = {1, 4};
Line(8) = {1, 5};

Curve Loop(1) = {5, 1, -6};
Plane Surface(1) = {1};
Curve Loop(2) = {6, 2, -7};
Plane Surface(2) = {2};
Curve Loop(3) = {7, 3, -8};
Plane Surface(3) = {3};
Curve Loop(4) = {8, 4, -5};
Plane Surface(4) = {4};

Physical Curve("surface", 9) = {4, 1, 2, 3};
Physical Surface("circle", 10) = {4, 1, 2, 3};

This is the output (boundary_vertices is an empty list) and the visualization:

Info    : Reading 'test.msh'...
Info    : 289 nodes
Info    : 576 elements
Info    : Done reading 'test.msh'
[]

84d1980ae897f7b69193abc1283efda

You might have to tweak the tolerance to np.isclose. From numpy.isclose — NumPy v1.26 Manual the default value is 1e-8 (I am looking at atol, the rtol is unused here, because the denominator of the relative error would be zero, since the point is located at the origin), which might be too tight for the mesh generated by gmsh.

At the moment, I’m not sure how to modify the tolerance of np.isclose , or if there are other ways to define a vertex subdomain, similar to how it’s done in dolfin (the code snippet below works well on the same mesh):

class Gamma(SubDomain):
    def inside(self, x, on_boundary):
        return near(x[0], 0, DOLFIN_EPS) and near(x[1], 0, DOLFIN_EPS)
Center = Gamma()
bc = DirichletBC(V, Constant((0, 0)), Center, method="pointwise")

As shown in the figure, I found the source code of isclose in the numpy library, but changing atol to 1e-14 or even 1e-40 didn’t work. As I mentioned above, is there a method similar to DOLFIN’s for defining vertex subdomains? That should be a better approach.
7f7c244aa1e9de0ff729f3a0b4ac805

You are going the other way around. The default tolerance is too tight, you need to relax it. Something like

def center(x):
    return np.isclose(x[0], 0, atol=1e-4) & np.isclose(x[1], 0, atol=1e-4)

and then double check that it only finds one point. Of course, if you relax it too much, you may end up finding too many points.

See also: How to set 'boundary condition' inside the boundary? - #11 by dokken on how to embed points in your mesh to guarantee its there.

Sorry for the misunderstanding earlier. I’ve now specified the value of atol in the code as per your suggestion, but regardless of how I modify it (increase or decrease), it doesn’t seem to take effect. It might not be due to the tolerance, and I’m trying Dokken’s suggestion.

Dear Dokken, following your advice, I used locate_entities , and the results seem to meet the requirements (see attached):
28b445f2d9fdee4a7452ff19d3959a7

VU = functionspace(domain, ("CG", 1, (domain.geometry.dim, )))
# boundary conditions
center = locate_entities(domain, 0, lambda x: np.isclose(x[0],0) & np.isclose(x[1], 0))
print(center)
u_D = np.array([0, 0], dtype = default_scalar_type)
bc = dirichletbc(u_D, center, VU)

However, I’m still unsure about the meaning of this code snippet. It’s possible that it just happened to yield the correct result by chance (or perhaps the code is still incorrect). I understand that dirichletbc requires the dofs parameter, but the return value of locate_entities is “Indices (local to the process) of marked mesh entities.” Does this have any relevance to dofs ? Could you provide some explanation to help me confirm whether this code snippet is correct?

You are missing a step. locate_entities with a 0 as dimensional input find all vertices satisfying your constraint. You still need to call `locate_dofs_topological with these vertices as input to get the dof value.

As @dokken noticed, your issue is that you were using locate_entities_boundary instead of locate_entities. I missed that when reading your code, so it’s likely that tolerance had nothing to do with it.

I added locate_dofs_topological

fdim = 0
center = locate_entities(domain, 0, lambda x: np.isclose(x[0],0) & np.isclose(x[1], 0))
print(center)
dofs = locate_dofs_topological(VU, fdim, center)
print(dofs)
u_D = np.array([0, 0], dtype = default_scalar_type)
bc = dirichletbc(u_D, dofs, VU)

but then encountered the following error:

Traceback (most recent call last):
  File "/home/dyfluid/Desktop/dolfinx/test6.py", line 35, in <module>
    dofs = locate_dofs_topological(VU, fdim, center)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dyfluid/anaconda3/envs/fenicsx-env/lib/python3.11/site-packages/dolfinx/fem/bcs.py", line 86, in locate_dofs_topological
    return _cpp.fem.locate_dofs_topological(V._cpp_object, entity_dim, _entities, remote)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Entity-to-cell connectivity has not been computed.
[190]

You’re right, and thanks again for your prompt response.

See for instance How to compute entity-to-cell connectivity? . Those sort of errors are very easy to search for and have likely been encountered by others in the past, please learn how to use the search button.

Thank you, the content in this link solved my problem.