Tutorial 3: S-Parameter Extraction
Time: 30 minutes
Difficulty: Intermediate
Prerequisites: Tutorials 1-2
Learning Objectives
Set up two-port measurements
Extract S11 (reflection) and S21 (transmission)
Calculate insertion loss and return loss
Perform frequency sweeps
The Device: Directional Coupler
We’ll analyze a 2×2 directional coupler and extract its full S-matrix.
Quick Implementation
import numpy as np
from prismo import Simulation
from prismo.modes.solver import ModeSolver
from prismo.monitors.mode_monitor import ModeExpansionMonitor
from prismo.sources.mode import ModeSource
# Setup (simplified for clarity)
wavelength = 1.55e-6
frequencies = np.linspace(185e12, 200e12, 16) # Frequency sweep
# 1. Solve for waveguide modes
mode_solver = ModeSolver(wavelength, x, y, epsilon)
modes = mode_solver.solve(num_modes=1, mode_type='TE')
# 2. Create mode source at port 1
source = ModeSource(
center=(0.0, 0.0, port1_z),
size=(4e-6, 4e-6, 0.0),
mode=modes[0],
direction='+z',
waveform=pulse,
)
# 3. Add mode monitors at all ports
port1_monitor = ModeExpansionMonitor(
center=(0.0, 0.0, port1_z),
size=(4e-6, 4e-6, 0.0),
modes=modes,
direction='z',
frequencies=frequencies.tolist(),
name='port1'
)
port2_monitor = ModeExpansionMonitor(
center=(0.0, 0.0, port2_z),
size=(4e-6, 4e-6, 0.0),
modes=modes,
direction='z',
frequencies=frequencies.tolist(),
name='port2'
)
# Add monitors for ports 3 and 4...
sim.add_source(source)
sim.add_monitor(port1_monitor)
sim.add_monitor(port2_monitor)
# 4. Run simulation
sim.run(200e-15)
# 5. Extract S-parameters
s11 = port1_monitor.compute_s_parameters(source_mode_index=0)['S_11']
s21 = port2_monitor.compute_s_parameters(source_mode_index=0)['S_11']
# 6. Plot results
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 1, figsize=(10, 8))
# Magnitude
axes[0].plot(frequencies/1e12, 20*np.log10(np.abs(s11)), label='S11 (Reflection)')
axes[0].plot(frequencies/1e12, 20*np.log10(np.abs(s21)), label='S21 (Transmission)')
axes[0].set_ylabel('Magnitude (dB)')
axes[0].set_title('S-Parameters vs Frequency')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# Phase
axes[1].plot(frequencies/1e12, np.angle(s11, deg=True), label='S11 Phase')
axes[1].plot(frequencies/1e12, np.angle(s21, deg=True), label='S21 Phase')
axes[1].set_xlabel('Frequency (THz)')
axes[1].set_ylabel('Phase (degrees)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('tutorial3_sparameters.png', dpi=150)
plt.show()
# 7. Calculate metrics
insertion_loss = -20 * np.log10(np.abs(s21)) # dB
return_loss = -20 * np.log10(np.abs(s11)) # dB
print(f"At center frequency:")
print(f" Insertion Loss: {insertion_loss[len(frequencies)//2]:.2f} dB")
print(f" Return Loss: {return_loss[len(frequencies)//2]:.2f} dB")
Understanding S-Parameters
For a 2-port device:
S11: Reflection at port 1 (how much reflects back)
S21: Transmission from port 1 to port 2
S12: Transmission from port 2 to port 1 (= S21 for reciprocal devices)
S22: Reflection at port 2
Port 1 Device Port 2
→ ──────────────────────────────── →
S11 ← S21 →
Key Metrics
def analyze_sparameters(s11, s21):
"""Extract key metrics from S-parameters."""
# Insertion Loss (IL)
IL = -20 * np.log10(np.abs(s21))
# Return Loss (RL)
RL = -20 * np.log10(np.abs(s11))
# Power transmission
T = np.abs(s21)**2
# Power reflection
R = np.abs(s11)**2
# Check energy conservation (for lossless device)
conservation = T + R
print(f"Insertion Loss: {np.mean(IL):.2f} ± {np.std(IL):.2f} dB")
print(f"Return Loss: {np.mean(RL):.2f} ± {np.std(RL):.2f} dB")
print(f"Power Conservation: {np.mean(conservation):.4f} (should be ~1.0)")
return {'IL': IL, 'RL': RL, 'T': T, 'R': R}
metrics = analyze_sparameters(s11, s21)
Validation Checks
# 1. Reciprocity (S12 should equal S21)
s12 = port1_monitor_reversed.compute_s_parameters(source_mode_index=0)['S_11']
reciprocity_error = np.abs(s12 - s21) / np.abs(s21)
print(f"Reciprocity error: {np.max(reciprocity_error)*100:.2f}%")
# 2. Energy conservation
energy_balance = np.abs(s11)**2 + np.abs(s21)**2
print(f"Energy balance: {np.mean(energy_balance):.4f} (should be ~1.0 for lossless)")
# 3. Passivity (|S| ≤ 1 for passive devices)
max_s11 = np.max(np.abs(s11))
max_s21 = np.max(np.abs(s21))
print(f"Max |S11|: {max_s11:.4f} (should be ≤ 1.0)")
print(f"Max |S21|: {max_s21:.4f} (should be ≤ 1.0)")
Touchstone File Export
Export S-parameters in standard format:
def export_touchstone(frequencies, s_params, filename='device.s2p'):
"""Export 2-port S-parameters to Touchstone format."""
with open(filename, 'w') as f:
# Header
f.write('! 2-port S-parameters from Prismo\n')
f.write('# HZ S MA R 50\n') # Hz, S-params, Magnitude-Angle, 50Ω
# Data
for i, freq in enumerate(frequencies):
s11 = s_params['S11'][i]
s12 = s_params['S12'][i]
s21 = s_params['S21'][i]
s22 = s_params['S22'][i]
# Format: freq S11_mag S11_ang S21_mag S21_ang S12_mag S12_ang S22_mag S22_ang
f.write(f"{freq:.6e} ")
f.write(f"{np.abs(s11):.6e} {np.angle(s11, deg=True):.6e} ")
f.write(f"{np.abs(s21):.6e} {np.angle(s21, deg=True):.6e} ")
f.write(f"{np.abs(s12):.6e} {np.angle(s12, deg=True):.6e} ")
f.write(f"{np.abs(s22):.6e} {np.angle(s22, deg=True):.6e}\n")
print(f"✓ Exported to {filename}")
# Export
s_params_dict = {
'S11': s11, 'S12': s12,
'S21': s21, 'S22': s22,
}
export_touchstone(frequencies, s_params_dict, 'coupler.s2p')
Exercises
Simulate a simple straight waveguide and verify S21 ≈ 1, S11 ≈ 0
Add losses (lossy material) and observe increased insertion loss
Create a Bragg grating and observe reflection peaks in S11
Measure 3 dB bandwidth of a resonator
Summary
Key Points:
Mode monitors enable S-parameter extraction
Always check reciprocity and energy conservation
Frequency sweeps reveal bandwidth and resonances
Touchstone format enables integration with circuit simulators
Next Steps
Tutorial 4: Parameter Optimization - Use S-parameters to optimize designs
Mode Ports - Advanced mode port techniques
Examples - More S-parameter examples