Generate mesh with perfect radial symmetry

Hello,
I would like to generate a two-dimensional ring mesh with perfect radial symmetry. I manage to generate a ring mesh it with the following code

import argparse
import gmsh
import warnings

parser = argparse.ArgumentParser()
parser.add_argument("resolution")
args = parser.parse_args()

warnings.filterwarnings("ignore")
gmsh.initialize()

gmsh.model.add("my model")
c_r = [0, 0, 0]
c_R = [0, 0, 0]
r = 1
R = 2
resolution = (float)(args.resolution)

disk_r = gmsh.model.occ.addDisk( c_r[0], c_r[1], c_r[2], r, r )
disk_R = gmsh.model.occ.addDisk( c_R[0], c_R[1], c_R[2], R, R )
ring = gmsh.model.occ.cut( [(2, disk_R)], [(2, disk_r)] )

gmsh.model.occ.synchronize()



surfaces = gmsh.model.occ.getEntities(dim=2)
assert surfaces == ring[0]
disk_subdomain_id = 0
gmsh.model.addPhysicalGroup( surfaces[0][0], [surfaces[0][1]], disk_subdomain_id )
gmsh.model.setPhysicalName( surfaces[0][0], disk_subdomain_id, "disk" )

lines = gmsh.model.occ.getEntities(dim=1)
#these are the subdomain_ids with which the components will be read in read_2dmesh_ring.py
circle_r_subdomain_id = 1
circle_R_subdomain_id = 2
gmsh.model.addPhysicalGroup( lines[0][0], [lines[0][1]], circle_r_subdomain_id )
gmsh.model.setPhysicalName( lines[0][0], disk_subdomain_id, "circle_r" )
gmsh.model.addPhysicalGroup( lines[1][0], [lines[1][1]], circle_R_subdomain_id )
gmsh.model.setPhysicalName( lines[1][0], disk_subdomain_id, "circle_R" )




#set the resolution
distance = gmsh.model.mesh.field.add("Distance")
gmsh.model.mesh.field.setNumbers(distance, "FacesList", [surfaces[0][0]])

threshold = gmsh.model.mesh.field.add("Threshold")
gmsh.model.mesh.field.setNumber(threshold, "IField", distance)
gmsh.model.mesh.field.setNumber(threshold, "LcMin", resolution)
gmsh.model.mesh.field.setNumber(threshold, "LcMax", resolution)
gmsh.model.mesh.field.setNumber(threshold, "DistMin", 0.5 * r)
gmsh.model.mesh.field.setNumber(threshold, "DistMax", r)

circle_r_dist = gmsh.model.mesh.field.add("Distance")
circle_r_threshold = gmsh.model.mesh.field.add( "Threshold" )

gmsh.model.mesh.field.setNumber( circle_r_threshold, "IField", circle_r_dist )
gmsh.model.mesh.field.setNumber( circle_r_threshold, "LcMin", resolution )
gmsh.model.mesh.field.setNumber( circle_r_threshold, "LcMax", resolution )
gmsh.model.mesh.field.setNumber( circle_r_threshold, "DistMin", 0.1 )
gmsh.model.mesh.field.setNumber( circle_r_threshold, "DistMax", 0.5 )

minimum = gmsh.model.mesh.field.add("Min")
gmsh.model.mesh.field.setNumbers( minimum, "FieldsList", [threshold, circle_r_threshold] )
gmsh.model.mesh.field.setAsBackgroundMesh(minimum)


gmsh.model.occ.synchronize()
gmsh.model.mesh.generate(2)

gmsh.write("solution/mesh.msh")

which runs ok:

$ python3 generate_2dmesh_ring.py 0.1
Info    : Meshing 1D...                                                                                                                        
Info    : [  0%] Meshing curve 1 (Ellipse)
Info    : [ 60%] Meshing curve 2 (Ellipse)
Info    : Done meshing 1D (Wall 0.00238325s, CPU 0.002387s)
Info    : Meshing 2D...
Info    : Meshing surface 2 (Plane, Frontal-Delaunay)
Info    : Done meshing 2D (Wall 0.0338327s, CPU 0.033824s)
Info    : 1229 nodes 2460 elements
Info    : Writing 'solution/mesh.msh'...
Info    : Done writing 'solution/mesh.msh'

