Waveguide box#
Simple elongated box waveguide mesh with two waveports.
Generates a rectangular waveguide (elongated box) where:
The two end faces are labeled “waveport_1” and “waveport_2”
The four lateral surfaces are labeled “metal”
Our goal is to measure the s parameters.
import gmsh
import math
import os
import sys
from pathlib import Path
from palacetoolkit.viz import view_mesh
from palacetoolkit.geometry import extract_tag, xmin, xmax, ymin, ymax, zmin, zmax
from palacetoolkit.mesh import refine_near_surfaces
from palacetoolkit.simulation import Simulation, run_palace
Parameters#
filename : Output mesh filename
width : width of the waveguide in meters
height : height of the waveguide in meters
length : length of the waveguide in meters
filename="waveguide_box.msh"
# Default dimensions of the waveguide box (wr-90 standard)
width=22.86e-3
height=10.16e-3
length=100e-3
Initialization of the model#
gmsh.initialize()
gmsh.model.add("waveguide_box")
kernel = gmsh.model.occ
Geometry definition#
# Create an elongated box centered at the origin
# addBox(x, y, z, dx, dy, dz) — (x,y,z) is the corner
box = kernel.addBox(
-width / 2, -height / 2, 0,
width, height, length,
)
kernel.synchronize()
Identifying Geometric Entities and Domains#
# Identify surfaces by their bounding box positions
all_surfaces = gmsh.model.getEntities(2)
waveport_1_tags = [] # face at z = 0
waveport_2_tags = [] # face at z = length
metal_tags = [] # the four lateral faces
eps = 1e-6
for dimtag in all_surfaces:
if abs(zmin(dimtag) - 0.0) < eps and abs(zmax(dimtag) - 0.0) < eps:
waveport_1_tags.append(dimtag)
elif abs(zmin(dimtag) - length) < eps and abs(zmax(dimtag) - length) < eps:
waveport_2_tags.append(dimtag)
else:
metal_tags.append(dimtag)
assert len(waveport_1_tags) == 1, f"Expected 1 waveport_1 face, found {len(waveport_1_tags)}"
assert len(waveport_2_tags) == 1, f"Expected 1 waveport_2 face, found {len(waveport_2_tags)}"
assert len(metal_tags) == 4, f"Expected 4 metal faces, found {len(metal_tags)}"
Defining Physical Groups#
To prepare the geometry for export to Palace, we must define “Physical Groups.” These groups serve as labels that the solver uses to identify boundaries (surfaces) and domains (volumes) for applying physics settings, such as excitation ports or material properties.
# Physical groups (these become boundary attributes in Palace).
# Use deterministic IDs so generated configs are stable and match the reference JSON.
pg_waveport_1 = gmsh.model.addPhysicalGroup(2, [x[1] for x in waveport_1_tags], 1, name="waveport_1")
pg_waveport_2 = gmsh.model.addPhysicalGroup(2, [x[1] for x in waveport_2_tags], 2, name="waveport_2")
pg_metal = gmsh.model.addPhysicalGroup(2, [x[1] for x in metal_tags], 3, name="metal")
# Volume physical group
all_volumes = gmsh.model.getEntities(3)
vol_tags = [tag for _, tag in all_volumes]
pg_volume = gmsh.model.addPhysicalGroup(3, vol_tags, 4, name="waveguide_volume")
# Map physical group names to their tags for later use.
pg_map = {
"waveport_1": pg_waveport_1,
"waveport_2": pg_waveport_2,
"metal": pg_metal,
"volume": pg_volume
}
assert pg_map == {"waveport_1": 1, "waveport_2": 2, "metal": 3, "volume": 4}
Mesh Generation and Export#
Next, we define the mesh resolution based on our operating frequency (10 GHz) and generate the final mesh for the Palace solver.
Refinement: We ensure higher mesh density near the waveports to capture electromagnetic behavior accurately.
Settings: We use the Frontal-Delaunay algorithm and set the mesh to first-order elements.
Export: The mesh is saved in the Gmsh 2.2 ASCII format, which is required for compatibility with Palace.
def _generate_waveguide_box_mesh():
freq = 10e9 # 10 GHz
c = 3e8 # speed of light in vacuum
wavelength = c / freq
refine_near_surfaces(waveport_1_tags + waveport_2_tags, ppw_far=10, ppw_near=20, wavelength=wavelength)
# Meshing
gmsh.option.setNumber("Mesh.Algorithm", 6) # Frontal-Delaunay for 2D
gmsh.option.setNumber("Mesh.Algorithm3D", 2) # Frontal-Delaunay for 3D
gmsh.model.mesh.generate(3)
gmsh.model.mesh.setOrder(1)
# Output in Gmsh 2.2 ASCII format (Palace compatible)
gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
gmsh.option.setNumber("Mesh.Binary", 0)
script_dir = os.getcwd()
output_path = os.path.join(script_dir, filename)
gmsh.write(output_path)
_generate_waveguide_box_mesh()
gmsh.finalize()
view_mesh(filename)
ppw_near=20 ppw_far=10
SizeMax=0.0030 transition=0.0075
global: 8 curves, SizeMin=0.0015
Info : Meshing 1D...
Info : [ 0%] Meshing curve 1 (Line)
Info : [ 10%] Meshing curve 2 (Line)
Info : [ 20%] Meshing curve 3 (Line)
Info : [ 30%] Meshing curve 4 (Line)
Info : [ 40%] Meshing curve 5 (Line)
Info : [ 50%] Meshing curve 6 (Line)
Info : [ 60%] Meshing curve 7 (Line)
Info : [ 60%] Meshing curve 8 (Line)
Info : [ 70%] Meshing curve 9 (Line)
Info : [ 80%] Meshing curve 10 (Line)
Info : [ 90%] Meshing curve 11 (Line)
Info : [100%] Meshing curve 12 (Line)
Info : Done meshing 1D (Wall 0.0374193s, CPU 0.037833s)
Info : Meshing 2D...
Info : [ 0%] Meshing surface 1 (Plane, Frontal-Delaunay)
Info : [ 20%] Meshing surface 2 (Plane, Frontal-Delaunay)
Info : [ 40%] Meshing surface 3 (Plane, Frontal-Delaunay)
Info : [ 60%] Meshing surface 4 (Plane, Frontal-Delaunay)
Info : [ 70%] Meshing surface 5 (Plane, Frontal-Delaunay)
Info : [ 90%] Meshing surface 6 (Plane, Frontal-Delaunay)
Info : Done meshing 2D (Wall 0.0184283s, CPU 0.018458s)
Info : Meshing 3D...
Info : 3D Meshing 1 volume with 1 connected component
Info : Tetrahedrizing 1266 nodes...
Info : Done tetrahedrizing 1274 nodes (Wall 0.00834981s, CPU 0.007382s)
Info : Reconstructing mesh...
Info : - Creating surface mesh
Info : - Identifying boundary edges
Info : - Recovering boundary
Info : Done reconstructing mesh (Wall 0.0181882s, CPU 0.013253s)
Info : Found volume 1
Info : It. 0 - 0 nodes created - worst tet radius 2.00763 (nodes removed 0 0)
Info : 3D refinement terminated (1649 nodes total):
Info : - 0 Delaunay cavities modified for star shapeness
Info : - 0 nodes could not be inserted
Info : - 6009 tetrahedra created in 0.010736 sec. (559703 tets/s)
Info : 0 node relocations
Info : Done meshing 3D (Wall 0.0399752s, CPU 0.033199s)
Info : Optimizing mesh...
Info : Optimizing volume 1
Info : Optimization starts (volume = 2.32258e-05) with worst = 0.0117629 / average = 0.761057:
Info : 0.00 < quality < 0.10 : 31 elements
Info : 0.10 < quality < 0.20 : 48 elements
Info : 0.20 < quality < 0.30 : 84 elements
Info : 0.30 < quality < 0.40 : 82 elements
Info : 0.40 < quality < 0.50 : 132 elements
Info : 0.50 < quality < 0.60 : 293 elements
Info : 0.60 < quality < 0.70 : 918 elements
Info : 0.70 < quality < 0.80 : 1658 elements
Info : 0.80 < quality < 0.90 : 1726 elements
Info : 0.90 < quality < 1.00 : 1037 elements
Info : 159 edge swaps, 2 node relocations (volume = 2.32258e-05): worst = 0.248964 / average = 0.777845 (Wall 0.00127162s, CPU 0.001314s)
Info : 161 edge swaps, 2 node relocations (volume = 2.32258e-05): worst = 0.300339 / average = 0.778112 (Wall 0.00153182s, CPU 0.00159s)
Info : No ill-shaped tets in the mesh :-)
Info : 0.00 < quality < 0.10 : 0 elements
Info : 0.10 < quality < 0.20 : 0 elements
Info : 0.20 < quality < 0.30 : 0 elements
Info : 0.30 < quality < 0.40 : 82 elements
Info : 0.40 < quality < 0.50 : 125 elements
Info : 0.50 < quality < 0.60 : 293 elements
Info : 0.60 < quality < 0.70 : 916 elements
Info : 0.70 < quality < 0.80 : 1643 elements
Info : 0.80 < quality < 0.90 : 1763 elements
Info : 0.90 < quality < 1.00 : 1041 elements
Info : Done optimizing mesh (Wall 0.00429291s, CPU 0.00431s)
Info : 1649 nodes 8635 elements
Info : Writing '/home/martin/Desktop/PalaceToolkit/docs/examples/waveguide_box.msh'...
Info : Done writing '/home/martin/Desktop/PalaceToolkit/docs/examples/waveguide_box.msh'
Loading mesh file: waveguide_box.msh
Groups to render transparent: ['air_none', 'air_plastic_enclosure']
Mesh loaded successfully with 2 cell blocks
Found 2528 triangles total
Physical group tags in mesh: {1: 'waveport_1', 2: 'waveport_2', 3: 'metal'}
Write a Palace JSON config for a simple hollow waveguide.#
output_file: destination JSON path.
freq_min: start frequency [GHz].
freq_max: end frequency [GHz].
freq_step: frequency step [GHz].
output_file: str = "waveguide_box.json"
freq_min: float = 6.0
freq_max: float = 15.0
freq_step: float = 0.5
if os.environ.get("DOCS_BUILD") == "1":
# Keep docs notebook execution fast while still attempting a real Palace solve.
freq_min = 10.0
freq_max = 10.0
freq_step = 1.0
output_stem = Path(output_file).stem
sim = Simulation(output_dir=os.getcwd(), apply_mesh_options=False)
sim.config = {
"Problem": {
"Type": "Driven",
"Verbose": 2,
"Output": f"postpro/{output_stem}",
},
"Model": {
"Mesh": filename,
"L0": 1.0,
"Refinement": {},
},
"Domains": {
"Materials": [
{
"Attributes": [pg_map["volume"]],
"Permeability": 1.0,
"Permittivity": 1.0,
"LossTan": 0.0,
}
],
},
"Boundaries": {
"PEC": {
"Attributes": [pg_map["metal"]],
},
"WavePort": [
{
"Index": 1,
"Attributes": [pg_map["waveport_1"]],
"Mode": 1,
"Offset": 0.0,
"Excitation": True,
},
{
"Index": 2,
"Attributes": [pg_map["waveport_2"]],
"Mode": 1,
"Offset": 0.0,
},
],
},
"Solver": {
"Order": 2,
"Device": "CPU",
"Driven": {
"MinFreq": freq_min,
"MaxFreq": freq_max,
"FreqStep": freq_step,
"SaveStep": 1,
"AdaptiveTol": 0.001,
},
"Linear": {
"Type": "Default",
"KSPType": "GMRES",
"Tol": 1e-8,
"MaxIts": 200,
},
},
}
config_path = str(sim.write_config(output_file))
run_palace(config_path, num_procs=8, work_dir=os.getcwd())
Palace config written to /home/martin/Desktop/PalaceToolkit/docs/examples/waveguide_box.json
Running: /home/martin/.cache/palacetoolkit/runtime/palace-cpu-v0.1.2/bin/palace --serial /home/martin/Desktop/PalaceToolkit/docs/examples/waveguide_box.json
>> /home/martin/.cache/palacetoolkit/runtime/palace-cpu-v0.1.2/bin/palace-x86_64.bin /home/martin/Desktop/PalaceToolkit/docs/examples/waveguide_box.json
_____________ _______
_____ __ \____ __ /____ ____________
____ /_/ / __ ` / / __ ` / ___/ _ \
___ _____/ /_/ / / /_/ / /__/ ___/
/__/ \___,__/__/\___,__/\_____\_____/
--> Warning!
Output folder is not empty; program will overwrite content! (postpro/waveguide_box)
Git changeset ID: v0.16.1-51-g4f2e2d97
Running with 1 MPI process, 1 OpenMP thread
Device configuration: omp,cpu
Memory configuration: host-std
libCEED backend: /cpu/self/xsmm/blocked
Characteristic length and time scales:
Lc = 1.000e-01 m, tc = 3.336e-01 ns
Finished partitioning mesh into 1 subdomain
Mesh curvature order: 1
Mesh bounding box:
(Xmin, Ymin, Zmin) = (-1.143e-02, -5.080e-03, +0.000e+00) m
(Xmax, Ymax, Zmax) = (+1.143e-02, +5.080e-03, +1.000e-01) m
Parallel Mesh Stats:
minimum average maximum total
vertices 1649 1649 1649 1649
edges 8775 8775 8775 8775
faces 12990 12990 12990 12990
elements 5863 5863 5863 5863
neighbors 0 0 0
minimum maximum
h 0.0121339 0.0474389
kappa 1.03977 7.87187
Estimated current per-rank memory usage is: Min. 48.8M, Max. 48.8M, Avg. 48.8M, Total 48.8M
Estimated current per-node memory usage is: Min. 48.8M, Max. 48.8M, Avg. 48.8M, Total 48.8M
Verification failed: (da >= 0 && db >= 0 && da != db) is false:
--> Unexpected wave port geometry for normalization!
... in function: palace::WavePortData::WavePortData(const palace::config::WavePortData&, const palace::config::BoundaryData&, const palace::config::DomainData&, palace::ProblemType, const palace::config::LinearSolverData&, const palace::Units&, const palace::MaterialOperator&, mfem::ParFiniteElementSpace&, mfem::ParFiniteElementSpace&, const mfem::Array<int>&)
... in file: /tmp/palace-src/palace/models/waveportoperator.cpp:553
--------------------------------------------------------------------------
MPI_ABORT was invoked on rank 0 in communicator MPI_COMM_WORLD
with errorcode 1.
NOTE: invoking MPI_ABORT causes Open MPI to kill all MPI processes.
You may or may not see output from other processes, depending on
exactly when Open MPI kills them.
--------------------------------------------------------------------------
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
Cell In[8], line 84
80 except RuntimeError as exc:
81 if os.environ.get("DOCS_BUILD") == "1":
82 print(f"Palace run skipped in docs build due to runtime failure: {exc}")
83 else:
---> 84 raise
File ~/Desktop/PalaceToolkit/src/palacetoolkit/simulation.py:412, in run_palace(config_file, num_procs, work_dir, sif_path)
410 result = subprocess.run(cmd, cwd=work_dir, capture_output=False, env=run_env)
411 if result.returncode != 0:
--> 412 raise RuntimeError(f"Palace exited with code {result.returncode}")
413 return
415 if palace_sif_path is None:
RuntimeError: Palace exited with code 1