Connecting different physical groups (subdomains) together

Hello Everyone,
So sorry To write the whole code here. I try to make a domain with some cycles in it with different physical groups. But unfortunately, the result shows that they are not connected! And the flow can not diffuse through the cycles. How can I connect the surfaces together?
I know that gmsh.model.occ.fragment glue the surfaces together, but unfortunately, it is not working in my code! It’s several days that I am struggling with it, but no success. Therefore, I put the whole code here.
Thanks in advance.

import gmsh
import numpy as np

scale_of_Mesh = 1e-7
domain_size         = np.array([5, 5])
RadiusOfIonomers= 0.5

sphere_positions= np.array([[2.18503937, 1.88976378, 1.7519685,  4.78346457, 0.25590551, 4.68503937],
 [3.22834646, 3.68110236, 3.11023622, 1.31889764, 2.91338583, 2.18503937]])
sphere_radii= np.array([0.39215686, 0.39215686 ,0.39215686, 0.39215686 ,0.39215686, 0.39215686])

def translate_Microstructure(sphere_positions, sphere_radii, domain_size):
    gmsh.initialize()
    gmsh.option.setNumber("Geometry.Tolerance", 1e-20)
    gmsh.option.setNumber("Mesh.MshFileVersion", 2.0)
    gmsh.option.setNumber("Mesh.ScalingFactor", scale_of_Mesh)
    gmsh.model.add("Microstructure_2D")
    X_ionmerslist=[]
    Y_ionmerslist=[]
    for i in range (0, sphere_positions.shape[1]):
        Xionomers = sphere_positions[0,i]
        Yionomers = sphere_positions[1,i]
        X_ionmerslist.append(Xionomers)
        Y_ionmerslist.append(Yionomers)
    domain          = gmsh.model.occ.addRectangle(0,0,0, domain_size[0],domain_size[1], 1)
    carbon_circles  = []
    ionomer_circles = []
    for i in range(0, sphere_positions.shape[1]):
        x_ = sphere_positions[0,i]
        y_ = sphere_positions[1,i]
        r_ = sphere_radii[i]
        carbon_circles.append(gmsh.model.occ.addDisk(x_, y_, 0, r_, r_ ))
    for i in range(0, len(X_ionmerslist)):
        xIomer = X_ionmerslist[i]
        yIonomer = Y_ionmerslist[i]
        rIonomer = RadiusOfIonomers    
        Ionomer_thickness =0# (ionomer_radius_factor-1)*sphere_radii[i]
        offset_x = 0 #(random.randrange(-20,20))/100
        offset_y = 0 #(random.randrange(-20,20))/100
        ionomer_circles.append(gmsh.model.occ.addDisk(xIomer + offset_x, yIonomer + offset_y, 0, rIonomer+Ionomer_thickness, rIonomer+Ionomer_thickness ))
    
    # Fuse all the Ionomer together
    if len(ionomer_circles) > 1:
        all_ionomer_temp, _ = gmsh.model.occ.fuse( [(2, ionomer_circles[0])] ,  [(2, ionomer_circles[1])])
    elif len(ionomer_circles) == 1:
        all_ionomer_temp = [(2, ionomer_circles[0])]
    else:
        assert 'No Ionomer in the microstructure'
    for i in range(2, len(ionomer_circles)):
        all_ionomer_temp, _ = gmsh.model.occ.fuse( all_ionomer_temp,  [(2, ionomer_circles[i])])

    # Fuse all the carbon together
    if len(carbon_circles ) > 1:
        carbon, _ = gmsh.model.occ.fuse( [(2, carbon_circles[0])] ,  [(2, carbon_circles[1])])
    elif len(carbon_circles ) == 1:
        carbon = [(2, carbon_circles[0])]
    else:
        assert 'No Carbon in the microstructure'
    for i in range(2, len(carbon_circles)):
        carbon, _ = gmsh.model.occ.fuse( carbon,  [(2, carbon_circles[i])])
 
    # ionomer = all_ionomer_temp - carbon
    ionomer_and_carbon, fragmentation_list = gmsh.model.occ.fragment(all_ionomer_temp, carbon) # ionomer_and_carbon: fragment returns a list of both, ionomer and the carbon
    ionomer_temp_list = []
    carbon_temp_list  = []
    for i in range(0, len(fragmentation_list)):
        if i < len(all_ionomer_temp):
            for element in fragmentation_list[i]:
                ionomer_temp_list.append(element)
        else:
            for element in fragmentation_list[i]:
                carbon_temp_list.append(element)
    for car in carbon_temp_list:
        ionomer_temp_list.remove(car)

    domain_fragment_ionomer, fragmentation_list = gmsh.model.occ.fragment( [(2,domain)], ionomer_and_carbon )
    void = fragmentation_list[0]
    
    out     = [] # element not in fragmentation_list[0] --> therefore not inside the domain
    ionomer = [] # element in fragmentation_list[0] and the element in ionomer_and_carbon[i] is in the ionomer_temp_list[i-1]
    carbon  = [] # element in fragmentation_list[0] and the element in ionomer_and_carbon[i] is in the carbon_temp_list[i-1]
    for i in range(1, len(fragmentation_list)):
        temp_list = fragmentation_list[i]
        for element in temp_list:
            if element not in void:
                out.append(element)
                domain_fragment_ionomer.remove(element)
            else:
                void.remove(element)
                if ionomer_and_carbon[i-1] in ionomer_temp_list:
                    ionomer.append(element)
                else:
                    carbon.append(element) 
    
    gmsh.model.occ.dilate(carbon, x=0, y=0, z=0, a=scale_of_Mesh, b=scale_of_Mesh, c=1)
    gmsh.model.occ.dilate(ionomer, x=0, y=0, z=0, a=scale_of_Mesh, b=scale_of_Mesh, c=1)
    gmsh.model.occ.dilate(void, x=0, y=0, z=0, a=scale_of_Mesh, b=scale_of_Mesh, c=1)
    gmsh.model.occ.synchronize()
    gmsh.model.removeEntities(out, True)  # Delete outside parts recursively: also delete the curves outside

    def sort_Boundary(bcs_lis):
        inside_domain  = []
        edge_of_domain = []
        LeftOfedge =[]
        RightOfedge=[]
        epsilon_bcs    = 1e-12 * scale_of_Mesh

        for i in range(0, len(bcs_lis)):
            temp = bcs_lis[i]
            com      = gmsh.model.occ.getCenterOfMass(temp[0], abs(temp[1]))
            temp_idx =  abs(temp[1])
            if com[0] <= epsilon_bcs:
                LeftOfedge.append(temp_idx)
            if com[0] >= domain_size[0]*scale_of_Mesh-epsilon_bcs:
                RightOfedge.append(temp_idx)
            if com[0] <= epsilon_bcs or com[1] <= epsilon_bcs or com[0] >= domain_size[0]*scale_of_Mesh - epsilon_bcs \
                        or com[1] >= domain_size[1]*scale_of_Mesh-epsilon_bcs:
                edge_of_domain.append(temp_idx)
            else:
                inside_domain.append(temp_idx)
        return inside_domain, edge_of_domain, LeftOfedge , RightOfedge

    carbon_Boundary_tuple = gmsh.model.getBoundary(carbon)
    carbon_boundary_inside, carbon_boundary_edge,_,_ = sort_Boundary(carbon_Boundary_tuple)

    void_Boundary_tuple = gmsh.model.getBoundary(void)
    _, void_boundary_edge,Leftline,Rightline = sort_Boundary(void_Boundary_tuple)

    carbon_material_marker   = 1  # marker for the surfaces with carbon
    carbon_bcs_inside_marker = 2  # marker for the boundaries of carbon inside the domain
    ionomer_material_marker   = 4 # marker of the ionomer surfaces in the domain
    void_marker          = 7 # marker of the void surfaces in the domain
    void_bcs_edge_marker = 8 # marker of the boundaries of the void with the edge of the domain

    # mark Carbon
    carbon_idx = []
    for car in carbon:
        carbon_idx.append( car[1] )
    gmsh.model.addPhysicalGroup(2, carbon_idx, carbon_material_marker)
    gmsh.model.setPhysicalName(2, carbon_material_marker, "Carbon")
    gmsh.model.addPhysicalGroup(1, carbon_boundary_inside, carbon_bcs_inside_marker)
    gmsh.model.setPhysicalName(1, carbon_bcs_inside_marker, "Carbon_BCS_inside")

    # mark Ionomer
    ionomer_idx = []
    for ion in ionomer:
         ionomer_idx.append( ion[1] )
    gmsh.model.addPhysicalGroup(2, ionomer_idx, ionomer_material_marker)
    gmsh.model.setPhysicalName(2, ionomer_material_marker, "Ionomer")

    # mark void
    void_idx = []
    for voi in void:
        void_idx.append( voi[1] )
    gmsh.model.addPhysicalGroup(2, void_idx, void_marker)
    gmsh.model.setPhysicalName(2, void_marker, "Void")
    gmsh.model.addPhysicalGroup(1, void_boundary_edge, void_bcs_edge_marker)
    gmsh.model.setPhysicalName(1, void_bcs_edge_marker, "Void_BCS_Edge")
    
    #LeftandRightLine  
    gmsh.model.addPhysicalGroup(1, Leftline, 101)
    gmsh.model.setPhysicalName(1, 101, "Left Line") 
    gmsh.model.addPhysicalGroup(1, Rightline, 202)
    gmsh.model.setPhysicalName(1, 202, "Right Line")    

    # Decrease Mesh-Size when the mesh gets close to the carbon and ionomer boundaries (inside of domain)
    field_close_to_carbon_marker1 = 1
    field_close_to_carbon_marker2 = 2
    resolution_close_to_carbon    = 1e-1 * scale_of_Mesh
    max_resolution                = 2
    gmsh.model.mesh.field.add("Distance", field_close_to_carbon_marker1)
    gmsh.model.mesh.field.setNumbers(field_close_to_carbon_marker1, "CurvesList", carbon_boundary_inside)
    gmsh.model.mesh.field.setNumber(field_close_to_carbon_marker1, "NumPointsPerCurve", 100)
    gmsh.model.mesh.field.add("Threshold", field_close_to_carbon_marker2)
    gmsh.model.mesh.field.setNumber(field_close_to_carbon_marker2, "IField", field_close_to_carbon_marker1)
    gmsh.model.mesh.field.setNumber(field_close_to_carbon_marker2, "LcMin", resolution_close_to_carbon)
    gmsh.model.mesh.field.setNumber(field_close_to_carbon_marker2, "LcMax", max_resolution)
    gmsh.model.mesh.field.setNumber(field_close_to_carbon_marker2, "DistMin", 0)
    gmsh.model.mesh.field.setNumber(field_close_to_carbon_marker2, "DistMax", 2)
    
    min_mesh_size_field_marker = 20
    gmsh.model.mesh.field.add("Min", min_mesh_size_field_marker)
    gmsh.model.mesh.field.setNumbers(min_mesh_size_field_marker, "FieldsList", [field_close_to_carbon_marker2])
    gmsh.model.mesh.field.setAsBackgroundMesh(min_mesh_size_field_marker)

    gmsh.option.setNumber("Mesh.Smoothing", 100)
    gmsh.model.mesh.generate(2)
    gmsh.fltk.run()
    gmsh.finalize()

if __name__ == "__main__":
    translate_Microstructure(sphere_positions, sphere_radii, domain_size)

You need to make sure that you syncronize after certain operations, see: Continuity of potential at subdomain interface - #2 by sbhasan

@dokken Thank you so much for your answer.
I wrote the fragment before synchronize, but no changes in the result!

    gmsh.model.occ.dilate(carbon, x=0, y=0, z=0, a=scale_of_Mesh, b=scale_of_Mesh, c=1)
    gmsh.model.occ.dilate(ionomer, x=0, y=0, z=0, a=scale_of_Mesh, b=scale_of_Mesh, c=1)
    gmsh.model.occ.dilate(void, x=0, y=0, z=0, a=scale_of_Mesh, b=scale_of_Mesh, c=1)

    al,_=gmsh.model.occ.fragment(void,ionomer)
    gmsh.model.occ.fragment(al,carbon)
    
    gmsh.model.occ.synchronize()

I still get this picture!

I would strongly suggest trying to reduce the complexity of the problem. What happens if you reduce it to a single circle, and try to use fragments?
As Im not familiar with the dilate command Im not sure if you need to sync after them

1 Like

Hey,

seems like individually using the dilate-command on every subdomain separates the whole domain. Using

everything = carbon + ionomer + void
gmsh.model.occ.dilate(everything, x=0, y=0, z=0, a=scale_of_Mesh, b=scale_of_Mesh, c=1)

does the trick i think.

1 Like