Pathway 1: Single Solid Analysis¶
This interactive tutorial walks through the simplest analysis pathway: a single rectangular waveguide solved with the Frequency Domain Solver (FDS) and compared against the analytical solution.
EMProject → Create Geometry → FDS Solve → Plot & Compare
Overview¶
All simulations in cavsim3d are managed through the EMProject class, which provides:
- Project directory management and persistence
- Geometry creation/import and meshing
- Solver configuration and execution
- Result access and plotting
1. Create Project & Geometry¶
We create a rectangular waveguide with:
- Width $a = 100$ mm
- Length $L = 200$ mm
The EMProject class manages the entire simulation lifecycle.
from cavsim3d.core.em_project import EMProject
from cavsim3d.geometry.primitives import RectangularWaveguide
import matplotlib.pyplot as plt
%matplotlib widget
# 1. Create the project
project_name = 'pathway1_single_rwg'
base_dir = './simulations' # Change to your preferred simulation directory
proj = EMProject(name=project_name, base_dir=base_dir, overwrite=True)
Creating new project 'pathway1_single_rwg' at simulations\pathway1_single_rwg
Create Geometry¶
For a single-component analysis, we create a RectangularWaveguide primitive
and assign it to the project. The geometry is automatically saved when the project is saved.
# 2. Create rectangular waveguide geometry
rwg = RectangularWaveguide(a=0.1, L=0.2)
proj.geometry = rwg
proj.save()
# Access geometry from the loaded project
geo = proj.geo
print(f"Geometry loaded: {type(geo).__name__}")
Geometry loaded: RectangularWaveguide
2. Visualise the Mesh¶
The waveguide mesh can be visualised interactively using NGSolve's WebGUI.
geo.show('mesh')
WebGuiWidget(layout=Layout(height='500px', width='100%'), value={'gui_settings': {}, 'ngsolve_version': '6.2.2…
3. Solve the Full-Order Model (FOM)¶
The solver is configured using a dictionary containing all parameters:
| Parameter | Description |
|---|---|
nportmodes |
Number of modes per port |
order |
Polynomial order of the finite element space |
nsamples |
Number of frequency sample points |
fmin |
Minimum frequency (GHz) |
fmax |
Maximum frequency (GHz) |
solver_type |
'direct' or 'iterative' |
At each frequency $\omega_i$, the solver computes:
$$ (\mathbf{K} - \omega_i^2 \mathbf{M}) \mathbf{x}_i = \mathbf{B} \mathbf{a}_i $$
# 3. Configure and run the frequency domain solver
fom_config = {
'nportmodes': 1,
'order': 3,
'nsamples': 100,
'fmin': 1e-3,
'fmax': 5,
'solver_type': 'direct',
# 'verbose': True,
'rerun': True # Uncomment to force re-solve even if results exist
}
fom_result = proj.fds.solve(config=fom_config)
vacuum: ['port1 (external, input)', 'port2 (external, output)'] ============================================================ ============================================================ Assembling Matrices... ============================================================ Solving port eigenmodes... Calculating Port Eigenmodes... port1 mode 0: TE_10, kc=31.4159, σ=+1 port2 mode 0: TE_10, kc=31.4159, σ=-1 Port eigenmodes complete: 2 total modes boundary condition: left|right|top|bottom Saved port modes to simulations\pathway1_single_rwg\fds\port_modes\port_modes.pkl FDS Solve: 0.0010 - 5.0000 GHz, 100 samples Global Coupled Solve Coupled solve complete: 2 external ports in 1.82s Saved port modes to simulations\pathway1_single_rwg\fds\port_modes\port_modes.pkl
4. Plot S-Parameters¶
For a single (non-compound) structure, results are accessed via proj.fds.fom.
The plot_s() and plot_z() methods accept a list of parameter labels in the format
'port(mode)port(mode)', e.g., '1(1)1(1)' for $S_{11}$ mode 1.
# 4. Plot S-parameters: magnitude and phase
which = [['1(1)1(1)'], ['1(1)2(1)']]
fig, axs = plt.subplot_mosaic(
[[1, 2], [3, 4]], figsize=(10, 8), layout='constrained'
)
for idx, wh in enumerate(which):
# Magnitude
proj.fds.fom.plot_s(wh, ax=axs[idx + 1])
# Phase
proj.fds.fom.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
fig.suptitle('Single RWG S-Parameters', fontsize=14)
Text(0.5, 0.98, 'Single RWG S-Parameters')
5. Compare with Analytical Solution¶
The RWGAnalytical class provides closed-form S- and Z-parameters for a
rectangular waveguide, allowing verification of the numerical results.
from cavsim3d.analytical.rectangular_waveguide import RWGAnalytical
# Create analytical model with same dimensions and frequency range
analytical = RWGAnalytical(
a=0.1, L=0.2,
freq_range=(fom_config['fmin'], fom_config['fmax'])
)
# Plot comparison: FOM vs Analytical
which = [['1(1)1(1)'], ['1(1)2(1)']]
fig, axs = plt.subplot_mosaic(
[[1, 2], [3, 4]], figsize=(10, 8), layout='constrained'
)
for idx, wh in enumerate(which):
# Magnitude
analytical.plot_s(wh, ax=axs[idx + 1])
proj.fds.fom.plot_s(wh, ax=axs[idx + 1])
# Phase
analytical.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
proj.fds.fom.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
fig.suptitle('FOM vs Analytical:: S-Parameters', fontsize=14)
Text(0.5, 0.98, 'FOM vs Analytical:: S-Parameters')
Model Order Reduction¶
# 5. Reduce the model order
rom = proj.fds.fom.reduce() # this creates a proj.fds.fom.rom object
# 6. Configure and run solve on the reduced order model for more frequency samples
rom_config = {
'nsamples': 1000,
'fmin': 1e-3,
'fmax': 5,
'solver_type': 'direct',
# 'verbose': True,
'rerun': True # Uncomment to force re-solve even if results exist
}
rom_result = proj.fds.fom.rom.solve(config=rom_config)
============================================================ Model Order Reduction ============================================================ Reduction complete: 1428 → 50 DOFs (96.5% compression) Saved port modes to simulations\pathway1_single_rwg\fds\port_modes\port_modes.pkl ROM Solve: 0.001 - 5 GHz, 1000 samples Solve loop: 0.008s (1000 freq points) Saved port modes to simulations\pathway1_single_rwg\fds\port_modes\port_modes.pkl
6. Compare with Analytical Solution and Full Order Model (FOM)¶
The RWGAnalytical class provides closed-form S- and Z-parameters for a
rectangular waveguide, allowing verification of the numerical results.
# Plot comparison: FOM vs Analytical
which = [['1(1)1(1)'], ['1(1)2(1)']]
fig, axs = plt.subplot_mosaic(
[[1, 2], [3, 4]], figsize=(10, 8), layout='constrained'
)
for idx, wh in enumerate(which):
# Magnitude
analytical.plot_s(wh, ax=axs[idx + 1])
proj.fds.fom.plot_s(wh, ax=axs[idx + 1], lw=0, marker='o', mfc='none')
proj.fds.fom.rom.plot_s(wh, ax=axs[idx + 1])
# Phase
analytical.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
proj.fds.fom.plot_s(wh, plot_type='phase', ax=axs[idx + 3], lw=0, marker='o', mfc='none')
proj.fds.fom.rom.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
fig.suptitle('FOM vs Analytical:: S-Parameters', fontsize=14)
Text(0.5, 0.98, 'FOM vs Analytical:: S-Parameters')
# Plot comparison: FOM vs Analytical
which = [['1(1)1(1)'], ['1(1)2(1)']]
fig, axs = plt.subplot_mosaic(
[[1, 2], [3, 4]], figsize=(10, 8), layout='constrained'
)
for idx, wh in enumerate(which):
# Magnitude
analytical.plot_z(wh, ax=axs[idx + 1])
proj.fds.fom.plot_z(wh, ax=axs[idx + 1], lw=0, marker='o', mfc='none')
proj.fds.fom.rom.plot_z(wh, ax=axs[idx + 1])
# Phase
analytical.plot_z(wh, plot_type='phase', ax=axs[idx + 3])
proj.fds.fom.plot_z(wh, plot_type='phase', ax=axs[idx + 3], lw=0, marker='o', mfc='none')
proj.fds.fom.rom.plot_z(wh, plot_type='phase', ax=axs[idx + 3])
fig.suptitle('FOM vs Analytical:: Z-Parameters', fontsize=14)
Text(0.5, 0.98, 'FOM vs Analytical:: Z-Parameters')
Summary¶
In this tutorial we:
- Created an
EMProjectto manage the simulation - Defined a rectangular waveguide geometry
- Configured and ran the Frequency Domain Solver via a config dictionary
- Plotted S-parameter magnitude and phase
- Validated results against the closed-form analytical solution
Next: Try Pathway 2: Global Assembly to analyse multi-component structures.