However, as you can see from the image attached, the mesh is not radially symmetric: there are some ‘impurities,’ which break the radial symmetry (in red)

How may I modify the code above so as to obtain a mesh radially symmetric ?

FYI, no internet search provided an answer to this. AI tools suggested a method based on transfinite Surfaces which did not work.

I also tried to define a perfectly symmetric disk by approximating as a polygon: a hexagon is perfectly symmetric, but polygons with larger number of sides, even if multiples of three, are not.

Thank you

This is a pure meshing question,and is better asked to either the GMSH developers (Issues · gmsh / gmsh · GitLab) or another meshing software (for instance netgen: Netgen - Netgen/NGSolve).

I managed to generate the mesh by adding multiple radial lines which, if close enough to each other, force the meshing algorithm to tessellate uniformly the ring, and gives a perfect radial symmetry:

import meshio
import argparse
import numpy as np
import gmsh
import warnings
from fenics import *
import sys

# add the path where to find the shared modules
module_path = '/home/fenics/shared/modules'
sys.path.append( module_path )

import input_output as io
import mesh as msh


parser = argparse.ArgumentParser()
parser.add_argument( "resolution" )
args = parser.parse_args()

msh_file_path = "solution/mesh.msh"

warnings.filterwarnings( "ignore" )
gmsh.initialize()

gmsh.model.add( "my model" )
c_r = [0, 0, 0]
c_R = [0, 0, 0]
r = 1
R = 2
N = 64
resolution = (float)( args.resolution )
print( f"Mesh resolution = {resolution}" )

delta_theta = 2 * np.pi / N

def Q(theta):
    return np.array( [[np.cos( theta ), -np.sin( theta )], [np.sin( theta ), np.cos( theta )]] )

x_r = np.array( [r+resolution, 0] )
x_R = np.array( [R-resolution, 0] )


disk_r = gmsh.model.occ.addDisk( c_r[0], c_r[1], c_r[2], r, r )
disk_R = gmsh.model.occ.addDisk( c_R[0], c_R[1], c_R[2], R, R )
# add this every time you add a component to the mesh and every time you make modifications to the mesh
gmsh.model.occ.synchronize()

ring = gmsh.model.occ.cut( [(2, disk_R)], [(2, disk_r)] )
gmsh.model.occ.synchronize()

print("Starting loop over circle ... ")
for i in range(N):

    Q_x_r = Q( i * delta_theta ).dot( x_r )
    Q_x_R = Q( i * delta_theta ).dot( x_R )

    p_r = gmsh.model.occ.addPoint( Q_x_r[0], Q_x_r[1], 0 )
    p_R = gmsh.model.occ.addPoint( Q_x_R[0], Q_x_R[1], 0 )
    gmsh.model.occ.synchronize()

    line_r_R = gmsh.model.occ.addLine( p_r, p_R )
    gmsh.model.occ.synchronize()

    gmsh.model.mesh.embed( 1, [line_r_R], 2, ring[0][0][0] )
    gmsh.model.occ.synchronize()
print("... done.")

# add 2-dimensional objects
surfaces = gmsh.model.occ.getEntities( dim=2 )
assert surfaces == ring[0]
disk_subdomain_id = 1

gmsh.model.addPhysicalGroup( surfaces[0][0], [surfaces[0][1]], disk_subdomain_id )
gmsh.model.setPhysicalName( surfaces[0][0], disk_subdomain_id, "disk" )

# add 1-dimensional objects
lines = gmsh.model.occ.getEntities( dim=1 )
circle_r_subdomain_id = 2
circle_R_subdomain_id = 3
# line_p_1_p_2_subdomain_id = 3

