Marking surfaces of a 3D mesh (mshr) to apply b.c

I see, thanks again. I am working on it, the python dolfin file is about 100 lines, but it is working fine and I feel I only changed the size of the mesh. Maybe I removed some part(s) of the code but I don’t see it. This modified code is working fine, or at least seems to be (the solution looks roughly OK at first glance, and there is no error returned).
Maybe it’s some parameter in the file.geo that isn’t appropriate, which I fixed by modifying some number, but I don’t see what exactly yet. I’ll investigate… I hope to find the culprit. Anyway I still have to define dS.

How would I do that, assuming that I have specified a Physical Surface() in the file.geo for Gmsh, which is the interface between the 2 blocks? I can only find examples of ds and dx but not dS.

Edit: I figured out what returned the error. I had messed up the “commentation” of the mshr part. So I used both meshio and mshr instead of just meshio, so it’s not surprising that something went wrong.

I have got a rather complex example here, solving Shape optimization problems were the gradient is dependent on internal boundaries: https://github.com/jorgensd/MultiMeshShapeOpt_code/blob/master/Poisson_MultiCable/single_mesh/MulticableSolver.py#L171 syntax is there.

I see, so in my case it should just be dS = Measure(“dS”, domain=mesh, subdomain_data=mf, subdomain_id=N) where N stands for the number I see in Gmsh (7 for instance).
There is no difference with the ds elements, it’s just that the s is replaced by an S. Does this sound correct?

There is a difference, and that is that Since dS is an internal facet, it has multiple values in many cases (like with grad(u), as it is discontinuous between elements) Therefore you Need to restrict the function to either side, or use jump or avg of the quantity. This should really be clear from integration by parts and what boundary condition you would like to enforce on the internal facet.

I solve for temperature, so in first approximation I only want continuity at that internal boundary. I also do not want to apply any boundary condition there. I only want to compute the “spatially averaged” temperature over that surface.

If your are using CG 1 elements the solution is continuous, and you can chose either side. Note that the ("+") and ("-") restriction is arbitrary, and if you want to Get the integration to be on a specific side, a hack has to be made, see Integrating over an interior surface

Hmm.
I tried the following:

assemble(avg(u)*dS(domain=mesh, subdomain_id=2, subdomain_data=boundary_markers))
assemble(avg(u)*dS(subdomain_id=2, subdomain_data=boundary_markers))
assemble(avg(u)*dS(subdomain_id=2, subdomain_data=mf)))

They all evaluate to 0.0 which is not correct (should be around 300.0).

Try to assemble a constant (Like 1) to check that the markers are working as they should. As long as you dont produce a minimal example, i wont be able to help you further. In the code https://github.com/jorgensd/MultiMeshShapeOpt_code/blob/master/Poisson_MultiCable/single_mesh/create_mesh.py you can see how i Added physical lines to interior boundaries (note that the pygmsh syntax and meshio syntax there is old).

I think you should spend some time making sure everything is working as intended in your code.

When I do assemble(1.0*dS(domain=mesh,subdomain_id=2, subdomain_data=boundary_markers)) I also get 0 instead of the area of the surface. When I don’t specify domain=mesh I get an error.

For a MWE, here’s the file.geo that one compiles with gmsh file.geo -3:

Mesh.RandomFactor=1.0e-4;
Mesh.Algorithm = 7;
Mesh.Algorithm3D= 7;

l = 4;
w = 4;
t = 3;

l_diss = 4;
w_diss = 4;
t_diss = 1;

res_xy = 3e-01;
res_z = 1e-01;

Point(1) = {0.0, 0.0, 0.0, res_xy};
Point(2) = {l, 0.0, 0.0, res_xy};
Point(3) = {l, w, 0.0, res_xy};
Point(4) = {0.0, w, 0.0, res_xy};

Point(5) = {0.0, 0.0, t, res_xy};
Point(6) = {l, 0.0, t, res_xy};
Point(7) = {l, w, t, res_xy};
Point(8) = {0.0, w, t, res_xy};

