use super::circuit_elements::{Complex, TransferMatrix2x2};
#[derive(Clone, Debug)]
pub struct WaveguideSection {
pub length_m: f64,
pub n_eff: f64,
pub loss_db_per_m: f64,
pub wavelength_m: f64,
}
impl WaveguideSection {
pub fn new(length_m: f64, n_eff: f64, loss_db_per_m: f64, wavelength_m: f64) -> Self {
Self {
length_m,
n_eff,
loss_db_per_m,
wavelength_m,
}
}
pub fn phase_rad(&self) -> f64 {
2.0 * std::f64::consts::PI * self.n_eff * self.length_m / self.wavelength_m
}
pub fn amplitude_transmission(&self) -> f64 {
let loss_db = self.loss_db_per_m * self.length_m;
10.0_f64.powf(-loss_db / 20.0)
}
pub fn transfer_matrix_single(&self) -> Complex {
Complex::from_polar(self.amplitude_transmission(), self.phase_rad())
}
pub fn transfer_matrix_upper_arm(&self) -> TransferMatrix2x2 {
let field = self.transfer_matrix_single();
TransferMatrix2x2 {
m: [
[field, Complex::new(0.0, 0.0)],
[Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)],
],
}
}
pub fn transfer_matrix_balanced(&self) -> TransferMatrix2x2 {
let field = self.transfer_matrix_single();
TransferMatrix2x2 {
m: [
[field, Complex::new(0.0, 0.0)],
[Complex::new(0.0, 0.0), field],
],
}
}
pub fn power_transmission(&self) -> f64 {
let a = self.amplitude_transmission();
a * a
}
pub fn insertion_loss_db(&self) -> f64 {
self.loss_db_per_m * self.length_m
}
pub fn group_delay_s(&self, n_group: f64) -> f64 {
n_group * self.length_m / crate::pic_simulation::noise_model::C_LIGHT
}
}
#[derive(Clone, Debug)]
pub struct GratingCoupler {
pub coupling_efficiency: f64,
pub center_wavelength_m: f64,
pub bandwidth_nm: f64,
pub insertion_loss_db: f64,
}
impl GratingCoupler {
pub fn standard_si() -> Self {
Self {
coupling_efficiency: 0.5,
center_wavelength_m: 1550e-9,
bandwidth_nm: 40.0,
insertion_loss_db: 3.0,
}
}
pub fn transmission(&self, wavelength_m: f64) -> f64 {
let lambda_nm = wavelength_m * 1e9;
let center_nm = self.center_wavelength_m * 1e9;
let sigma = self.bandwidth_nm / (2.0 * (2.0 * 2.0_f64.ln()).sqrt());
let gauss = (-((lambda_nm - center_nm).powi(2)) / (2.0 * sigma * sigma)).exp();
let il_linear = 10.0_f64.powf(-self.insertion_loss_db / 10.0);
self.coupling_efficiency * il_linear * gauss
}
pub fn bandwidth_1db_nm(&self) -> f64 {
self.bandwidth_nm / 1.23
}
pub fn back_reflection_db(&self) -> f64 {
-20.0
}
pub fn peak_insertion_loss_db(&self) -> f64 {
-10.0 * self.coupling_efficiency.log10() + self.insertion_loss_db
}
}
#[derive(Clone, Debug)]
pub struct YJunction {
pub split_ratio: f64,
pub excess_loss_db: f64,
pub imbalance_db: f64,
}
impl YJunction {
pub fn balanced() -> Self {
Self {
split_ratio: 0.5,
excess_loss_db: 0.2,
imbalance_db: 0.1,
}
}
pub fn ideal() -> Self {
Self {
split_ratio: 0.5,
excess_loss_db: 0.0,
imbalance_db: 0.0,
}
}
pub fn power_to_port1(&self, input_power: f64) -> f64 {
let il = 10.0_f64.powf(-self.excess_loss_db / 10.0);
input_power * il * self.split_ratio
}
pub fn power_to_port2(&self, input_power: f64) -> f64 {
let il = 10.0_f64.powf(-self.excess_loss_db / 10.0);
input_power * il * (1.0 - self.split_ratio)
}
pub fn imbalance_linear(&self) -> f64 {
10.0_f64.powf(self.imbalance_db / 10.0)
}
}
#[derive(Clone, Debug)]
pub struct PicCascade {
pub elements: Vec<String>,
pub matrices: Vec<TransferMatrix2x2>,
}
impl Default for PicCascade {
fn default() -> Self {
Self::new()
}
}
impl PicCascade {
pub fn new() -> Self {
Self {
elements: Vec::new(),
matrices: Vec::new(),
}
}
pub fn add_element(&mut self, label: &str, m: TransferMatrix2x2) {
self.elements.push(label.to_string());
self.matrices.push(m);
}
pub fn total_matrix(&self) -> TransferMatrix2x2 {
self.matrices
.iter()
.fold(TransferMatrix2x2::identity(), |acc, m| m.cascade(&acc))
}
pub fn through_transmission(&self, _wavelength_m: f64) -> f64 {
let m = self.total_matrix();
let (b1, _) = m.apply(Complex::new(1.0, 0.0), Complex::new(0.0, 0.0));
b1.abs2()
}
pub fn cross_transmission(&self, _wavelength_m: f64) -> f64 {
let m = self.total_matrix();
let (_, b2) = m.apply(Complex::new(1.0, 0.0), Complex::new(0.0, 0.0));
b2.abs2()
}
pub fn insertion_loss_db(&self, wavelength_m: f64) -> f64 {
let t = self.through_transmission(wavelength_m);
if t <= 0.0 {
return f64::INFINITY;
}
-10.0 * t.log10()
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn summary(&self) -> String {
let mut s = format!("PicCascade ({} elements):\n", self.elements.len());
for (i, label) in self.elements.iter().enumerate() {
s.push_str(&format!(" [{:02}] {}\n", i, label));
}
s
}
}
#[cfg(test)]
mod tests {
use super::super::circuit_elements::DirectionalCoupler;
use super::*;
#[test]
fn waveguide_phase_positive() {
let wg = WaveguideSection::new(100e-6, 2.4, 3000.0, 1550e-9);
assert!(wg.phase_rad() > 0.0);
}
#[test]
fn waveguide_lossless_amplitude_is_one() {
let wg = WaveguideSection::new(100e-6, 2.4, 0.0, 1550e-9);
assert!((wg.amplitude_transmission() - 1.0).abs() < 1e-12);
}
#[test]
fn waveguide_loss_monotone_with_length() {
let wg1 = WaveguideSection::new(100e-6, 2.4, 3000.0, 1550e-9);
let wg2 = WaveguideSection::new(200e-6, 2.4, 3000.0, 1550e-9);
assert!(wg1.amplitude_transmission() > wg2.amplitude_transmission());
}
#[test]
fn grating_coupler_peak_transmission() {
let gc = GratingCoupler::standard_si();
let t_peak = gc.transmission(gc.center_wavelength_m);
let expected = gc.coupling_efficiency * 10.0_f64.powf(-gc.insertion_loss_db / 10.0);
assert!((t_peak - expected).abs() < 1e-10, "t_peak={}", t_peak);
}
#[test]
fn grating_coupler_off_wavelength_lower() {
let gc = GratingCoupler::standard_si();
let t_peak = gc.transmission(gc.center_wavelength_m);
let t_off = gc.transmission(gc.center_wavelength_m + 30e-9);
assert!(t_off < t_peak, "off-centre should be lower");
}
#[test]
fn y_junction_power_conservation() {
let yj = YJunction::ideal();
let p1 = yj.power_to_port1(1.0);
let p2 = yj.power_to_port2(1.0);
assert!((p1 + p2 - 1.0).abs() < 1e-10, "P1+P2={}", p1 + p2);
}
#[test]
fn pic_cascade_empty_identity() {
let cascade = PicCascade::new();
let t = cascade.through_transmission(1550e-9);
assert!((t - 1.0).abs() < 1e-12, "empty cascade T={}", t);
}
#[test]
fn pic_cascade_two_couplers_power_conserved() {
let dc1 = DirectionalCoupler::new(0.5).transfer_matrix();
let dc2 = DirectionalCoupler::new(0.5).transfer_matrix();
let mut cascade = PicCascade::new();
cascade.add_element("dc1", dc1);
cascade.add_element("dc2", dc2);
let m = cascade.total_matrix();
let (b1, b2) = m.apply(Complex::new(1.0, 0.0), Complex::new(0.0, 0.0));
let total = b1.abs2() + b2.abs2();
assert!((total - 1.0).abs() < 1e-10, "Power={}", total);
}
}