use std::f64::consts::PI;
const C0: f64 = 2.997_924_58e8;
#[derive(Debug, Clone)]
pub struct SoiProcess {
pub silicon_thickness_nm: f64,
pub oxide_thickness_nm: f64,
pub n_si: f64,
pub n_sio2: f64,
pub min_feature_size_nm: f64,
pub waveguide_loss_db_per_cm: f64,
}
impl SoiProcess {
pub fn standard_220nm() -> Self {
Self {
silicon_thickness_nm: 220.0,
oxide_thickness_nm: 2000.0,
n_si: 3.4757,
n_sio2: 1.4440,
min_feature_size_nm: 100.0,
waveguide_loss_db_per_cm: 2.0,
}
}
pub fn thin_300nm() -> Self {
Self {
silicon_thickness_nm: 300.0,
oxide_thickness_nm: 3000.0,
n_si: 3.4757,
n_sio2: 1.4440,
min_feature_size_nm: 120.0,
waveguide_loss_db_per_cm: 1.2,
}
}
pub fn n_eff_strip(&self, width_nm: f64) -> f64 {
let w = width_nm.clamp(300.0, 1200.0);
let h_norm = self.silicon_thickness_nm / 220.0;
let a = 2.45;
let b = 0.55;
let c = 0.25;
let w_norm = (w - 450.0) / 450.0;
let h_fac = h_norm - 1.0;
(a + b * w_norm + c * h_fac).clamp(self.n_sio2 + 0.01, self.n_si - 0.01)
}
pub fn n_eff_rib(&self, width_nm: f64, etch_nm: f64) -> f64 {
let w = width_nm.clamp(400.0, 2000.0);
let etch_frac = (etch_nm / self.silicon_thickness_nm).clamp(0.0, 1.0);
let n_strip = self.n_eff_strip(w);
let n_slab = self.n_sio2 + (n_strip - self.n_sio2) * 0.6 * (1.0 - etch_frac);
n_slab + (n_strip - n_slab) * etch_frac.powi(2)
}
pub fn single_mode_width_range(&self) -> (f64, f64) {
match self.silicon_thickness_nm as u32 {
0..=249 => (300.0, 500.0),
250..=269 => (320.0, 550.0),
270..=320 => (340.0, 600.0),
_ => (350.0, 650.0),
}
}
pub fn propagation_loss_db_per_cm(&self, width_nm: f64) -> f64 {
let w_ref = 450.0_f64;
let w = width_nm.max(200.0);
self.waveguide_loss_db_per_cm * (w_ref / w).powi(3)
}
}
#[derive(Debug, Clone)]
pub struct SiNProcess {
pub n_sin: f64,
pub thickness_nm: f64,
pub stress_mpa: f64,
pub waveguide_loss_db_per_cm: f64,
pub wavelength_range: (f64, f64),
}
impl SiNProcess {
pub fn standard_400nm() -> Self {
Self {
n_sin: 1.9963,
thickness_nm: 400.0,
stress_mpa: 900.0,
waveguide_loss_db_per_cm: 0.15,
wavelength_range: (0.4e-6, 2.35e-6),
}
}
pub fn low_loss_700nm() -> Self {
Self {
n_sin: 1.9870,
thickness_nm: 700.0,
stress_mpa: 200.0,
waveguide_loss_db_per_cm: 0.05,
wavelength_range: (0.5e-6, 2.35e-6),
}
}
pub fn n_eff_strip(&self, width_nm: f64) -> f64 {
let w = width_nm.clamp(500.0, 3000.0);
let h_norm = self.thickness_nm / 400.0;
let a = 1.70;
let b = 0.22;
let c = 0.10;
let w_norm = (w - 1000.0) / 1000.0;
let h_fac = h_norm - 1.0;
(a + b * w_norm + c * h_fac).clamp(1.444 + 0.01, self.n_sin - 0.01)
}
pub fn anomalous_dispersion_width_nm(&self) -> f64 {
1750.0 * (self.thickness_nm / 700.0).powf(0.8)
}
}
#[derive(Debug, Clone)]
pub enum PicProcess {
Soi220(SoiProcess),
Sin400(SiNProcess),
Sin700(SiNProcess),
InP,
LiNbO3,
}
impl PicProcess {
pub fn core_index(&self) -> f64 {
match self {
Self::Soi220(p) => p.n_si,
Self::Sin400(p) | Self::Sin700(p) => p.n_sin,
Self::InP => 3.17,
Self::LiNbO3 => 2.21,
}
}
pub fn clad_index(&self) -> f64 {
match self {
Self::Soi220(_) => 1.4440,
Self::Sin400(_) | Self::Sin700(_) => 1.4440,
Self::InP => 3.17, Self::LiNbO3 => 1.444,
}
}
pub fn baseline_loss_db_per_cm(&self) -> f64 {
match self {
Self::Soi220(p) => p.waveguide_loss_db_per_cm,
Self::Sin400(p) | Self::Sin700(p) => p.waveguide_loss_db_per_cm,
Self::InP => 3.0,
Self::LiNbO3 => 0.3,
}
}
}
#[derive(Debug, Clone)]
pub struct MmiSpec {
pub length_um: f64,
pub width_um: f64,
pub insertion_loss_db: f64,
pub imbalance_db: f64,
pub bandwidth_nm: f64,
}
#[derive(Debug, Clone)]
pub struct DcSpec {
pub coupling_length_um: f64,
pub coupling_coefficient: f64,
pub extinction_ratio_db: f64,
pub bandwidth_nm: f64,
}
#[derive(Debug, Clone)]
pub struct YJunctionSpec {
pub length_um: f64,
pub insertion_loss_db: f64,
pub imbalance_db: f64,
}
#[derive(Debug, Clone)]
pub struct GcSpec {
pub period_nm: f64,
pub duty_cycle: f64,
pub coupling_efficiency_db: f64,
pub bandwidth_nm: f64,
pub angle_deg: f64,
}
#[derive(Debug, Clone)]
pub struct RingSpec {
pub radius_um: f64,
pub gap_nm: f64,
pub q_factor: f64,
pub fsr_nm: f64,
}
#[derive(Debug, Clone)]
pub struct PicComponentLibrary {
pub process: PicProcess,
pub wavelength: f64,
}
impl PicComponentLibrary {
pub fn new_soi(wavelength: f64) -> Self {
Self {
process: PicProcess::Soi220(SoiProcess::standard_220nm()),
wavelength,
}
}
pub fn new_sin(wavelength: f64) -> Self {
Self {
process: PicProcess::Sin400(SiNProcess::standard_400nm()),
wavelength,
}
}
fn lambda_nm(&self) -> f64 {
self.wavelength * 1.0e9
}
pub fn mmi_1x2(&self) -> MmiSpec {
let lambda_nm = self.lambda_nm();
let n_r = self.process.core_index();
let w_um = match &self.process {
PicProcess::Soi220(_) => 5.6,
PicProcess::Sin400(_) | PicProcess::Sin700(_) => 8.0,
_ => 6.0,
};
let w_nm = w_um * 1000.0;
let l_pi_nm = n_r * w_nm * w_nm / lambda_nm;
let length_um = 3.0 * l_pi_nm / 4.0 / 1000.0;
MmiSpec {
length_um,
width_um: w_um,
insertion_loss_db: 0.3,
imbalance_db: 0.1,
bandwidth_nm: 80.0,
}
}
pub fn mmi_2x2(&self) -> MmiSpec {
let lambda_nm = self.lambda_nm();
let n_r = self.process.core_index();
let w_um = match &self.process {
PicProcess::Soi220(_) => 6.0,
PicProcess::Sin400(_) | PicProcess::Sin700(_) => 9.0,
_ => 7.0,
};
let w_nm = w_um * 1000.0;
let l_pi_nm = n_r * w_nm * w_nm / lambda_nm;
let length_um = l_pi_nm / 1000.0;
MmiSpec {
length_um,
width_um: w_um,
insertion_loss_db: 0.5,
imbalance_db: 0.2,
bandwidth_nm: 60.0,
}
}
pub fn directional_coupler(&self, gap_nm: f64) -> DcSpec {
let lambda_nm = self.lambda_nm();
let n_eff = match &self.process {
PicProcess::Soi220(p) => p.n_eff_strip(450.0),
PicProcess::Sin400(p) | PicProcess::Sin700(p) => p.n_eff_strip(1000.0),
_ => 2.0,
};
let g0 = match &self.process {
PicProcess::Soi220(_) => 200.0_f64,
_ => 500.0_f64,
};
let kappa = (-gap_nm / g0).exp() * 0.98;
let l_beat_um = lambda_nm / (2.0 * (n_eff * 0.02 * (-gap_nm / g0).exp())) / 1000.0;
let l_beat_um = l_beat_um.clamp(5.0, 2000.0);
let coupling_length_um = l_beat_um / 2.0;
DcSpec {
coupling_length_um,
coupling_coefficient: kappa.clamp(0.0, 1.0),
extinction_ratio_db: 20.0 - gap_nm / 50.0,
bandwidth_nm: 30.0 + gap_nm / 20.0,
}
}
pub fn y_junction(&self) -> YJunctionSpec {
let length_um = match &self.process {
PicProcess::Soi220(_) => 20.0,
PicProcess::Sin400(_) | PicProcess::Sin700(_) => 30.0,
_ => 25.0,
};
YJunctionSpec {
length_um,
insertion_loss_db: 0.5,
imbalance_db: 0.05,
}
}
pub fn grating_coupler(&self) -> GcSpec {
let lambda_nm = self.lambda_nm();
let theta_deg = 10.0_f64;
let theta_rad = theta_deg * PI / 180.0;
let n_eff = match &self.process {
PicProcess::Soi220(p) => p.n_eff_strip(450.0),
PicProcess::Sin400(p) => p.n_eff_strip(1000.0),
PicProcess::Sin700(p) => p.n_eff_strip(1000.0),
_ => 2.0,
};
let n_clad = 1.0; let period_nm = lambda_nm / (n_eff - n_clad * theta_rad.sin());
GcSpec {
period_nm,
duty_cycle: 0.50,
coupling_efficiency_db: -3.5,
bandwidth_nm: 40.0,
angle_deg: theta_deg,
}
}
pub fn ring_resonator_wg(&self) -> RingSpec {
let lambda = self.wavelength;
let n_eff = match &self.process {
PicProcess::Soi220(p) => p.n_eff_strip(450.0),
PicProcess::Sin400(p) | PicProcess::Sin700(p) => p.n_eff_strip(1000.0),
_ => 2.0,
};
let n_g = n_eff * 1.30;
let radius_um = 10.0;
let circumference_m = 2.0 * PI * radius_um * 1.0e-6;
let fsr_m = lambda * lambda / (n_g * circumference_m);
let fsr_nm = fsr_m * 1.0e9;
RingSpec {
radius_um,
gap_nm: 200.0,
q_factor: 10_000.0,
fsr_nm,
}
}
pub fn fsr_nm(&self, radius_um: f64, n_g: f64) -> f64 {
let lambda = self.wavelength;
let circumference_m = 2.0 * PI * radius_um * 1.0e-6;
lambda * lambda / (n_g * circumference_m) * 1.0e9
}
pub fn finesse_from_q(&self, q_factor: f64, fsr_nm: f64) -> f64 {
let lambda_nm = self.lambda_nm();
q_factor * fsr_nm / lambda_nm
}
}
pub fn wavelength_to_freq(wavelength_m: f64) -> f64 {
C0 / wavelength_m
}
pub fn freq_to_wavelength(freq_hz: f64) -> f64 {
C0 / freq_hz
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_soi_220_n_eff_reasonable() {
let soi = SoiProcess::standard_220nm();
let n_eff = soi.n_eff_strip(450.0);
assert!(n_eff > 2.0, "n_eff too low: {n_eff}");
assert!(n_eff < 3.5, "n_eff too high: {n_eff}");
}
#[test]
fn test_soi_single_mode_range() {
let soi = SoiProcess::standard_220nm();
let (w_min, w_max) = soi.single_mode_width_range();
assert!(w_min < w_max);
assert!(w_min >= 250.0);
assert!(w_max <= 600.0);
}
#[test]
fn test_soi_loss_increases_for_narrow_guides() {
let soi = SoiProcess::standard_220nm();
let loss_450 = soi.propagation_loss_db_per_cm(450.0);
let loss_300 = soi.propagation_loss_db_per_cm(300.0);
assert!(loss_300 > loss_450, "Narrow guide should have higher loss");
}
#[test]
fn test_sin_anomalous_dispersion_width() {
let sin = SiNProcess::low_loss_700nm();
let w = sin.anomalous_dispersion_width_nm();
assert_abs_diff_eq!(w, 1750.0, epsilon = 1.0);
}
#[test]
fn test_mmi_1x2_length_positive() {
let lib = PicComponentLibrary::new_soi(1.55e-6);
let mmi = lib.mmi_1x2();
assert!(mmi.length_um > 0.0);
assert!(mmi.width_um > 0.0);
}
#[test]
fn test_ring_fsr_soi() {
let lib = PicComponentLibrary::new_soi(1.55e-6);
let ring = lib.ring_resonator_wg();
assert!(ring.fsr_nm > 1.0, "FSR too small: {} nm", ring.fsr_nm);
assert!(ring.fsr_nm < 50.0, "FSR too large: {} nm", ring.fsr_nm);
}
#[test]
fn test_grating_coupler_period_reasonable() {
let lib = PicComponentLibrary::new_soi(1.55e-6);
let gc = lib.grating_coupler();
assert!(
gc.period_nm > 400.0 && gc.period_nm < 1000.0,
"Unexpected grating period: {} nm",
gc.period_nm
);
}
#[test]
fn test_wavelength_freq_roundtrip() {
let lambda = 1.55e-6_f64;
let f = wavelength_to_freq(lambda);
let lambda2 = freq_to_wavelength(f);
assert_abs_diff_eq!(lambda, lambda2, epsilon = 1.0e-18);
}
#[test]
fn test_finesse_from_q() {
let lib = PicComponentLibrary::new_soi(1.55e-6);
let f = lib.finesse_from_q(10_000.0, 10.0);
assert!(f > 50.0 && f < 100.0, "Finesse out of range: {f}");
}
}