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