Skip to content

Commit 2f4b96e

Browse files
committed
Plot farfield-antenna example with short dipole
1 parent d3a1277 commit 2f4b96e

File tree

8 files changed

+687
-25
lines changed

8 files changed

+687
-25
lines changed

examples/antenna/antenna.json renamed to examples/antenna/antenna_halfwave_dipole.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
{
44
"Type": "Driven",
55
"Verbose": 2,
6-
"Output": "postpro"
6+
"Output": "postpro/antenna_halfwave_dipole"
77
}
88
,
99
"Model":
1010
{
11-
"Mesh": "mesh/antenna.msh",
11+
"Mesh": "mesh/antenna_halfwave_dipole.msh",
1212
"L0": 1.0 // 1 meter
1313
},
1414
"Domains":
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"Problem":
3+
{
4+
"Type": "Driven",
5+
"Verbose": 2,
6+
"Output": "postpro/antenna_short_dipole"
7+
},
8+
"Model":
9+
{
10+
"Mesh": "mesh/antenna_short_dipole.msh",
11+
"L0": 1.0
12+
},
13+
"Domains":
14+
{
15+
"Materials":
16+
[
17+
{
18+
"Attributes": [2]
19+
}
20+
],
21+
"CurrentDipole": [
22+
{
23+
"Index": 1,
24+
"Moment": [0.0, 0.0, 1],
25+
"Center": [0.0, 0.0, 0.0]
26+
}
27+
]
28+
},
29+
"Boundaries":
30+
{
31+
"Absorbing":
32+
{
33+
"Attributes": [1]
34+
},
35+
"Postprocessing": {
36+
"FarField": {
37+
"Attributes": [1],
38+
"NSample": 100,
39+
"ThetaPhis": [[35, 20]]
40+
}
41+
}
42+
},
43+
"Solver":
44+
{
45+
"Order": 2,
46+
"Device": "CPU",
47+
"Driven":
48+
{
49+
"Samples":
50+
[
51+
{
52+
"Type": "Point",
53+
"Freq": [0.0749],
54+
"SaveStep": 1
55+
}
56+
]
57+
},
58+
"Linear":
59+
{
60+
"Type": "Default",
61+
"KSPType": "GMRES",
62+
"Tol": 1.0e-10,
63+
"MaxIts": 1000
64+
}
65+
}
66+
}
18.1 MB
Binary file not shown.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
#=
5+
# README
6+
7+
This Julia script uses Gmsh to create a mesh for a dipole antenna enclosed within a
8+
spherical boundary. A dipole antenna consists of two equal cylinders (the arms) separated by
9+
a thin gap. In the thin gap, there is a flat rectangle that connects the cylinder and that
10+
can is used as a lumped port.
11+
12+
The generated mesh contains two regions:
13+
1. A 3D volume region (the space inside the sphere)
14+
2. A large outer spherical boundary (typically set to "absorbing" boundary conditions)
15+
16+
## Prerequisites
17+
18+
This script requires the Gmsh Julia package. If you don't already have it installed, you can
19+
install it with
20+
21+
```bash
22+
julia -e 'using Pkg; Pkg.add("Gmsh")'
23+
```
24+
25+
## How to run
26+
27+
From this directory, run:
28+
```bash
29+
julia -e 'include("mesh.jl"); generate_antenna_mesh(; filename="antenna.msh")'
30+
```
31+
To visualize the mesh in Gmsh's graphical interface, add the `gui=true` parameter:
32+
```bash
33+
julia -e 'include("mesh.jl"); generate_antenna_mesh(; filename="antenna.msh", gui=true)'
34+
```
35+
36+
The script will generate a mesh file and print the "attribute" numbers for each region.
37+
These attributes are needed when configuring Palace simulations.
38+
=#
39+
40+
using Gmsh: gmsh
41+
42+
"""
43+
extract_tag(object)
44+
45+
Extract the Gmsh tag in `object`.
46+
47+
If `object` contains only one tag, return it as an integer, otherwise, preserve its
48+
container.
49+
50+
Most gmsh functions return list of tuples like `[(2, 5), (2, 8), (2, 10), ...]`, where the
51+
first number is dimensionality and the second is the integer tag associated to that object.
52+
53+
#### Example
54+
55+
```jldoctest
56+
julia> extract_tag((3, 6))
57+
6
58+
59+
julia> entities = [(2, 5), (2, 8), (2, 10)];
60+
61+
julia> extract_tag.(entities)
62+
[5, 8, 10]
63+
```
64+
"""
65+
extract_tag(object) = extract_tag(only(object))
66+
extract_tag(object::Tuple) = last(object)
67+
extract_tag(object::Integer) = error("You passed an integer tag directly to `extract_tag`")
68+
69+
# Convenience functions to extract extrema of the bounding box of an entity
70+
xmin(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[1]
71+
ymin(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[2]
72+
zmin(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[3]
73+
xmax(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[4]
74+
ymax(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[5]
75+
zmax(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[6]
76+
77+
"""
78+
generate_antenna_mesh(;
79+
filename::AbstractString,
80+
wavelength::Real=4.0,
81+
arm_length::Real=wavelength/4,
82+
arm_radius::Real=arm_length/20,
83+
gap_size::Real=arm_length/100,
84+
outer_boundary_radius::Real=1.5wavelength,
85+
verbose::Integer=5,
86+
gui::Bool=false
87+
)
88+
89+
Generate a mesh for a dipole antenna using Gmsh.
90+
91+
# Arguments
92+
93+
- filename - output mesh filename
94+
- wavelength - wavelength of the resulting electromagnetic wave
95+
- arm_length - length of each antenna arm
96+
- arm_radius - radius of the cylindrical antenna arms
97+
- gap_size - size of the gap between the two arms (port region)
98+
- outer_boundary_radius - radius of the outer spherical boundary
99+
- verbose - gmsh verbosity level (0-5, higher = more verbose)
100+
- gui - whether to launch the Gmsh GUI after mesh generation
101+
"""
102+
function generate_antenna_mesh(;
103+
filename::AbstractString,
104+
wavelength::Real=4.0,
105+
arm_length::Real=wavelength/4,
106+
arm_radius::Real=arm_length/20,
107+
gap_size::Real=arm_length/100,
108+
outer_boundary_radius::Real=1.5wavelength,
109+
verbose::Integer=5,
110+
gui::Bool=false
111+
)
112+
# We will create this mesh with a simple approach. We create the 3D
113+
# sphere only, which produces:
114+
# - 1 3D entity (the domain)
115+
# - 1 2D entity (the outer boundary)
116+
#
117+
# After creating, we add them to the correct gmsh physical groups. Finally, we
118+
# control mesh size with a mesh size field and generate the mesh.
119+
120+
# Boilerplate
121+
gmsh.initialize()
122+
kernel = gmsh.model.occ
123+
gmsh.option.setNumber("General.Verbosity", verbose)
124+
125+
# Create a new model. The name dipole is not important. If a model was already added,
126+
# remove it first (this is useful when interactively evaluating the body of this
127+
# function in the REPL).
128+
if "dipole" in gmsh.model.list()
129+
gmsh.model.setCurrent("dipole")
130+
gmsh.model.remove()
131+
end
132+
gmsh.model.add("dipole")
133+
134+
# Mesh refinement parameter: controls elements around cylinder circumference.
135+
# Higher number = higher resolution.
136+
n_circle = 12
137+
# How many elements per wavelength on the outer sphere.
138+
# Higher number = higher resolution.
139+
n_farfield = 3
140+
141+
# Create geometry
142+
outer_boundary = kernel.addSphere(0, 0, 0, outer_boundary_radius)
143+
144+
# Synchronize CAD operations with Gmsh model.
145+
kernel.synchronize()
146+
147+
# Helper functions to identify the various components.
148+
all_2d_entities = kernel.getEntities(2)
149+
all_3d_entities = kernel.getEntities(3)
150+
151+
# For a simple sphere, there should be exactly one 2D and one 3D entity.
152+
outer_sphere_dimtags = all_2d_entities
153+
domain_dimtags = all_3d_entities
154+
155+
# Verify we found the expected number of entities.
156+
@assert length(outer_sphere_dimtags) == 1 # Single outer boundary
157+
@assert length(domain_dimtags) == 1 # Single 3D domain
158+
159+
# Create physical groups (these become attributes in Palace).
160+
outer_boundary_group = gmsh.model.addPhysicalGroup(
161+
2,
162+
extract_tag.(outer_sphere_dimtags),
163+
-1,
164+
"outer_boundary"
165+
)
166+
domain_group =
167+
gmsh.model.addPhysicalGroup(3, extract_tag.(domain_dimtags), -1, "domain")
168+
169+
# Set mesh size parameters.
170+
gmsh.option.setNumber("Mesh.MeshSizeMin", 2.0 * pi * arm_radius / n_circle / 2.0)
171+
gmsh.option.setNumber("Mesh.MeshSizeMax", wavelength / n_farfield)
172+
# Set minimum number of elements per 2π radians of curvature.
173+
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", n_circle)
174+
# Don't extend mesh size constraints from boundaries into the volume.
175+
# This option is typically activated when working with mesh size fields.
176+
gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)
177+
178+
# Finally, we control mesh size using a mesh size field.
179+
180+
# Create a simple distance field from the center for mesh sizing.
181+
gmsh.model.mesh.field.add("Distance", 1)
182+
gmsh.model.mesh.field.setNumbers(1, "PointsList", []) # No specific points
183+
184+
# Use a Box field to control mesh size - finer at center, coarser toward boundary
185+
gmsh.model.mesh.field.add("Box", 2)
186+
gmsh.model.mesh.field.setNumber(2, "VIn", wavelength / n_farfield / 2) # Fine mesh at center
187+
gmsh.model.mesh.field.setNumber(2, "VOut", wavelength / n_farfield) # Coarser toward boundary
188+
gmsh.model.mesh.field.setNumber(2, "XMin", -outer_boundary_radius/4)
189+
gmsh.model.mesh.field.setNumber(2, "XMax", outer_boundary_radius/4)
190+
gmsh.model.mesh.field.setNumber(2, "YMin", -outer_boundary_radius/4)
191+
gmsh.model.mesh.field.setNumber(2, "YMax", outer_boundary_radius/4)
192+
gmsh.model.mesh.field.setNumber(2, "ZMin", -outer_boundary_radius/4)
193+
gmsh.model.mesh.field.setNumber(2, "ZMax", outer_boundary_radius/4)
194+
195+
# Use this Box field to determine element sizes.
196+
gmsh.model.mesh.field.setAsBackgroundMesh(2)
197+
198+
# Set 2D/3D meshing algorithm. Chosen to be deterministic, not necessarily the
199+
# best.
200+
gmsh.option.setNumber("Mesh.Algorithm3D", 1)
201+
gmsh.option.setNumber("Mesh.Algorithm", 6)
202+
203+
# Generate 3D volume mesh and set to 3rd order elements.
204+
gmsh.model.mesh.generate(3)
205+
gmsh.model.mesh.setOrder(3)
206+
207+
# Set output format for Palace compatibility.
208+
gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
209+
gmsh.option.setNumber("Mesh.Binary", 1)
210+
gmsh.write(joinpath(@__DIR__, filename))
211+
212+
println("\nFinished generating mesh. Physical group tags:")
213+
println("Farfield boundary (2D): ", outer_boundary_group)
214+
println("Domain (3D): ", domain_group)
215+
println()
216+
217+
# Optionally launch the Gmsh GUI.
218+
if gui
219+
gmsh.fltk.run()
220+
end
221+
222+
# Clean up Gmsh resources.
223+
return gmsh.finalize()
224+
end

0 commit comments

Comments
 (0)