use std::f64::consts::PI;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Complex(pub f64, pub f64);
impl Complex {
#[inline]
pub fn new(re: f64, im: f64) -> Self {
Self(re, im)
}
#[inline]
pub fn from_polar(r: f64, theta: f64) -> Self {
Self(r * theta.cos(), r * theta.sin())
}
#[inline]
pub fn abs(&self) -> f64 {
(self.0 * self.0 + self.1 * self.1).sqrt()
}
#[inline]
pub fn abs2(&self) -> f64 {
self.0 * self.0 + self.1 * self.1
}
#[inline]
pub fn conj(&self) -> Self {
Self(self.0, -self.1)
}
#[inline]
pub fn mul(&self, other: &Self) -> Self {
Self(
self.0 * other.0 - self.1 * other.1,
self.0 * other.1 + self.1 * other.0,
)
}
#[inline]
pub fn add(&self, other: &Self) -> Self {
Self(self.0 + other.0, self.1 + other.1)
}
#[inline]
pub fn scale(&self, s: f64) -> Self {
Self(self.0 * s, self.1 * s)
}
#[inline]
pub fn sub(&self, other: &Self) -> Self {
Self(self.0 - other.0, self.1 - other.1)
}
#[inline]
pub fn recip(&self) -> Self {
let d = self.abs2();
Self(self.0 / d, -self.1 / d)
}
#[inline]
pub fn div(&self, other: &Self) -> Self {
self.mul(&other.recip())
}
}
impl std::fmt::Display for Complex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.1 >= 0.0 {
write!(f, "{:.6}+{:.6}i", self.0, self.1)
} else {
write!(f, "{:.6}{:.6}i", self.0, self.1)
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct TransferMatrix2x2 {
pub m: [[Complex; 2]; 2],
}
impl TransferMatrix2x2 {
pub fn identity() -> Self {
let z = Complex::new(0.0, 0.0);
let o = Complex::new(1.0, 0.0);
Self {
m: [[o, z], [z, o]],
}
}
pub fn cascade(&self, other: &TransferMatrix2x2) -> TransferMatrix2x2 {
let mut result = [[Complex::new(0.0, 0.0); 2]; 2];
for (i, res_row) in result.iter_mut().enumerate() {
for (j, cell) in res_row.iter_mut().enumerate() {
let mut sum = Complex::new(0.0, 0.0);
for k in 0..2 {
sum = sum.add(&other.m[i][k].mul(&self.m[k][j]));
}
*cell = sum;
}
}
TransferMatrix2x2 { m: result }
}
pub fn apply(&self, a1: Complex, a2: Complex) -> (Complex, Complex) {
let b1 = self.m[0][0].mul(&a1).add(&self.m[0][1].mul(&a2));
let b2 = self.m[1][0].mul(&a1).add(&self.m[1][1].mul(&a2));
(b1, b2)
}
pub fn s_matrix(&self) -> [[Complex; 2]; 2] {
self.m
}
pub fn through_power(&self) -> f64 {
let (b1, _) = self.apply(Complex::new(1.0, 0.0), Complex::new(0.0, 0.0));
b1.abs2()
}
pub fn cross_power(&self) -> f64 {
let (_, b2) = self.apply(Complex::new(1.0, 0.0), Complex::new(0.0, 0.0));
b2.abs2()
}
}
#[derive(Clone, Debug)]
pub struct DirectionalCoupler {
pub coupling_ratio: f64,
pub excess_loss_db: f64,
pub phase_imbalance_rad: f64,
}
impl DirectionalCoupler {
pub fn new(coupling_ratio: f64) -> Self {
Self {
coupling_ratio: coupling_ratio.clamp(0.0, 1.0),
excess_loss_db: 0.0,
phase_imbalance_rad: 0.0,
}
}
pub fn new_50_50() -> Self {
Self::new(0.5)
}
pub fn coupling_angle_rad(&self) -> f64 {
self.coupling_ratio.sqrt().asin()
}
pub fn through_transmission(&self) -> f64 {
1.0 - self.coupling_ratio
}
pub fn cross_transmission(&self) -> f64 {
self.coupling_ratio
}
pub fn transfer_matrix(&self) -> TransferMatrix2x2 {
let theta = self.coupling_angle_rad();
let loss_amp = 10.0_f64.powf(-self.excess_loss_db / 20.0);
let cos_t = Complex::new(theta.cos() * loss_amp, 0.0);
let i_sin_t = Complex::new(0.0, theta.sin() * loss_amp);
let phi = self.phase_imbalance_rad;
let phase = Complex::from_polar(1.0, phi);
let i_sin_phased = i_sin_t.mul(&phase);
TransferMatrix2x2 {
m: [[cos_t, i_sin_phased], [i_sin_phased, cos_t]],
}
}
}
#[derive(Clone, Debug)]
pub struct MicroringResonator {
pub radius_m: f64,
pub coupling_through: f64,
pub coupling_drop: f64,
pub round_trip_loss: f64,
pub effective_index: f64,
pub wavelength_m: f64,
}
impl MicroringResonator {
pub fn new(radius_m: f64, n_eff: f64, wavelength_m: f64) -> Self {
Self {
radius_m,
coupling_through: 0.1,
coupling_drop: 0.1,
round_trip_loss: 0.99,
effective_index: n_eff,
wavelength_m,
}
}
pub fn round_trip_phase(&self) -> f64 {
2.0 * PI * self.effective_index * 2.0 * PI * self.radius_m / self.wavelength_m
}
pub fn fsr_nm(&self, n_group: f64) -> f64 {
self.wavelength_m * self.wavelength_m / (n_group * 2.0 * PI * self.radius_m) * 1e9
}
pub fn loaded_q(&self) -> f64 {
let t1 = (1.0 - self.coupling_through).sqrt();
let t2 = (1.0 - self.coupling_drop).sqrt();
let a = self.round_trip_loss;
let round_trip = a * t1 * t2;
if round_trip >= 1.0 {
return f64::INFINITY;
}
let l_rt = 2.0 * PI * self.radius_m;
PI * round_trip.sqrt() / (1.0 - round_trip) * self.effective_index * l_rt
/ self.wavelength_m
}
pub fn through_transmission(&self, detuning_rad: f64) -> f64 {
let t1 = (1.0 - self.coupling_through).sqrt();
let t2 = (1.0 - self.coupling_drop).sqrt();
let a = self.round_trip_loss;
let phi = self.round_trip_phase() + detuning_rad;
let exp_phi = Complex::from_polar(1.0, phi);
let num = Complex::new(t1, 0.0).sub(&exp_phi.scale(a * t2));
let den = Complex::new(1.0, 0.0).sub(&exp_phi.scale(a * t1 * t2));
num.abs2() / den.abs2().max(1e-30)
}
pub fn drop_transmission(&self, detuning_rad: f64) -> f64 {
let t1 = (1.0 - self.coupling_through).sqrt();
let t2 = (1.0 - self.coupling_drop).sqrt();
let a = self.round_trip_loss;
let phi = self.round_trip_phase() + detuning_rad;
let exp_phi = Complex::from_polar(1.0, phi);
let den = Complex::new(1.0, 0.0).sub(&exp_phi.scale(a * t1 * t2));
(self.coupling_through * self.coupling_drop * a) / den.abs2().max(1e-30)
}
pub fn extinction_ratio_db(&self) -> f64 {
let t_on = self.through_transmission(0.0);
let t_off = self.through_transmission(PI);
if t_on < 1e-20 {
return 60.0; }
10.0 * (t_off / t_on.max(1e-20)).log10()
}
pub fn finesse(&self) -> f64 {
let t1 = (1.0 - self.coupling_through).sqrt();
let t2 = (1.0 - self.coupling_drop).sqrt();
let a = self.round_trip_loss;
let denom = 1.0 - a * t1 * t2;
if denom <= 0.0 {
return f64::INFINITY;
}
PI / denom
}
}
#[derive(Clone, Debug)]
pub struct MachZehnderInterferometer {
pub coupler1: DirectionalCoupler,
pub coupler2: DirectionalCoupler,
pub delta_phi: f64,
pub delta_length_m: f64,
pub n_eff: f64,
pub wavelength_m: f64,
}
impl MachZehnderInterferometer {
pub fn symmetric() -> Self {
Self {
coupler1: DirectionalCoupler::new_50_50(),
coupler2: DirectionalCoupler::new_50_50(),
delta_phi: 0.0,
delta_length_m: 0.0,
n_eff: 2.4,
wavelength_m: 1550e-9,
}
}
pub fn phase_from_length_difference(&self) -> f64 {
2.0 * PI * self.n_eff * self.delta_length_m / self.wavelength_m
}
pub fn total_phase(&self) -> f64 {
self.phase_from_length_difference() + self.delta_phi
}
pub fn through_transmission(&self) -> f64 {
(self.total_phase() / 2.0).cos().powi(2)
}
pub fn cross_transmission(&self) -> f64 {
(self.total_phase() / 2.0).sin().powi(2)
}
pub fn transfer_matrix(&self) -> TransferMatrix2x2 {
let m1 = self.coupler1.transfer_matrix();
let phi = self.total_phase();
let phase_m = TransferMatrix2x2 {
m: [
[Complex::from_polar(1.0, phi), Complex::new(0.0, 0.0)],
[Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)],
],
};
let m2 = self.coupler2.transfer_matrix();
let m_mid = phase_m.cascade(&m1);
m2.cascade(&m_mid)
}
pub fn eo_modulator_response(&self, voltage_v: f64, v_pi: f64) -> f64 {
self.total_phase() + PI * voltage_v / v_pi
}
pub fn extinction_ratio_db(&self) -> f64 {
let kappa1 = self.coupler1.coupling_ratio;
let kappa2 = self.coupler2.coupling_ratio;
let t1 = (1.0 - kappa1).sqrt();
let c1 = kappa1.sqrt();
let t2 = (1.0 - kappa2).sqrt();
let c2 = kappa2.sqrt();
let t_max = (t1 * t2 + c1 * c2).powi(2);
let t_min = (t1 * t2 - c1 * c2).powi(2);
if t_min < 1e-30 {
return 60.0; }
10.0 * (t_max / t_min).log10()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn complex_polar_roundtrip() {
let z = Complex::from_polar(2.0, 1.0);
assert!((z.abs() - 2.0).abs() < 1e-12, "abs={}", z.abs());
}
#[test]
fn complex_mul_conj_is_abs2() {
let z = Complex::new(3.0, 4.0);
let prod = z.mul(&z.conj());
assert!((prod.0 - z.abs2()).abs() < 1e-12);
assert!(prod.1.abs() < 1e-12);
}
#[test]
fn directional_coupler_unitary() {
let dc = DirectionalCoupler::new(0.3);
let m = dc.transfer_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 not conserved: {}",
total
);
}
#[test]
fn directional_coupler_50_50_splits_equally() {
let dc = DirectionalCoupler::new_50_50();
let m = dc.transfer_matrix();
let (b1, b2) = m.apply(Complex::new(1.0, 0.0), Complex::new(0.0, 0.0));
assert!((b1.abs2() - 0.5).abs() < 1e-10, "through={}", b1.abs2());
assert!((b2.abs2() - 0.5).abs() < 1e-10, "cross={}", b2.abs2());
}
#[test]
fn ring_fsr_reasonable() {
let ring = MicroringResonator::new(5e-6, 2.4, 1550e-9);
let fsr = ring.fsr_nm(4.0);
assert!(fsr > 10.0 && fsr < 30.0, "FSR={} nm", fsr);
}
#[test]
fn ring_drop_at_resonance() {
let mut ring = MicroringResonator::new(5e-6, 2.4, 1550e-9);
ring.coupling_through = 0.2;
ring.coupling_drop = 0.2;
ring.round_trip_loss = 0.98;
let phi_rt = ring.round_trip_phase();
let t_drop = ring.drop_transmission(-phi_rt);
assert!(t_drop > 0.0 && t_drop <= 1.0, "T_drop={}", t_drop);
}
#[test]
fn mzi_on_state_full_transmission() {
let mzi = MachZehnderInterferometer::symmetric();
assert!(
(mzi.through_transmission() - 1.0).abs() < 1e-10,
"T={}",
mzi.through_transmission()
);
}
#[test]
fn mzi_off_state_zero_transmission() {
let mut mzi = MachZehnderInterferometer::symmetric();
mzi.delta_phi = PI;
assert!(
mzi.through_transmission() < 0.01,
"T={}",
mzi.through_transmission()
);
}
#[test]
fn transfer_matrix_cascade_identity() {
let id = TransferMatrix2x2::identity();
let dc = DirectionalCoupler::new(0.3).transfer_matrix();
let result = dc.cascade(&id);
let (b1_dc, b2_dc) = dc.apply(Complex::new(1.0, 0.0), Complex::new(0.0, 0.0));
let (b1_r, b2_r) = result.apply(Complex::new(1.0, 0.0), Complex::new(0.0, 0.0));
assert!((b1_dc.abs2() - b1_r.abs2()).abs() < 1e-12);
assert!((b2_dc.abs2() - b2_r.abs2()).abs() < 1e-12);
}
}