gmsh.model.addPhysicalGroup( lines[0][0], [lines[0][1]], circle_r_subdomain_id )
gmsh.model.setPhysicalName( lines[0][0], circle_r_subdomain_id, "circle_r" )
gmsh.model.addPhysicalGroup( lines[1][0], [lines[1][1]], circle_R_subdomain_id )
gmsh.model.setPhysicalName( lines[1][0], circle_R_subdomain_id, "circle_R" )

# add 0-dimensional objects
vertices = gmsh.model.occ.getEntities( dim=0 )

# set the resolution
distance = gmsh.model.mesh.field.add( "Distance" )
gmsh.model.mesh.field.setNumbers( distance, "FacesList", [surfaces[0][0]] )

threshold = gmsh.model.mesh.field.add( "Threshold" )
gmsh.model.mesh.field.setNumber( threshold, "IField", distance )
gmsh.model.mesh.field.setNumber( threshold, "LcMin", resolution )
gmsh.model.mesh.field.setNumber( threshold, "LcMax", resolution )
gmsh.model.mesh.field.setNumber( threshold, "DistMin", 0.5 * r )
gmsh.model.mesh.field.setNumber( threshold, "DistMax", r )

circle_r_dist = gmsh.model.mesh.field.add( "Distance" )
circle_r_threshold = gmsh.model.mesh.field.add( "Threshold" )

gmsh.model.mesh.field.setNumber( circle_r_threshold, "IField", circle_r_dist )
gmsh.model.mesh.field.setNumber( circle_r_threshold, "LcMin", resolution )
gmsh.model.mesh.field.setNumber( circle_r_threshold, "LcMax", resolution )
gmsh.model.mesh.field.setNumber( circle_r_threshold, "DistMin", 0.1 )
gmsh.model.mesh.field.setNumber( circle_r_threshold, "DistMax", 0.5 )

minimum = gmsh.model.mesh.field.add( "Min" )
gmsh.model.mesh.field.setNumbers( minimum, "FieldsList", [threshold, circle_r_threshold] )
gmsh.model.mesh.field.setAsBackgroundMesh( minimum )

gmsh.model.occ.synchronize()
gmsh.model.mesh.generate( 2 )
gmsh.write( msh_file_path )





mesh_from_file = meshio.read( msh_file_path )

# create a triangle mesh in which the surfaces will be stored
triangle_mesh = msh.create_mesh( mesh_from_file, "triangle", prune_z=True )
meshio.write( "solution/triangle_mesh.xdmf", triangle_mesh )

# create a line mesh
line_mesh = msh.create_mesh( mesh_from_file, "line", True )
meshio.write( "solution/line_mesh.xdmf", line_mesh )

# create a vertex mesh
'''
vertex_mesh = msh.create_mesh( mesh_from_file, "vertex", True )
meshio.write( "solution/vertex_mesh.xdmf", vertex_mesh )
print( f"Check if all line vertices are triangle vertices : {np.isin( msh.line_vertices( msh_file_path ), msh.triangle_vertices( msh_file_path ) )}" )
'''

#print the mesh vertices to file
mesh = Mesh()
msh.read_mesh(mesh, "solution/triangle_mesh.xdmf")
io.print_vertices_to_csv_file(mesh, "solution/vertices.csv" )

which runs