Point(9) = {0.0, 0.0, t + t_diss, res_xy};
Point(10) = {l_diss, 0.0, t + t_diss, res_xy};
Point(11) = {l_diss, w_diss, t+t_diss, res_xy};
Point(12) = {0.0, w_diss, t + t_diss, res_xy};

Line(100) = {1,2};
Line(101) = {2,3};
Line(102) = {3,4};
Line(103) = {4,1};

Line(200) = {5,6};
Line(201) = {6,7};
Line(202) = {7,8};
Line(203) = {8,5};

Line(300) = {9,10};
Line(301) = {10,11};
Line(302) = {11,12};
Line(303) = {12,9};

Line(501) = {1,5};
Line(502) = {2,6};
Line(503) = {3,7};
Line(504) = {4,8};

Line(601) = {5,9};
Line(602) = {6,10};
Line(603) = {7,11};
Line(604) = {8,12};

Line Loop(1000) = {100,101,102,103};
Line Loop(2000) = {200,201,202,203};
Line Loop(3000) = {300,301,302,303};

Line Loop(5000) = {100,502,-200,-501};
Line Loop(5100) = {101,503,-201,-502};
Line Loop(5200) = {102,504,-202,-503};
Line Loop(5300) = {103,501,-203,-504};

Line Loop(6000) = {200,602,-300,-601};
Line Loop(6100) = {201,603,-301,-602};
Line Loop(6200) = {202,604,-302,-603};
Line Loop(6300) = {203,601,-303,-604};

Plane Surface(1001) = {1000};
Plane Surface(2001) = {2000};
Plane Surface(5001) = {5000};
Plane Surface(5101) = {5100};
Plane Surface(5201) = {5200};
Plane Surface(5301) = {5300};

Plane Surface(3001) = {3000};
Plane Surface(6001) = {6000};
Plane Surface(6101) = {6100};
Plane Surface(6201) = {6200};
Plane Surface(6301) = {6300};

Surface Loop(10000) = {1001,2001,5001,5101,5201,5301,3001,6001,6101,6201,6301};
Volume(10001) = {10000};

Field[1] = MathEvalAniso;
Field[1].m11 = "1/(10e-1)^2";
Field[1].m12 = "0.0";
Field[1].m13 = "0.0";
Field[1].m22 = "1/(10e-1)^2";
Field[1].m23 = "0.0";
Field[1].m33 = "1/(5e-1)^2";

Background Field = 1;
Mesh.CharacteristicLengthFromPoints = 0;
Mesh.CharacteristicLengthFromCurvature = 0;
Mesh.CharacteristicLengthExtendFromBoundary = 0;

Physical Surface("bottom_0") = {1001};
Physical Surface("top_0") = {2001};
Physical Surface("top_1") = {3001};
Physical Volume("All") = {10001};

Then the Python file:

import meshio
msh = meshio.read("simple_gmsh_mesh.msh")
for key in msh.cell_data_dict["gmsh:physical"].keys():
    if key == "triangle":
        triangle_data = msh.cell_data_dict["gmsh:physical"][key]
    elif key == "tetra":
        tetra_data = msh.cell_data_dict["gmsh:physical"][key]
for cell in msh.cells:
    if cell.type == "tetra":
        tetra_cells = cell.data
    elif cell.type == "triangle":
        triangle_cells = cell.data
tetra_mesh = meshio.Mesh(points=msh.points, cells={"tetra": tetra_cells},
                         cell_data={"name_to_read":[tetra_data]})
triangle_mesh =meshio.Mesh(points=msh.points,
                           cells=[("triangle", triangle_cells)],
                           cell_data={"name_to_read":[triangle_data]})
meshio.write("file2.xdmf", tetra_mesh)
meshio.write("mf2.xdmf", triangle_mesh)
mesh = Mesh()
with XDMFFile("file2.xdmf") as infile:
    infile.read(mesh)
mvc = MeshValueCollection("size_t", mesh, 2)
with XDMFFile("mf2.xdmf") as infile:
    infile.read(mvc, "name_to_read")
mf = cpp.mesh.MeshFunctionSizet(mesh, mvc)
mvc2 = MeshValueCollection("size_t", mesh, 3)
with XDMFFile("file2.xdmf") as infile:
    infile.read(mvc2, "name_to_read")
