Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
82c1ffc
Initial commit for adding Electrical Current Dipole
laylagi Oct 17, 2025
760efcf
--docs: fix typo
laylagi Nov 17, 2025
fa819cf
Plot farfield-antenna example with short dipole
laylagi Nov 17, 2025
7fe01e6
fix tests
laylagi Nov 30, 2025
0f0f2d0
fix format
laylagi Nov 30, 2025
304d322
--fix comment
laylagi Dec 3, 2025
dfd1be6
wip-verification test
laylagi Dec 4, 2025
ad1f87f
wip - fix solver setup
laylagi Dec 11, 2025
34b8770
Fixed the test setup by manually applying 474db9a7673df963a84f77c8309…
laylagi Dec 11, 2025
4afc44c
Set up the exact solution and compute the relative error (failing ...)
laylagi Dec 16, 2025
2bf8e9d
revert changes in test-strattonchu.cpp and copy/past it to test-currr…
laylagi Dec 16, 2025
c342a26
wip - test-currentdipole: Use the exact solution in em.geosci.xyz
laylagi Dec 16, 2025
36c5511
something to consider (-J/J?)
laylagi Dec 16, 2025
0a98402
temp - debugging ...
laylagi Dec 16, 2025
754e8ed
fix format
laylagi Dec 16, 2025
cc64405
wip
laylagi Dec 18, 2025
077f9da
fix missing nondimensionalization of the moment vector
laylagi Dec 19, 2025
c6c77be
wip: fix the rhs
laylagi Dec 19, 2025
e9f199b
wip
laylagi Dec 22, 2025
e1886af
--typo
laylagi Dec 22, 2025
d317cfa
wip - Palace return Ey and Ez when they are supposed to be zero.
laylagi Dec 23, 2025
35ea1d8
WIP - Fixed multiple impl bugs
laylagi Dec 23, 2025
f8ce217
wip - fixing scaling issue
laylagi Dec 23, 2025
d9fb657
wip - the dipole is not on a vertex and the spurious/asymmetric resul…
laylagi Dec 30, 2025
976876f
Remove arbitrary 1e6 factor, add some comments, and reduce frequency
laylagi Dec 30, 2025
67ebd11
More debugging ...
laylagi Dec 31, 2025
49665ae
use a sphere instead of a cube
laylagi Dec 31, 2025
593acf9
Add viz
laylagi Jan 2, 2026
3d1d3e6
This transformation is not good per paraview. Need a good spherical m…
laylagi Jan 2, 2026
ed40597
Add a good spherical mesh
laylagi Jan 5, 2026
9e23511
Increase the polynomial order
laylagi Jan 5, 2026
f278692
Disable multigrid
laylagi Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/src/examples/antenna.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ aligned on the xz-plane and spans the diameter of the cylindrical conductors.
## Configuration File

