Monopole Antenna#
We are going to modify the code of the dipole antenna to create a monopole antenna. The monopole antenna is a half of the dipole antenna, and it is typically mounted above a ground plane.
import gmsh
import math
import os
from palacetoolkit.geometry import (xmin, xmax, ymin, ymax, zmin, zmax, extract_tag)
from palacetoolkit.viz import view_mesh
from palacetoolkit.mesh import (
Entity,
run_meshing_pipeline,
generate_3d_mesh,
refine_near_surfaces
)
Parameters:
filename - Output mesh filename
wavelength - Wavelength of the resulting electromagnetic wave
arm_length - Length of each antenna arm (default: wavelength/4)
arm_radius - Radius of the cylindrical antenna arms (default: arm_length/20)
base_length - Length of the ground plane (default: arm_radius * 4)
base_width - Width of the ground plane (default: arm_radius * 4)
gap_size - Size of the gap between the two arms (port region) (default: arm_length/100)
outer_boundary_radius - Radius of the outer spherical boundary (default: 1.5*wavelength)
verbose - Gmsh verbosity level (0-5, higher = more verbose)
gui - Whether to launch the Gmsh GUI after mesh generation
filename = "monopole.msh"
wavelength = 4.0
base_width = None
base_length = None
arm_length = None
arm_radius = None
gap_size = None
outer_boundary_radius = None
verbose = 5
gui = True
# Set default values
if arm_length is None:
arm_length = wavelength / 4
if arm_radius is None:
arm_radius = arm_length / 20
if gap_size is None:
gap_size = arm_length / 100
if outer_boundary_radius is None:
outer_boundary_radius = 1.5 * wavelength
if base_width is None:
base_width = arm_radius * 4
if base_length is None:
base_length = arm_radius * 4
Initialize gmsh
gmsh.initialize()
kernel = gmsh.model.occ
gmsh.option.setNumber("General.Verbosity", verbose)
# Create a new model. The name dipole is not important. If a model was already added,
# remove it first (this is useful when interactively evaluating this function).
if "monopole" in gmsh.model.list():
gmsh.model.setCurrent("monopole")
gmsh.model.remove()
gmsh.model.add("monopole")
# addSphere: The first three parameters are the coordinates of the center, and the last one is the radius.
outer_boundary = kernel.addSphere(0, 0, 0, outer_boundary_radius)
# addCylinder: The first three parameters are the coordinates of the center of the cylinder base,
# the second three defines its axis and the last one its radius.
arm = kernel.addCylinder(0, 0, gap_size / 2, 0, 0, arm_length, arm_radius)
# addRectangle: The first three parameters are the coordinates of the left corner of the rectangle,
# and the next two define the width and length of the rectangle.
base = kernel.addRectangle(-base_width/2, -base_length/2, -gap_size/2,
base_width, base_length)
# Create gap rectangle (port region) and rotate to XZ plane (we need to do this because
# OpenCASCADE does not have a primitive to create a rectangle directly on the XZ
# plane...).
gap_rectangle = kernel.addRectangle(-arm_radius, -gap_size/2, 0, 2*arm_radius, gap_size)
kernel.rotate([(2, gap_rectangle)], 0, 0, 0, 1, 0, 0, math.pi/2)
kernel.synchronize()
# Define the entities which later will generate the physical groups.
entities = [
Entity("arm", dim=3, mesh_order=1, tags=[arm]),
Entity("outer_boundary", dim=3, mesh_order=2, tags=[outer_boundary]),
Entity("base", dim=2, mesh_order=1, tags=[base]),
Entity("port", dim=2, mesh_order=0, tags=[gap_rectangle]),
]
# Boolean operations to guarantee a nice mesh, algo it returns the
# physical group map.
pg_map = run_meshing_pipeline(entities)
print(entities)
# Refine near the port
refine_near_surfaces(entities[-1].dimtags,
wavelength,
ppw_near=10,
ppw_far=10,
set_as_background=True,
local_refinements = {entities[0].dimtags[0]: 10})
# Mesh sizes
mesh_sizes = {}
generate_3d_mesh(entities, mesh_sizes, filename, optimize=False, verbose=False)
# Mesh visualization, rendering transparent the outer boundary.
view_mesh(filename, transparent_groups= "outer_boundary__None")