Pathway 3: FOM Concatenation¶
This tutorial demonstrates the per-domain Full Order Model concatenation workflow. Each solid is solved independently, and their FOMs are cascaded via Kirchhoff coupling.
EMProject → Create Assembly → Per-Domain FDS Solve → Concatenation → Plot & Compare
Overview¶
When a compound structure is solved with proj.fds.solve(), the solver automatically:
- Identifies the multi-domain topology
- Solves each domain independently
- Concatenates the per-domain results via Kirchhoff coupling at internal ports
The Kirchhoff coupling enforces continuity at internal interfaces:
$$ \mathbf{a}_{\text{int}}^{(d)} = \mathbf{b}_{\text{int}}^{(d+1)}, \quad \mathbf{b}_{\text{int}}^{(d)} = \mathbf{a}_{\text{int}}^{(d+1)} $$
This cascades the per-domain S-matrices into a global 2-port response.
1. Create Project and Assembly¶
We create a two-component rectangular waveguide assembly.
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 = 'pathway3_fom_concatenation'
base_dir = './simulations' # Change to your preferred simulation directory
proj = EMProject(name=project_name, base_dir=base_dir, overwrite=True)
2. Build the Assembly¶
We add two rectangular waveguide sections to an assembly.
The after argument auto-aligns components along the principal axis.
# 2. Create assembly and add components
assembly = proj.create_assembly(main_axis='Z')
wg1 = RectangularWaveguide(a=0.1, L=0.1)
assembly.add("rwg1", wg1)
assembly.add("rwg1", wg1, after="rwg1")
assembly.build()
assembly.generate_mesh(maxh=0.02)
# Visualise the mesh
proj.geo.show('mesh')
3. Solve Per-Domain FOMs¶
The solver detects the compound structure and solves each domain independently. At each frequency $\omega_i$, for each domain $d$:
$$ (\mathbf{K}_d - \omega_i^2 \mathbf{M}_d) \mathbf{x}_{d,i} = \mathbf{B}_d \mathbf{a}_{d,i} $$
The results are then automatically concatenated via Kirchhoff coupling.
# 3. Configure and run the frequency domain solver
fom_config = {
'nportmodes': 3,
'order': 3,
'nsamples': 100,
'fmin': 1e-3,
'fmax': 5,
'solver_type': 'direct',
'rerun': True # Uncomment to force re-solve even if results exist
}
fom_result = proj.fds.solve(config=fom_config)
4. Plot Concatenated S-Parameters¶
The concatenated results for compound structures are accessed via proj.fds.foms.
Both magnitude and phase can be plotted.
# 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.foms.plot_s(wh, ax=axs[idx + 1])
# Phase
proj.fds.foms.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
fig.suptitle('FOM Concatenation — S-Parameters', fontsize=14)
5. Compare with Analytical Solution¶
Validate the concatenated FOM results against the closed-form analytical solution. The total waveguide length is the sum of both components ($L = 0.2$ m).
from cavsim3d.analytical.rectangular_waveguide import RWGAnalytical
# Create analytical model with total length
analytical = RWGAnalytical(
a=0.1, L=0.2,
freq_range=(fom_config['fmin'], fom_config['fmax'])
)
# Plot comparison: FOM Concatenation 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.foms.plot_s(wh, ax=axs[idx + 1])
# Phase
analytical.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
proj.fds.foms.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
fig.suptitle('FOM Concatenation vs Analytical — S-Parameters', fontsize=14)
Summary¶
In this tutorial we:
- Created a multi-domain assembly with two rectangular waveguide sections
- The solver automatically detected the compound structure
- Each domain was solved independently at each frequency point
- The per-domain FOMs were concatenated via Kirchhoff coupling
- Validated results against the analytical solution
Key concept: The EMProject orchestrator handles per-domain solving and concatenation
automatically — the same proj.fds.solve(config=...) call is used regardless of whether
the structure is single-domain or compound.
Next: Pathway 4: ROM Concatenation — the most efficient workflow.