The configuration file for the *Palace* simulation is found in
[`antenna.json`](https://github.com/awslabs/palace/blob/main/examples/antenna/antenna.json).
[`antenna_halfwave_dipole.json`](https://github.com/awslabs/palace/blob/main/examples/antenna/antenna_halfwave_dipole.json).
The simulation is performed in the frequency domain using the `"Driven"` solver
type, operating at a single frequency of ``0.0749\text{ GHz}``.

Expand Down Expand Up @@ -138,7 +138,7 @@ The `plot_farfield.jl` Julia script processes this file and produces plots polar
for the E- and H- planes (xz/xy-planes) and in the 3D.

```bash
julia --project plot_radiation_pattern.jl postpro/farfield-rE.csv
julia --project plot_farfield.jl postpro/farfield-rE.csv
```

The results for the polar plot are shown below.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
{
"Type": "Driven",
"Verbose": 2,
"Output": "postpro"
"Output": "postpro/antenna_halfwave_dipole"
}
,
"Model":
{
"Mesh": "mesh/antenna.msh",
"Mesh": "mesh/antenna_halfwave_dipole.msh",
"L0": 1.0 // 1 meter
},
"Domains":
Expand Down
66 changes: 66 additions & 0 deletions examples/antenna/antenna_short_dipole.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"Problem":
{
"Type": "Driven",
"Verbose": 2,
"Output": "postpro/antenna_short_dipole"
},
"Model":
{
"Mesh": "mesh/antenna_short_dipole.msh",
"L0": 1.0
},
"Domains":
{
"Materials":
[
{
"Attributes": [2]
}
],
"CurrentDipole": [
{
"Index": 1,
"Moment": [0.0, 0.0, 1],
"Center": [0.0, 0.0, 0.0]
}
]
},
"Boundaries":
{
"Absorbing":
{
"Attributes": [1]
},
"Postprocessing": {
"FarField": {
"Attributes": [1],
"NSample": 100,
"ThetaPhis": [[35, 20]]
}
}
},
"Solver":
{
"Order": 2,
"Device": "CPU",
"Driven":
{
"Samples":
[
{
"Type": "Point",
"Freq": [0.0749],
"SaveStep": 1
}
]
},
"Linear":
{
"Type": "Default",
"KSPType": "GMRES",
"Tol": 1.0e-10,
"MaxIts": 1000
}
}
}
Binary file added examples/antenna/mesh/antenna_short_dipole.msh
Binary file not shown.
224 changes: 224 additions & 0 deletions examples/antenna/mesh/mesh_antenna_short_dipole.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

#=
# README

This Julia script uses Gmsh to create a mesh for a dipole antenna enclosed within a
spherical boundary. A dipole antenna consists of two equal cylinders (the arms) separated by
a thin gap. In the thin gap, there is a flat rectangle that connects the cylinder and that
can is used as a lumped port.

The generated mesh contains two regions:
1. A 3D volume region (the space inside the sphere)
2. A large outer spherical boundary (typically set to "absorbing" boundary conditions)

## Prerequisites

This script requires the Gmsh Julia package. If you don't already have it installed, you can
install it with

```bash
julia -e 'using Pkg; Pkg.add("Gmsh")'
```

## How to run

From this directory, run:
```bash
julia -e 'include("mesh.jl"); generate_antenna_mesh(; filename="antenna.msh")'
```
To visualize the mesh in Gmsh's graphical interface, add the `gui=true` parameter:
```bash
julia -e 'include("mesh.jl"); generate_antenna_mesh(; filename="antenna.msh", gui=true)'
```

The script will generate a mesh file and print the "attribute" numbers for each region.
These attributes are needed when configuring Palace simulations.
=#

using Gmsh: gmsh

"""
extract_tag(object)

Extract the Gmsh tag in `object`.

If `object` contains only one tag, return it as an integer, otherwise, preserve its
container.

Most gmsh functions return list of tuples like `[(2, 5), (2, 8), (2, 10), ...]`, where the
first number is dimensionality and the second is the integer tag associated to that object.

#### Example

```jldoctest
julia> extract_tag((3, 6))
6

julia> entities = [(2, 5), (2, 8), (2, 10)];

julia> extract_tag.(entities)
[5, 8, 10]
```
"""
extract_tag(object) = extract_tag(only(object))
extract_tag(object::Tuple) = last(object)
extract_tag(object::Integer) = error("You passed an integer tag directly to `extract_tag`")

# Convenience functions to extract extrema of the bounding box of an entity
xmin(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[1]
ymin(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[2]
zmin(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[3]
xmax(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[4]
ymax(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[5]
zmax(x::Tuple) = gmsh.model.occ.get_bounding_box(x...)[6]

"""
generate_antenna_mesh(;
filename::AbstractString,
wavelength::Real=4.0,
arm_length::Real=wavelength/4,
arm_radius::Real=arm_length/20,
gap_size::Real=arm_length/100,
outer_boundary_radius::Real=1.5wavelength,
verbose::Integer=5,
gui::Bool=false
)

Generate a mesh for a dipole antenna using Gmsh.

# Arguments

- filename - output mesh filename
- wavelength - wavelength of the resulting electromagnetic wave
- arm_length - length of each antenna arm
- arm_radius - radius of the cylindrical antenna arms
- gap_size - size of the gap between the two arms (port region)
- outer_boundary_radius - radius of the outer spherical boundary
- verbose - gmsh verbosity level (0-5, higher = more verbose)
- gui - whether to launch the Gmsh GUI after mesh generation
"""
function generate_antenna_mesh(;
filename::AbstractString,
wavelength::Real=4.0,
arm_length::Real=wavelength/4,
arm_radius::Real=arm_length/20,
gap_size::Real=arm_length/100,
outer_boundary_radius::Real=1.5wavelength,
verbose::Integer=5,
gui::Bool=false
)
# We will create this mesh with a simple approach. We create the 3D
# sphere only, which produces:
# - 1 3D entity (the domain)
# - 1 2D entity (the outer boundary)
#
# After creating, we add them to the correct gmsh physical groups. Finally, we
# control mesh size with a mesh size field and generate the mesh.

# Boilerplate
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 the body of this
# function in the REPL).
if "dipole" in gmsh.model.list()
gmsh.model.setCurrent("dipole")
gmsh.model.remove()
end
gmsh.model.add("dipole")

# Mesh refinement parameter: controls elements around cylinder circumference.
# Higher number = higher resolution.
n_circle = 12
# How many elements per wavelength on the outer sphere.
# Higher number = higher resolution.
n_farfield = 3

# Create geometry
outer_boundary = kernel.addSphere(0, 0, 0, outer_boundary_radius)

# Synchronize CAD operations with Gmsh model.
kernel.synchronize()

# Helper functions to identify the various components.
all_2d_entities = kernel.getEntities(2)
all_3d_entities = kernel.getEntities(3)

# For a simple sphere, there should be exactly one 2D and one 3D entity.
outer_sphere_dimtags = all_2d_entities
domain_dimtags = all_3d_entities

# Verify we found the expected number of entities.
@assert length(outer_sphere_dimtags) == 1 # Single outer boundary
@assert length(domain_dimtags) == 1 # Single 3D domain

# Create physical groups (these become attributes in Palace).
outer_boundary_group = gmsh.model.addPhysicalGroup(
2,
extract_tag.(outer_sphere_dimtags),
-1,
"outer_boundary"
)
domain_group =
gmsh.model.addPhysicalGroup(3, extract_tag.(domain_dimtags), -1, "domain")

# Set mesh size parameters.
gmsh.option.setNumber("Mesh.MeshSizeMin", 2.0 * pi * arm_radius / n_circle / 2.0)
gmsh.option.setNumber("Mesh.MeshSizeMax", wavelength / n_farfield)
# Set minimum number of elements per 2π radians of curvature.
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", n_circle)
# Don't extend mesh size constraints from boundaries into the volume.
# This option is typically activated when working with mesh size fields.
gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)

# Finally, we control mesh size using a mesh size field.

# Create a simple distance field from the center for mesh sizing.
gmsh.model.mesh.field.add("Distance", 1)
gmsh.model.mesh.field.setNumbers(1, "PointsList", []) # No specific points

# Use a Box field to control mesh size - finer at center, coarser toward boundary
gmsh.model.mesh.field.add("Box", 2)
gmsh.model.mesh.field.setNumber(2, "VIn", wavelength / n_farfield / 2) # Fine mesh at center
gmsh.model.mesh.field.setNumber(2, "VOut", wavelength / n_farfield) # Coarser toward boundary
gmsh.model.mesh.field.setNumber(2, "XMin", -outer_boundary_radius/4)
gmsh.model.mesh.field.setNumber(2, "XMax", outer_boundary_radius/4)
gmsh.model.mesh.field.setNumber(2, "YMin", -outer_boundary_radius/4)
gmsh.model.mesh.field.setNumber(2, "YMax", outer_boundary_radius/4)
gmsh.model.mesh.field.setNumber(2, "ZMin", -outer_boundary_radius/4)
gmsh.model.mesh.field.setNumber(2, "ZMax", outer_boundary_radius/4)

# Use this Box field to determine element sizes.
gmsh.model.mesh.field.setAsBackgroundMesh(2)

# Set 2D/3D meshing algorithm. Chosen to be deterministic, not necessarily the
# best.
gmsh.option.setNumber("Mesh.Algorithm3D", 1)
gmsh.option.setNumber("Mesh.Algorithm", 6)

# Generate 3D volume mesh and set to 3rd order elements.
gmsh.model.mesh.generate(3)
gmsh.model.mesh.setOrder(3)

# Set output format for Palace compatibility.
gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
gmsh.option.setNumber("Mesh.Binary", 1)
gmsh.write(joinpath(@__DIR__, filename))

println("\nFinished generating mesh. Physical group tags:")
println("Farfield boundary (2D): ", outer_boundary_group)
println("Domain (3D): ", domain_group)
println()

# Optionally launch the Gmsh GUI.
if gui
gmsh.fltk.run()
end

# Clean up Gmsh resources.
return gmsh.finalize()
end
Loading
Loading