use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct EoCrystal {
pub n: f64,
pub r_eff: f64,
pub name: &'static str,
}
impl EoCrystal {
pub fn lithium_niobate() -> Self {
Self {
n: 2.17,
r_eff: 30.8e-12,
name: "LiNbO3",
}
}
pub fn ktp() -> Self {
Self {
n: 1.74,
r_eff: 35.0e-12,
name: "KTP",
}
}
pub fn bbo() -> Self {
Self {
n: 1.67,
r_eff: 2.2e-12,
name: "BBO",
}
}
pub fn gaas() -> Self {
Self {
n: 3.37,
r_eff: 1.5e-12,
name: "GaAs",
}
}
pub fn delta_n(&self, e_field: f64) -> f64 {
-0.5 * self.n.powi(3) * self.r_eff * e_field
}
}
#[derive(Debug, Clone)]
pub struct LongitudinalPockelsCell {
pub crystal: EoCrystal,
pub wavelength: f64,
}
impl LongitudinalPockelsCell {
pub fn new(crystal: EoCrystal, wavelength: f64) -> Self {
Self {
crystal,
wavelength,
}
}
pub fn v_pi(&self) -> f64 {
self.wavelength / (self.crystal.n.powi(3) * self.crystal.r_eff)
}
pub fn phase_retardation(&self, voltage: f64) -> f64 {
PI * voltage / self.v_pi()
}
pub fn transmission(&self, voltage: f64) -> f64 {
(self.phase_retardation(voltage) / 2.0).sin().powi(2)
}
}
#[derive(Debug, Clone)]
pub struct TransversePockelsCell {
pub crystal: EoCrystal,
pub wavelength: f64,
pub gap: f64,
pub length: f64,
}
impl TransversePockelsCell {
pub fn new(crystal: EoCrystal, wavelength: f64, gap: f64, length: f64) -> Self {
Self {
crystal,
wavelength,
gap,
length,
}
}
pub fn v_pi(&self) -> f64 {
self.wavelength * self.gap / (self.crystal.n.powi(3) * self.crystal.r_eff * self.length)
}
pub fn phase_shift(&self, voltage: f64) -> f64 {
PI * voltage / self.v_pi()
}
pub fn e_field(&self, voltage: f64) -> f64 {
voltage / self.gap
}
pub fn delta_n(&self, voltage: f64) -> f64 {
self.crystal.delta_n(self.e_field(voltage))
}
}
#[derive(Debug, Clone)]
pub struct EoModulatorBandwidth {
pub resistance: f64,
pub electrode_area: f64,
pub gap: f64,
pub eps_r_rf: f64,
}
impl EoModulatorBandwidth {
const EPS0: f64 = 8.854_187_817e-12;
pub fn linbo3_travelling_wave() -> Self {
Self {
resistance: 50.0,
electrode_area: 1e-2 * 10e-6,
gap: 10e-6,
eps_r_rf: 28.0,
}
}
pub fn capacitance(&self) -> f64 {
Self::EPS0 * self.eps_r_rf * self.electrode_area / self.gap
}
pub fn bandwidth_hz(&self) -> f64 {
1.0 / (2.0 * PI * self.resistance * self.capacitance())
}
pub fn vpi_l_product(crystal: &EoCrystal, wavelength: f64, gap: f64) -> f64 {
wavelength * gap / (crystal.n.powi(3) * crystal.r_eff) * 1e2 }
}
#[derive(Debug, Clone, Copy)]
pub struct SiPlasmaDispersion {
pub delta_n_carriers: f64,
pub delta_p_carriers: f64,
pub length: f64,
}
impl SiPlasmaDispersion {
pub fn phase_shift(&self) -> f64 {
let wavelength = 1550e-9;
let dn = self.delta_n_1550();
2.0 * PI * dn * self.length / wavelength
}
pub fn delta_n_1550(&self) -> f64 {
let dn_m3 = self.delta_n_carriers * 1e-6; let dp_m3 = self.delta_p_carriers * 1e-6;
-(8.8e-22 * dn_m3 + 8.5e-18 * dp_m3.powf(0.8))
}
pub fn delta_alpha_1550(&self) -> f64 {
let dn_cm = self.delta_n_carriers * 1e-6;
let dp_cm = self.delta_p_carriers * 1e-6;
(8.5e-18 * dn_cm + 6.0e-18 * dp_cm) * 100.0 }
pub fn vpi_l_pm(&self) -> f64 {
let dphase = self.phase_shift().abs();
if dphase > 0.0 {
PI * self.length / dphase } else {
f64::INFINITY
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linbo3_vpi_reasonable() {
let cell = LongitudinalPockelsCell::new(EoCrystal::lithium_niobate(), 1550e-9);
let vpi = cell.v_pi();
assert!(vpi > 1000.0 && vpi < 20000.0, "V_pi={vpi}");
}
#[test]
fn transverse_vpi_lower() {
let crystal = EoCrystal::lithium_niobate();
let cell = TransversePockelsCell::new(crystal, 1550e-9, 10e-6, 1e-2);
let vpi = cell.v_pi();
assert!(vpi > 0.0 && vpi < 50.0, "V_pi transverse={vpi}");
}
#[test]
fn transmission_at_vpi_is_one() {
let cell = LongitudinalPockelsCell::new(EoCrystal::lithium_niobate(), 1550e-9);
let vpi = cell.v_pi();
let t = cell.transmission(vpi);
assert!((t - 1.0).abs() < 1e-10, "T at Vpi={t}");
}
#[test]
fn transmission_at_zero_is_zero() {
let cell = LongitudinalPockelsCell::new(EoCrystal::lithium_niobate(), 1550e-9);
let t = cell.transmission(0.0);
assert!(t < 1e-10, "T at 0={t}");
}
#[test]
fn eo_bandwidth_linbo3() {
let bw = EoModulatorBandwidth::linbo3_travelling_wave();
let f3db = bw.bandwidth_hz();
assert!(f3db > 1e9, "bandwidth too low: {f3db}");
}
#[test]
fn ktp_crystal_vpi_different_from_linbo3() {
let ln = LongitudinalPockelsCell::new(EoCrystal::lithium_niobate(), 1550e-9);
let ktp = LongitudinalPockelsCell::new(EoCrystal::ktp(), 1550e-9);
assert!((ln.v_pi() - ktp.v_pi()).abs() > 100.0);
}
#[test]
fn si_plasma_dispersion_phase_shift() {
let ps = SiPlasmaDispersion {
delta_n_carriers: 1e23, delta_p_carriers: 1e23,
length: 1e-3, };
let dphase = ps.phase_shift();
assert!(dphase.abs() > 0.0);
}
#[test]
fn delta_n_negative_for_free_carriers() {
let ps = SiPlasmaDispersion {
delta_n_carriers: 1e23,
delta_p_carriers: 1e23,
length: 1e-3,
};
assert!(ps.delta_n_1550() < 0.0);
}
#[test]
fn vpi_l_product_linbo3() {
let crystal = EoCrystal::lithium_niobate();
let vpi_l = EoModulatorBandwidth::vpi_l_product(&crystal, 1550e-9, 10e-6);
assert!(vpi_l > 0.0 && vpi_l < 100.0, "V_pi*L = {vpi_l} V·cm");
}
}