Pathway 2: Multi-Solid Global Assembly¶
This tutorial demonstrates assembling multiple geometric parts into a single domain and solving the fused system globally.
EMProject → Create Assembly → Add Components → Build & Mesh → FDS Solve → Plot & Compare
Overview¶
When multiple parts are fused into a single domain via build(),
the interfaces between them disappear and the solver sees a single continuous mesh.
This pathway is ideal for small assemblies where solving the fused system
is computationally feasible.
1. Create Project and Assembly¶
The EMProject manages the full simulation lifecycle.
An Assembly groups multiple parts along a principal axis.
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 = 'pathway2_global_assembly'
base_dir = './simulations' # Change to your preferred simulation directory
proj = EMProject(name=project_name, base_dir=base_dir, overwrite=True)
Creating new project 'pathway2_global_assembly' at simulations\pathway2_global_assembly
2. Add Components¶
We add two rectangular waveguide sections.
The after argument auto-aligns the second component flush against the first.
# 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("rwg2", wg1, after="rwg1")
assembly.build()
assembly.generate_mesh(maxh=0.03)
# Visualise the mesh
proj.geo.show('mesh')
Port naming complete: Total ports: 3 External ports: 2 Interface ports: 1
WebGuiWidget(layout=Layout(height='500px', width='100%'), value={'gui_settings': {}, 'ngsolve_version': '6.2.2…
3. Solve the Full-Order Model (FOM)¶
Since the assembly is a compound structure (multiple domains), the solver automatically performs per-domain solving and concatenation.
The solver is configured using a config dictionary:
| 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' |
# 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)
rwg1: ['port1 (external, input)', 'port2 (internal)'] rwg2: ['port2 (internal)', 'port3 (external, output)'] ============================================================ ============================================================ Assembling Matrices... ============================================================ Solving port eigenmodes... Calculating Port Eigenmodes... port1 mode 0: TE_10, kc=31.4159, σ=+1 port1 mode 1: TE_01, kc=62.8319, σ=+1 port1 mode 2: TE_20, kc=62.8319, σ=+1 port2 mode 0: kc=31.4161, type=TE, fc=1.4990 GHz, σ=-1, phase=+ port2 mode 1: kc=62.8343, type=TE, fc=2.9980 GHz, σ=-1, phase=+, pol=0°, degen=2 port2 mode 2: kc=62.8343, type=TE, fc=2.9980 GHz, σ=-1, phase=+, pol=90°, degen=2 port3 mode 0: TE_10, kc=31.4159, σ=-1 port3 mode 1: TE_01, kc=62.8319, σ=-1 port3 mode 2: TE_20, kc=62.8319, σ=-1 Port eigenmodes complete: 9 total modes boundary condition: default Saved port modes to simulations\pathway2_global_assembly\fds\port_modes\port_modes.pkl FDS Solve: 0.0010 - 5.0000 GHz, 100 samples Per-Domain Solve Completed: 2 ports, 100 frequencies in 6.41s Completed: 2 ports, 100 frequencies in 7.15s Saved port modes to simulations\pathway2_global_assembly\fds\port_modes\port_modes.pkl
4. Plot S-Parameters¶
For a compound structure (multiple domains), results are accessed via proj.fds.foms
(note the plural). The parameter labels use the format 'port(mode)port(mode)'.
# 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('Global Assembly — S-Parameters', fontsize=14)
Text(0.5, 0.98, 'Global Assembly — S-Parameters')
5. Concatenate Full Order Model (Only for testing)¶
roms = proj.fds.foms.reduce()
roms_config = {
'nsamples': 1000, # keeping nsamples moderate because this is essentially the full order model
'fmin': 1e-3,
'fmax': 5,
'solver_type': 'direct',
'rerun': True # Uncomment to force re-solve even if results exist
}
roms_result = roms.solve(config=roms_config)
concat = roms.concatenate()
# 3. Configure and run the frequency domain solver
concat_config = {
'nsamples': 1000, # keeping nsamples moderate because this is essentially the full order model
'fmin': 1e-3,
'fmax': 5,
'solver_type': 'direct',
'rerun': True # Uncomment to force re-solve even if results exist
}
concat_result = concat.solve(config=concat_config)
============================================================ Model Order Reduction ============================================================ Reduction complete: 6953 → 98 DOFs (98.6% compression) Saved port modes to simulations\pathway2_global_assembly\fds\port_modes\port_modes.pkl ROM Solve: 0.001 - 5 GHz, 1000 samples Concat Solve: 0.001 - 5.0 GHz, 1000 samples, system size 95 Concat solve complete: 0.227s (1000 frequencies) Concat Solve: 0.001 - 5 GHz, 1000 samples, system size 95 Concat solve complete: 0.317s (1000 frequencies)
6. Compare with Analytical Solution¶
The RWGAnalytical class provides closed-form S- and Z-parameters for a
rectangular waveguide. Note that the total length is the sum of the two
components: $L = 0.1 + 0.1 = 0.2$ m.
from cavsim3d.analytical.rectangular_waveguide import RWGAnalytical
# Create analytical model with the total length (sum of both components)
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])
concat.plot_s(wh, ax=axs[idx + 1])
# Phase
analytical.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
concat.plot_s(wh, plot_type='phase', ax=axs[idx + 3])
fig.suptitle('Concat vs Analytical — S-Parameters', fontsize=14)
Text(0.5, 0.98, 'Concat vs Analytical — S-Parameters')
Summary¶
In this tutorial we:
- Created an
EMProjectand a multi-component assembly with automatic alignment - Built and meshed the fused assembly
- Configured and ran the Frequency Domain Solver via the
configdictionary - Plotted compound S-parameter magnitude and phase using
proj.fds.foms - Validated results against the analytical solution
This pathway is ideal for small assemblies. For larger systems, see: