use std::f64::consts::PI;
use num_complex::Complex64;
use oxiphoton::fdtd::monitor::eigenmode_decomp::{EigenModeMonitor, EigenModeProfile};
use oxiphoton::fdtd::monitor::flux::FluxNormal;
use oxiphoton::io::{cascade_two_port, TouchstoneReader, TouchstoneWriter};
fn fabry_perot_s2port(freq_hz: f64, fsr_hz: f64, r_mirror: f64) -> Vec<Vec<Complex64>> {
let phi = PI * freq_hz / fsr_hz;
let exp2i_phi = Complex64::new((2.0 * phi).cos(), (2.0 * phi).sin());
let r2 = r_mirror * r_mirror;
let one = Complex64::new(1.0, 0.0);
let denom = one - Complex64::new(r2, 0.0) * exp2i_phi;
let s21 = Complex64::new(1.0 - r2, 0.0) / denom;
let s11 = Complex64::new(r_mirror, 0.0) * (exp2i_phi - one) / denom;
vec![vec![s11, s21], vec![s21, s11]]
}
fn main() {
println!("=== S-Parameter Extraction and Touchstone Export Example ===");
println!();
let fsr_hz = 50.0e9_f64; let r_mirror = 0.5_f64; let f_start = 185.0e12_f64; let f_end = 195.0e12_f64; let n_freq = 201usize;
println!("Fabry-Perot resonator parameters:");
println!(" FSR = {:.1} GHz", fsr_hz * 1e-9);
println!(
" Mirror reflectivity r = {:.2} (R = {:.0}%)",
r_mirror,
r_mirror * r_mirror * 100.0
);
println!(
" Finesse F ≈ π√R / (1-R) ≈ {:.1}",
PI * r_mirror / (1.0 - r_mirror * r_mirror)
);
println!(
" Frequency range: {:.1}–{:.1} THz",
f_start * 1e-12,
f_end * 1e-12
);
println!(" Number of points: {}", n_freq);
println!();
let mut writer = TouchstoneWriter::new(2, 50.0);
let df = (f_end - f_start) / (n_freq - 1) as f64;
for i in 0..n_freq {
let f = f_start + i as f64 * df;
let s_mat = fabry_perot_s2port(f, fsr_hz, r_mirror);
writer.add_frequency_point(f, s_mat);
}
let tmp_path = {
let mut p = std::env::temp_dir();
p.push("fabry_perot.s2p");
p.to_string_lossy().into_owned()
};
writer
.write_to_file(&tmp_path)
.expect("Writing Touchstone file should succeed");
println!("Touchstone file written to: {}", tmp_path);
let file_content =
std::fs::read_to_string(&tmp_path).expect("Reading back Touchstone file should succeed");
let reader =
TouchstoneReader::parse(&file_content).expect("Parsing Touchstone file should succeed");
println!("Round-trip verification:");
println!(
" Written frequencies: {}, Read frequencies: {}",
writer.frequencies.len(),
reader.frequencies.len()
);
let mut max_err = 0.0_f64;
for i in 0..n_freq {
let f_w = writer.frequencies[i];
let f_r = reader.frequencies[i];
let freq_err = (f_w - f_r).abs() / f_w.max(1.0);
max_err = max_err.max(freq_err);
let s21_w = writer.s_params[i][1][0];
let s21_r = reader.s_params[i][1][0];
let s_err = (s21_w - s21_r).norm();
max_err = max_err.max(s_err);
}
println!(" Maximum round-trip error (freq + S21): {:.3e}", max_err);
if max_err < 1e-4 {
println!(" [OK] Round-trip accuracy verified (< 0.01%).");
}
println!();
let il_db = reader.insertion_loss_db(0, 1); let rl_db = reader.return_loss_db(0); let gd = reader.group_delay(0, 1);
let mut resonances: Vec<(f64, f64)> = Vec::new(); let s21_mag = reader.magnitude(0, 1);
for i in 1..n_freq.saturating_sub(1) {
if s21_mag[i] > s21_mag[i - 1] && s21_mag[i] > s21_mag[i + 1] {
resonances.push((reader.frequencies[i] * 1e-12, il_db[i]));
}
}
println!("Insertion loss S21 analysis:");
println!(
" Min IL = {:.3} dB (at resonance: near-zero loss)",
il_db.iter().cloned().fold(f64::INFINITY, f64::min)
);
println!(
" Max IL = {:.3} dB (at anti-resonance)",
il_db.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
);
println!(" Return loss S11 analysis:");
println!(
" Min RL = {:.3} dB (at resonance: near-zero reflection)",
rl_db.iter().cloned().fold(f64::INFINITY, f64::min)
);
println!(
" Max RL = {:.3} dB (at anti-resonance)",
rl_db.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
);
println!(
" First {} resonances detected (f, IL_dB):",
resonances.len().min(5)
);
for &(f_thz, il) in resonances.iter().take(5) {
println!(" f = {:.3} THz, IL = {:.4} dB", f_thz, il);
}
if let Some(i_res) = s21_mag
.iter()
.copied()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i)
{
println!(
" Group delay at peak transmission: {:.3} ps",
gd[i_res] * 1e12
);
}
println!();
let bw_3db = reader.bandwidth_3db(0, 1);
let finesse = PI * r_mirror / (1.0 - r_mirror * r_mirror);
let expected_bw = fsr_hz / finesse;
match bw_3db {
Some(bw) => {
println!("3-dB bandwidth analysis:");
println!(" Measured BW_3dB = {:.3} GHz", bw * 1e-9);
println!(
" Expected BW_3dB = FSR/F = {:.3} GHz (FSR={:.0} GHz, F={:.1})",
expected_bw * 1e-9,
fsr_hz * 1e-9,
finesse
);
let bw_err = (bw - expected_bw).abs() / expected_bw;
println!(
" Agreement: {:.1}% (limited by frequency sampling resolution)",
(1.0 - bw_err) * 100.0
);
}
None => {
println!("3-dB bandwidth: insufficient data (increase n_freq for accurate result)");
}
}
println!();
let monitor_freqs = vec![191.0e12_f64, 192.0e12, 193.0e12]; let dt = 1e-17_f64;
let mut emon = EigenModeMonitor::new(
FluxNormal::Z, 20, (5, 35), (5, 35), monitor_freqs.clone(),
dt,
);
let ni = 30usize; let nj = 30usize;
let n_eff_te = 2.35_f64; let sigma_grid = 4.0_f64;
let mut te_mode = EigenModeProfile::gaussian(n_eff_te, ni, nj, sigma_grid, sigma_grid);
te_mode
.normalize()
.expect("Mode normalization should succeed for non-zero Gaussian mode");
te_mode.mode_number = 0;
emon.add_mode(te_mode);
println!("EigenModeMonitor setup:");
println!(
" Monitor plane: z = 20, transverse range 5..35 × 5..35 ({} × {} cells)",
ni, nj
);
println!(
" Frequencies: {} points ({:.0}–{:.0} THz)",
monitor_freqs.len(),
monitor_freqs.first().copied().unwrap_or(0.0) * 1e-12,
monitor_freqs.last().copied().unwrap_or(0.0) * 1e-12
);
println!(
" Registered modes: {} (TE fundamental, n_eff = {:.3})",
emon.modes.len(),
n_eff_te
);
println!(
" Mode self-overlap (after normalise): {:.6}",
emon.modes[0].self_overlap()
);
println!();
let amp = (0.5_f64).sqrt(); let s_splitter: Vec<Vec<Complex64>> = vec![
vec![Complex64::new(0.0, 0.0), Complex64::new(0.0, amp)],
vec![Complex64::new(0.0, amp), Complex64::new(0.0, 0.0)],
];
let s_cascade = cascade_two_port(&s_splitter, &s_splitter);
let s11_c = s_cascade[0][0];
let s21_c = s_cascade[1][0];
let s12_c = s_cascade[0][1];
let s22_c = s_cascade[1][1];
println!("Cascade of two identical 3-dB splitters:");
println!(" Each splitter: S21 = i/√2 (|S21|² = 0.5 → 3 dB)");
println!(" Cascaded result:");
println!(
" S11 = {:.4}+{:.4}i (|S11|² = {:.4})",
s11_c.re,
s11_c.im,
s11_c.norm_sqr()
);
println!(
" S21 = {:.4}+{:.4}i (|S21|² = {:.4} → {:.1} dB)",
s21_c.re,
s21_c.im,
s21_c.norm_sqr(),
-10.0 * s21_c.norm_sqr().log10()
);
println!(
" S12 = {:.4}+{:.4}i (|S12|² = {:.4})",
s12_c.re,
s12_c.im,
s12_c.norm_sqr()
);
println!(
" S22 = {:.4}+{:.4}i (|S22|² = {:.4})",
s22_c.re,
s22_c.im,
s22_c.norm_sqr()
);
println!(
" Phase of S21: {:.1}° (expected: 180° = 2×90°)",
s21_c.arg().to_degrees()
);
println!(
" Energy conservation: |S21|² + |S11|² = {:.4} (should be ≤ 1)",
s21_c.norm_sqr() + s11_c.norm_sqr()
);
println!();
println!("── S-parameter analysis summary ─────────────────────────────────────");
println!(
" FP resonator (r={:.2}, FSR={:.0} GHz):",
r_mirror,
fsr_hz * 1e-9
);
println!(
" Peak |S21|: {:.3} (at resonance)",
reader
.magnitude(0, 1)
.iter()
.cloned()
.fold(0.0_f64, f64::max)
);
println!(
" Min |S11|: {:.3} (at resonance)",
reader
.magnitude(0, 0)
.iter()
.cloned()
.fold(f64::INFINITY, f64::min)
);
println!(
" Peak group delay: {:.3} ps",
gd.iter().cloned().fold(f64::NEG_INFINITY, f64::max) * 1e12
);
println!(" Resonances found: {}", resonances.len());
if let Some(bw) = bw_3db {
println!(" 3-dB bandwidth: {:.3} GHz", bw * 1e-9);
}
println!(" Touchstone file: {}", tmp_path);
println!();
println!("=== Example complete ===");
}