cf = cpp.mesh.MeshFunctionSizet(mesh, mvc2)
dx = Measure("dx", domain=mesh,subdomain_data=cf)
ds = Measure("ds", domain=mesh, subdomain_data=mf)
subdomains = cf
boundary_parts = mf
V = FunctionSpace(mesh, 'CG', 1)
T_bott, T_r, tol, blokus_height, top_topus = 273+60.0, 273+10.0, 1E-14, 3, 1
class Bottom(SubDomain):
    def inside(self, x , on_boundary):
       return on_boundary and near(x[2], 0.0, tol)
class Omega_0(SubDomain):
    def inside(self, x, on_boundary):
        return x[2] >= 0.0 + tol and x[2] <= blokus_height - tol
class Omega_1(SubDomain):
    def inside(self, x, on_boundary):
        return x[2] >= 3.63e-3 + tol and x[2] <= top_topus - tol
class top_blokus(SubDomain):
    def inside(self, x , on_boundary):
        return on_boundary and near(x[2], blokus_height, tol)
class top_dis(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[2], blokus_height + top_topus, tol)
boundary_markers = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_markers.set_all(0) 
ds = Measure('ds', domain=mesh, subdomain_data=boundary_markers)
subdomain_1 = top_blokus()
subdomain_1.mark(boundary_markers, 1)
subdomain_2 = top_dis()
subdomain_2.mark(boundary_markers, 2)
materials = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
subdomain_10 = Omega_0()
subdomain_11 = Omega_1()
subdomain_10.mark(materials, 10)
subdomain_11.mark(materials, 11)
k_0 = 1.0
k_1 = 2.0
t_f = 10
num_steps = 10
dt = t_f / num_steps
class K(UserExpression):
    def __init__(self, materials, k_0, k_1, **kwargs):
        super().__init__(**kwargs)
        self.materials = materials
        self.k_0 = k_0
        self.k_1 = k_1
    def eval_cell(self, values, x, cell):
        if self.materials[cell.index] == 0:
            values[0] = self.k_0
        else:
            values[0] = self.k_1
    def value_shape(self):
        return ()
kappa = K(materials, k_0, k_1, degree=1)
u_D0 = Constant(T_bott)
bc0 = DirichletBC(V, u_D0, Bottom())
u_0 = Expression('T_r', T_r = T_r, degree = 1)
u_n = interpolate(u_0, V)
u = TrialFunction(V)
v = TestFunction(V)
F = dt * dot(kappa*grad(u), grad(v)) * dx + v *(u-u_n)*dx
a, L = lhs(F), rhs(F)
u = Function(V)
t = 0
for n in range(num_steps):
    u_D0.t = t     
    t += dt
    solve(a == L, u, bcs = bc0)
    u_n.assign(u)
    print('the temp at the origin is ', u([0,0,0]))
    print('the temp at the top is ', u([0,0,blokus_height + top_topus]))
    print('debug line: ', assemble(avg(u)*dS(domain=mesh, subdomain_id=2, subdomain_data=boundary_markers)))

This is much longer than 20 lines… I do hope it’s not too much.

I just have to look at your mesh creation. You are not marking boundaries correctly (not sure if the mesh you are creating is what you would like either). This is what I obtain when i run the mesh creation part of your script (visualized in Paraview).

I do not understand the magnitude of the mesh, i.e. the colors. But the internal surface I wish to compute the temperature on (or u in the code) is definitely a plane like the one on the left side of your screenshot. I am worried about the colors though, as you say there is probably something fishy going on…

Edit: I just checked with Gmsh and the shape of the mesh looks fine, I am able to observe the same plane as your left side screenshot, this is where I want to compute the spatially averaged temperature.

The problem is that in the newest meshio, the input msh file split the cells in CellBlocks. See for example: Transitioning from mesh.xml to mesh.xdmf, from dolfin-convert to meshio where I combine all the blocks of the triangular elements. You need to do the same for tetrahedron elements (and triangular elements).
To make sure that you read in your correct mesh (before doing calculations) compute volume and surface areas (using assemble(Constant(1)*dx(domain=mesh, subdomain_data=... )) and similarly for dS and ds.

The volume is correct (4x4x4 and it yields 64).
The external surface gives 96. That’s 16 x 6, i.e. all sides of the region (I thought this would only include the 3 surfaces I had defined with Physical Surface in the file.geo!). Not too bad up till now.
The internal surface gives 683.76. So I guess it calculates every single surface inside the volume, not just the one I had specified with Physical Surface in the file.geo, again.

However as soon as I specify the id in print('test surface: ',assemble(Constant(1)*dS(domain=mesh, subdomain_data=mf, subdomain_id=2))) I seem to get 0.0 no matter what…

Then you should use boolean fragments to make sure that the internal interface is resolved in the mesh. I mentioned this many posts ago.

1 Like

Thank you very much for all, I’ll tackle this tomorrow. I have just checked your post Transitioning from mesh.xml to mesh.xdmf, from dolfin-convert to meshio and I hope it will be a one liner or similar. That would be nice.

Ok here I am again.
Here’s my attempt at a correct meshing, however Gmsh returns an error that the top block, volume(10003), has no element. I do not understand why… I can clearly see that the mesh looks OK at least visually, even in that zone.

The code is the same as before, here I past only the last lines:

//Surface Loop(10000) = {1001,2001,5001,5101,5201,5301,3001,6001,6101,6201,6301};
//Volume(10001) = {10000};
Surface Loop(10000) = {1001,2001,5001,5101,5201,5301};
Volume(10001) = {10000};
Surface Loop(10002)={3001,6001,6101,6201,6301,2001};
Volume(10003)={10002};
v() = BooleanFragments { Volume{10001,10003}; Delete; }{};


Field[1] = MathEvalAniso;
Field[1].m11 = "1/(10e-1)^2";
Field[1].m12 = "0.0";
Field[1].m13 = "0.0";
Field[1].m22 = "1/(10e-1)^2";
Field[1].m23 = "0.0";
Field[1].m33 = "1/(5e-1)^2";

Background Field = 1;
Mesh.CharacteristicLengthFromPoints = 0;
Mesh.CharacteristicLengthFromCurvature = 0;
Mesh.CharacteristicLengthExtendFromBoundary = 0;

Physical Surface("bottom_0") = {1001};
Physical Surface("top_0") = {2001};
Physical Surface("top_1") = {3001};
//Physical Volume("bott_block") = {10001};
//Physical Volume("top_block") = {10002};
Physical Volume("bott_block") = {#v()};
Physical Volume("top_block") = {#v()-1};

Does someone have an idea on what’s wrong?

You are not using boolean fragments correctly, see my previous post, or the Gmsh documentation and their respective turtorials.

I am not sure boolean fragment is the problem. I notice that even without using any boolean fragment, the volume 10001 seems to be the whole structure (at least its meshing is), while the smaller block has indeed no volume meshed inside of it (only its surface is meshed). So overall the whole volume is meshed, but it is not meshed by volumes correctly.
I’ve read the Gmsh documentation about the Boolean fragment (https://gmsh.info/doc/texinfo/gmsh.html#Boolean-operations), without understanding everything. I’ve seen some examples at https://gitlab.onelab.info/gmsh/gmsh/tree/master/demos/boolean, again without really understanding what’s going on.
Anyway I’ve tried things like v() = BooleanFragments { Volume{10001}; Delete; }{ Volume{10003}; Delete;}; to no avail. But like I said, I think that even with the correct BooleanFragments code, I think there’s something else that’s wrong.

In fact I am not even sure what the logic should be.
It seems I want to decompose the big volume into 2 smaller “block” volumes. If I do so, I will need to delete a Surface (not a volume, right?) from either volume.
So, something like v() = BooleanFragments { Volume{10001}; Delete; }{ Surface{2001}; Delete;};. Is this correct? I guess not… but I don’t see what else I could try.

You should rather build two smaller volumes, using boolean fragments to combine them with a common surface. Btw. You can Make the two boxes using the box command in Gmsh.