Geometry and Mesh Generation for a Rectangular Patch Antenna
In this example, we use Gmsh to create the geometry and finite element mesh for the probe-fed rectangular patch antenna from the IEICE electromagnetic simulation benchmark problems [1,2].
The antenna consists of a square metal patch on top of a dielectric substrate and a ground plane underneath. We also include a simplified feed and the surrounding air region so that the mesh can be used in an electromagnetic simulation. This notebook covers geometry and mesh generation only; it does not run an EM simulation.
Gmsh is not included in the Tanager package. Install it separately with
pip install gmsh. Gmsh is distributed under its own
license.
import gmsh, math
Model Dimensions and Frequency Range
The substrate measures 80 mm × 80 mm and is 1.53 mm thick. The square patch is 28.5 mm × 28.5 mm, with the feed offset 4.8 mm in the X direction from the patch center. All dimensions in the code are specified in meters (m).
The intended simulation frequency range is 2–3 GHz. The wavelength at 3 GHz is used to choose an upper limit for the mesh element size: higher frequencies have shorter wavelengths and generally require a finer mesh. The wavelength at 2 GHz is used to help determine how far the surrounding air region extends beyond the antenna. These frequencies are used only to set up the mesh; no frequency sweep is performed in this notebook.
freq_min = 2e9
freq_max = 3e9
ground_width = 80e-3
patch_width = 28.5e-3
thick = 1.53e-3
feed_center_x = 4.8e-3
feed_diameter = 1.25e-3
feed_width = 2*feed_diameter
radiation_radius = 0.5*ground_width+0.25*(3e8/freq_min)
max_edge_length = (3e8/freq_max)/5
Creating the Geometry
We use Gmsh’s OpenCASCADE geometry kernel to construct the model. The dielectric substrate is represented by a solid box, the conducting layers and feed by surfaces, and the surrounding air by a spherical volume. First, we initialize Gmsh and create a model.
gmsh.initialize()
gmsh.model.add("patchAntenna")
gmsh.option.setNumber("Geometry.OCCBooleanPreserveNumbering",1)
Dielectric Substrate, Radiating Patch, and Ground Plane
The square radiating patch lies on the top of the dielectric substrate, while the ground plane covers its underside. The substrate is a 3D volume, but the patch and ground plane are modeled as surfaces with no metal thickness. In the subsequent EM simulation, these surfaces can be assigned perfect electric conductor (PEC) boundary conditions.
substrate_tag = gmsh.model.occ.addBox(
-0.5*ground_width,-0.5*ground_width,0,
ground_width,ground_width,thick
)
patch_tag = gmsh.model.occ.addRectangle(
-0.5*patch_width,-0.5*patch_width,thick,
patch_width,patch_width
)
ground_tag = gmsh.model.occ.addRectangle(
-0.5*ground_width,-0.5*ground_width,0,
ground_width,ground_width
)
Feed Port Surface
In a physical probe-fed antenna, a conductor passes through the substrate to connect the feed to the patch. Here, rather than modeling the detailed probe and connector, we use a small vertical rectangular surface between the ground plane and the patch to represent a lumped port.
The code creates a rectangle in the XY plane, then rotates it by 90°
about the X axis so that it stands in the XZ plane. The parameter
feed_center_x determines its horizontal position, and thick sets its
height to match the substrate thickness.
port_tag = gmsh.model.occ.addRectangle(
feed_center_x-0.5*feed_width,0,0,
feed_width,thick
)
gmsh.model.occ.rotate(
[(2,port_tag)],
feed_center_x,0,0,
1,0,0,
math.pi/2
)
Merge Shared Interfaces
The substrate, metal surfaces, and feed surface are initially created as separate geometric objects. Without an additional operation, their intersections may not be represented by matching mesh entities. This matters because the feed surface must be recognized inside the substrate, and the metal surfaces need well-defined boundaries.
The fragment() operation splits intersecting geometry into entities
that share the same interfaces, allowing Gmsh to generate a conformal
mesh. It also makes it possible to identify the patch, ground plane, and
port surfaces for applying boundary conditions later.
Boolean operations can change Gmsh’s entity tags, so the code retrieves
the resulting tags from fragment() instead of relying on the original
ones.
_,entities = gmsh.model.occ.fragment(
[(3,substrate_tag)],
[(2,patch_tag),(2,ground_tag),(2,port_tag)]
)
substrate_entities,patch_entities,ground_entities,port_entities = entities
substrate_tag = [tag for dim,tag in substrate_entities if dim == 3]
patch_tag = [tag for dim,tag in patch_entities if dim == 2]
ground_tag = [tag for dim,tag in ground_entities if dim == 2]
port_tag = [tag for dim,tag in port_entities if dim == 2]
Surrounding Air Region
The antenna radiates electromagnetic waves into the space around the substrate. To include that space in a finite computational model, we create a sphere large enough to enclose the antenna and use the region inside it as the air domain.
The dielectric substrate must not occupy the same volume as air. We therefore subtract the substrate volume from the sphere, leaving separate, non-overlapping regions for the substrate and air. In the EM simulation, the sphere’s outer surface will be assigned an absorbing boundary condition (ABC) to reduce reflections from the artificial outer boundary.
sphere_tag = gmsh.model.occ.addSphere(
0,0,0.5*thick,radiation_radius,
angle1=-math.pi/2,
angle2=math.pi/2,
angle3=2*math.pi
)
radiation,_ = gmsh.model.occ.cut(
[(3,sphere_tag)],
[(3,tag) for tag in substrate_tag],
removeObject=True,
removeTool=False
)
radiation_tag = [tag for dim,tag in radiation if dim == 3]
# Identify the outer spherical surface for the absorbing boundary condition.
_,surface_loops = gmsh.model.occ.getSurfaceLoops(radiation_tag[0])
radiation_surfaces = {tag for loop in surface_loops for tag in loop}
absorbing_surfaces = [max(
radiation_surfaces,
key=lambda tag: gmsh.model.occ.getMass(2,tag)
)]
Identifying Materials and Boundary Conditions
An EM solver needs to know which mesh elements belong to the dielectric substrate or the surrounding air, and which surfaces represent the conductors, the absorbing boundary, and the feed port.
In Gmsh, we identify these regions using physical groups. Each group
is assigned a name and a numerical tag, which the solver can use after
importing the mesh. The volume groups Substrate and Radiation
identify the material regions, while the surface groups PEC, ABC,
and Lumped Port identify where boundary conditions will be applied.
This step only labels the mesh regions. Material properties and boundary conditions are assigned later in the EM simulation notebook.
gmsh.model.occ.synchronize()
dim3 = 3
dim2 = 2
substrate_domain = 1
radiation_domain = 2
pec_bc = 1
absorbing_bc = 2
port_bc = 3
gmsh.model.addPhysicalGroup(dim3,substrate_tag,substrate_domain,"Substrate")
gmsh.model.addPhysicalGroup(dim3,radiation_tag,radiation_domain,"Radiation")
gmsh.model.addPhysicalGroup(dim2,patch_tag+ground_tag,pec_bc,"PEC")
gmsh.model.addPhysicalGroup(dim2,absorbing_surfaces,absorbing_bc,"ABC")
gmsh.model.addPhysicalGroup(dim2,port_tag,port_bc,"Lumped Port")
3
Local Mesh Refinement Near the Patch Edges
Electric fields can vary rapidly near the edges of a radiating patch because of fringing fields. A coarse mesh may not resolve these variations well. We therefore use smaller elements near the four outer edges of the patch and allow larger elements farther away, where such fine detail is less important. This helps limit the total number of elements and the computational cost.
The code uses a Gmsh Distance field to measure the distance from the
patch perimeter, followed by a Threshold field to set the desired
element size based on that distance. The target size is 2 mm near the
patch edges and increases toward 20 mm away from them.
# Select the four outer patch edges, excluding any edges created by the feed.
patch_perimeter = []
patch_edges = gmsh.model.getBoundary(
[(2,tag) for tag in patch_tag],
combined=True,
oriented=False
)
for dim,tag in patch_edges:
if dim == 1:
x,y,_ = gmsh.model.occ.getCenterOfMass(1,tag)
if (abs(abs(x)-0.5*patch_width) < 1e-9 or
abs(abs(y)-0.5*patch_width) < 1e-9):
patch_perimeter.append(tag)
patch_perimeter = sorted(set(patch_perimeter))
if not patch_perimeter:
raise RuntimeError("Could not identify the patch perimeter curves")
# Set the target element size according to the distance from the patch edges.
# Use smaller elements near the perimeter and larger ones farther away.
distance_field = gmsh.model.mesh.field.add("Distance")
gmsh.model.mesh.field.setNumbers(
distance_field,"CurvesList",patch_perimeter
)
gmsh.model.mesh.field.setNumber(distance_field,"Sampling",200)
threshold_field = gmsh.model.mesh.field.add("Threshold")
gmsh.model.mesh.field.setNumber(threshold_field,"InField",distance_field)
gmsh.model.mesh.field.setNumber(threshold_field,"SizeMin",max_edge_length/10)
gmsh.model.mesh.field.setNumber(threshold_field,"SizeMax",max_edge_length)
gmsh.model.mesh.field.setNumber(threshold_field,"DistMin",0.0)
gmsh.model.mesh.field.setNumber(threshold_field,"DistMax",3*max_edge_length)
gmsh.model.mesh.field.setAsBackgroundMesh(threshold_field)
Generating and Saving the Mesh
Gmsh now divides the substrate and surrounding air into tetrahedral elements to create a three-dimensional mesh. We use quadratic (second-order) tetrahedral elements, which have nodes at both their vertices and edge midpoints. Compared with linear elements, they can represent curved boundaries—such as the spherical outer surface—more accurately.
The mesh is saved as patchAntenna.msh in Gmsh MSH 2.2 format, for
use in the subsequent finite element simulation. If you have a desktop
environment, you can also open Gmsh’s viewer to inspect the generated
geometry and mesh.
gmsh.option.setNumber("Mesh.CharacteristicLengthMax",max_edge_length)
gmsh.option.setNumber("Mesh.ElementOrder",2)
gmsh.option.setNumber("Mesh.HighOrderOptimize",1)
gmsh.model.mesh.generate(3)
gmsh.option.setNumber("Mesh.MshFileVersion",2.2)
gmsh.write("patchAntenna.msh")
# Optional: open the Gmsh viewer if a desktop environment is available.
# gmsh.fltk.run()
gmsh.finalize()
References
[1] IEICE Technical Committee on Electronics Simulation Technology (EST), “Rectangular Patch Antenna (Probe-Fed),” Electromagnetic Simulation Canonical Problems.
[2] Takuichi Hirano, Hiroshi Okabe, and Shinichiro Ohnuki, “Canonical Problems for Electromagnetic Simulators — Excitation Modeling with Connectors,” Microwave Workshops & Exhibition (MWE 2014), Workshop 07, December 11, 2014.