$ rm -r solution; mkdir solution; python3 generate_mesh.py 0.1
Mesh resolution = 0.1
Starting loop over circle ...                                                                                                                  
... done.
Info    : Meshing 1D...
Info    : [  0%] Meshing curve 1 (Ellipse)
Info    : [ 10%] Meshing curve 2 (Ellipse)
Info    : [ 10%] Meshing curve 3 (Line)
Info    : [ 10%] Meshing curve 4 (Line)
Info    : [ 10%] Meshing curve 5 (Line)
Info    : [ 10%] Meshing curve 6 (Line)
Info    : [ 10%] Meshing curve 7 (Line)
Info    : [ 20%] Meshing curve 8 (Line)
Info    : [ 20%] Meshing curve 9 (Line)
Info    : [ 20%] Meshing curve 10 (Line)
Info    : [ 20%] Meshing curve 11 (Line)
Info    : [ 20%] Meshing curve 12 (Line)
Info    : [ 20%] Meshing curve 13 (Line)
Info    : [ 20%] Meshing curve 14 (Line)
Info    : [ 30%] Meshing curve 15 (Line)
Info    : [ 30%] Meshing curve 16 (Line)
Info    : [ 30%] Meshing curve 17 (Line)
Info    : [ 30%] Meshing curve 18 (Line)
Info    : [ 30%] Meshing curve 19 (Line)
Info    : [ 30%] Meshing curve 20 (Line)
Info    : [ 40%] Meshing curve 21 (Line)
Info    : [ 40%] Meshing curve 22 (Line)
Info    : [ 40%] Meshing curve 23 (Line)
Info    : [ 40%] Meshing curve 24 (Line)
Info    : [ 40%] Meshing curve 25 (Line)
Info    : [ 40%] Meshing curve 26 (Line)
Info    : [ 40%] Meshing curve 27 (Line)
Info    : [ 50%] Meshing curve 28 (Line)
Info    : [ 50%] Meshing curve 29 (Line)
Info    : [ 50%] Meshing curve 30 (Line)
Info    : [ 50%] Meshing curve 31 (Line)
Info    : [ 50%] Meshing curve 32 (Line)
Info    : [ 50%] Meshing curve 33 (Line)
Info    : [ 60%] Meshing curve 34 (Line)
Info    : [ 60%] Meshing curve 35 (Line)
Info    : [ 60%] Meshing curve 36 (Line)
Info    : [ 60%] Meshing curve 37 (Line)
Info    : [ 60%] Meshing curve 38 (Line)
Info    : [ 60%] Meshing curve 39 (Line)
Info    : [ 60%] Meshing curve 40 (Line)
Info    : [ 70%] Meshing curve 41 (Line)
Info    : [ 70%] Meshing curve 42 (Line)
Info    : [ 70%] Meshing curve 43 (Line)
Info    : [ 70%] Meshing curve 44 (Line)
Info    : [ 70%] Meshing curve 45 (Line)
Info    : [ 70%] Meshing curve 46 (Line)
Info    : [ 70%] Meshing curve 47 (Line)
Info    : [ 80%] Meshing curve 48 (Line)
Info    : [ 80%] Meshing curve 49 (Line)
Info    : [ 80%] Meshing curve 50 (Line)
Info    : [ 80%] Meshing curve 51 (Line)
Info    : [ 80%] Meshing curve 52 (Line)
Info    : [ 80%] Meshing curve 53 (Line)
Info    : [ 90%] Meshing curve 54 (Line)
Info    : [ 90%] Meshing curve 55 (Line)
Info    : [ 90%] Meshing curve 56 (Line)
Info    : [ 90%] Meshing curve 57 (Line)
Info    : [ 90%] Meshing curve 58 (Line)
Info    : [ 90%] Meshing curve 59 (Line)
Info    : [ 90%] Meshing curve 60 (Line)
Info    : [100%] Meshing curve 61 (Line)
Info    : [100%] Meshing curve 62 (Line)
Info    : [100%] Meshing curve 63 (Line)
Info    : [100%] Meshing curve 64 (Line)
Info    : [100%] Meshing curve 65 (Line)
Info    : [100%] Meshing curve 66 (Line)
Info    : Done meshing 1D (Wall 0.008609s, CPU 0.007712s)
Info    : Meshing 2D...
Info    : Meshing surface 2 (Plane, Frontal-Delaunay)
Info    : Done meshing 2D (Wall 0.0775154s, CPU 0.077512s)
Info    : 1347 nodes 3336 elements
Info    : Writing 'solution/mesh.msh'...
Info    : Done writing 'solution/mesh.msh'

and yields a symmetric mesh, here is mesh.msh opened with Gmsh: