use std::f64::consts::PI;
use crate::units::conversion::{EPSILON_0, HBAR, SPEED_OF_LIGHT};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SlabLattice {
Square { period: f64 },
Hexagonal { period: f64 },
Honeycomb { period: f64 },
}
impl SlabLattice {
pub fn period(&self) -> f64 {
match self {
Self::Square { period } => *period,
Self::Hexagonal { period } => *period,
Self::Honeycomb { period } => *period,
}
}
pub fn unit_cell_area(&self) -> f64 {
let a = self.period();
match self {
Self::Square { .. } => a * a,
Self::Hexagonal { .. } => 3_f64.sqrt() / 2.0 * a * a,
Self::Honeycomb { .. } => 3_f64.sqrt() / 2.0 * a * a,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HoleShape {
Circular,
Elliptical { rx: f64, ry: f64 },
Square { side: f64 },
}
impl HoleShape {
pub fn area(&self, default_radius: f64) -> f64 {
match self {
Self::Circular => PI * default_radius * default_radius,
Self::Elliptical { rx, ry } => PI * rx * ry,
Self::Square { side } => side * side,
}
}
}
#[derive(Debug, Clone)]
pub struct PhCSlabStructure {
pub lattice: SlabLattice,
pub slab_thickness: f64,
pub n_slab: f64,
pub n_substrate: f64,
pub n_cladding: f64,
pub hole_radius: f64,
pub hole_shape: HoleShape,
}
impl PhCSlabStructure {
pub fn new_square(period: f64, hole_radius: f64, slab_thickness: f64, n_slab: f64) -> Self {
Self {
lattice: SlabLattice::Square { period },
slab_thickness,
n_slab,
n_substrate: 1.0,
n_cladding: 1.0,
hole_radius,
hole_shape: HoleShape::Circular,
}
}
pub fn new_hexagonal(period: f64, hole_radius: f64, slab_thickness: f64, n_slab: f64) -> Self {
Self {
lattice: SlabLattice::Hexagonal { period },
slab_thickness,
n_slab,
n_substrate: 1.0,
n_cladding: 1.0,
hole_radius,
hole_shape: HoleShape::Circular,
}
}
pub fn fill_fraction(&self) -> f64 {
let a_hole = self.hole_shape.area(self.hole_radius);
let a_cell = self.lattice.unit_cell_area();
(a_hole / a_cell).clamp(0.0, 0.9)
}
pub fn effective_index(&self) -> f64 {
let eta = self.fill_fraction();
let eps_slab = self.n_slab * self.n_slab;
let eps_air = 1.0; let eps_eff = eta * eps_air + (1.0 - eta) * eps_slab;
eps_eff.sqrt()
}
fn confinement_factor(&self) -> f64 {
let a = self.lattice.period();
let f_norm = self.band_gap_center_raw();
if f_norm < 1e-12 {
return 0.9;
}
let lambda = a / f_norm;
let n_c = self.n_cladding;
let na_sq = self.n_slab * self.n_slab - n_c * n_c;
if na_sq <= 0.0 {
return 0.5;
}
let v = PI * self.slab_thickness / lambda * na_sq.sqrt();
(1.0 - (-v * v).exp()).clamp(0.1, 1.0)
}
fn band_gap_center_raw(&self) -> f64 {
let n_ref = 3.476_f64;
let f_centre_ref = match self.lattice {
SlabLattice::Square { .. } => 0.35, SlabLattice::Hexagonal { .. } => 0.305,
SlabLattice::Honeycomb { .. } => 0.25, };
let n_eff = self.effective_index();
f_centre_ref * n_ref / n_eff.max(1.0)
}
pub fn band_gap_center(&self) -> f64 {
self.band_gap_center_raw()
}
pub fn band_gap_width(&self) -> f64 {
let delta_ref = match self.lattice {
SlabLattice::Square { .. } => 0.04,
SlabLattice::Hexagonal { .. } => 0.06,
SlabLattice::Honeycomb { .. } => 0.03,
};
let n_ref = 3.476_f64;
let index_scale = ((self.n_slab - 1.0) / (n_ref - 1.0)).clamp(0.0, 2.0);
let gamma = self.confinement_factor();
(delta_ref * index_scale * gamma).max(0.0)
}
pub fn guided_mode_cutoff(&self) -> f64 {
let a = self.lattice.period();
let d = self.slab_thickness;
let n_c = self.n_cladding;
let na_sq = self.n_slab * self.n_slab - n_c * n_c;
if na_sq <= 0.0 || d < 1e-20 {
return 0.0;
}
a / (2.0 * d * na_sq.sqrt())
}
pub fn quality_factor_estimate(&self, mode_volume_cubic_lambda: f64) -> f64 {
let f0 = self.band_gap_center();
let df = self.band_gap_width();
if f0 < 1e-12 || df < 1e-12 {
return 0.0;
}
let gap_ratio = df / f0;
let v = mode_volume_cubic_lambda.max(0.1);
gap_ratio * gap_ratio * self.n_slab.powi(3) / v * 1e4
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum DefectType {
MissingHole,
ShiftedHole { shift_x: f64, shift_y: f64 },
ModifiedRadius { radius: f64 },
H1,
H3,
L3,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CavityPolarization {
TE,
TM,
Hybrid,
}
#[derive(Debug, Clone)]
pub struct CavityMode {
pub resonant_freq_normalized: f64,
pub quality_factor: f64,
pub mode_volume_cubic_lambda: f64,
pub polarization: CavityPolarization,
}
#[derive(Debug, Clone)]
pub struct PointDefectCavity {
pub base_slab: PhCSlabStructure,
pub defect_type: DefectType,
pub cavity_mode: CavityMode,
}
impl PointDefectCavity {
pub fn new_l3(slab: PhCSlabStructure) -> Self {
let f0 = slab.band_gap_center() * 1.02; let mode = CavityMode {
resonant_freq_normalized: f0,
quality_factor: 1e5,
mode_volume_cubic_lambda: 0.7,
polarization: CavityPolarization::TE,
};
Self {
base_slab: slab,
defect_type: DefectType::L3,
cavity_mode: mode,
}
}
pub fn new_h1(slab: PhCSlabStructure) -> Self {
let f0 = slab.band_gap_center() * 0.95; let mode = CavityMode {
resonant_freq_normalized: f0,
quality_factor: 500.0,
mode_volume_cubic_lambda: 1.2,
polarization: CavityPolarization::TE,
};
Self {
base_slab: slab,
defect_type: DefectType::H1,
cavity_mode: mode,
}
}
pub fn purcell_factor(&self) -> f64 {
let q = self.cavity_mode.quality_factor;
let v = self.cavity_mode.mode_volume_cubic_lambda.max(1e-6);
(3.0 / (4.0 * PI * PI)) * q / v
}
pub fn coupling_efficiency(&self, beta_factor: f64) -> f64 {
let fp = self.purcell_factor();
let b = beta_factor.clamp(0.0, 1.0);
let num = b * fp;
num / (num + 1.0)
}
pub fn zero_point_field(&self, wavelength: f64) -> f64 {
let n = self.base_slab.n_slab;
let a = self.base_slab.lattice.period();
let f_norm = self.cavity_mode.resonant_freq_normalized;
let lambda_res = if f_norm > 1e-12 {
a / f_norm
} else {
wavelength
};
let omega = 2.0 * PI * SPEED_OF_LIGHT / lambda_res;
let v_cubic_lambda = self.cavity_mode.mode_volume_cubic_lambda;
let lambda_over_n = lambda_res / n;
let v_eff = v_cubic_lambda * lambda_over_n.powi(3);
let eps_eff = EPSILON_0 * n * n;
(HBAR * omega / (2.0 * eps_eff * v_eff)).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct W1Waveguide {
pub slab: PhCSlabStructure,
pub length: f64,
pub slow_light_region: (f64, f64),
}
impl W1Waveguide {
pub fn new(slab: PhCSlabStructure, length: f64) -> Self {
let f_lower = Self::lower_band_edge_norm(&slab);
let bw = Self::guided_bandwidth_norm(&slab);
let slow_hi = f_lower + 0.20 * bw;
Self {
slab,
length,
slow_light_region: (f_lower, slow_hi),
}
}
fn lower_band_edge_norm(slab: &PhCSlabStructure) -> f64 {
let f0 = slab.band_gap_center();
f0 * 0.94 }
fn guided_bandwidth_norm(slab: &PhCSlabStructure) -> f64 {
let f0 = slab.band_gap_center();
0.12 * f0
}
pub fn group_index(&self, freq_normalized: f64) -> f64 {
let f_lower = Self::lower_band_edge_norm(&self.slab);
let bw = Self::guided_bandwidth_norm(&self.slab);
let df_slow = 0.5 * bw;
let delta = freq_normalized - f_lower;
if delta <= 0.0 {
return 200.0; }
let x = (delta / df_slow).clamp(1e-4, 1.0);
(5.0 / x.sqrt()).clamp(5.0, 200.0)
}
pub fn slow_light_bandwidth(&self) -> f64 {
self.slow_light_region.1 - self.slow_light_region.0
}
pub fn propagation_loss_db_per_cm(&self, surface_roughness_nm: f64) -> f64 {
let f_mid = (self.slow_light_region.0 + self.slow_light_region.1) / 2.0;
let ng = self.group_index(f_mid);
let ng0 = 10.0_f64;
let sigma0 = 1.0_f64; let sigma = surface_roughness_nm.max(0.0);
2.0 * (ng / ng0).powi(2) * (sigma / sigma0).powi(2)
}
pub fn transmission_db(&self) -> f64 {
let loss_per_cm = self.propagation_loss_db_per_cm(2.0);
let length_cm = self.length * 100.0; -loss_per_cm * length_cm
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
fn si_hex_slab() -> PhCSlabStructure {
PhCSlabStructure::new_hexagonal(420e-9, 126e-9, 220e-9, 3.476)
}
#[test]
fn fill_fraction_hex_typical() {
let slab = si_hex_slab();
let ff = slab.fill_fraction();
assert!(ff > 0.25 && ff < 0.45, "fill fraction = {ff:.4}");
}
#[test]
fn effective_index_between_air_and_slab() {
let slab = si_hex_slab();
let n_eff = slab.effective_index();
assert!(n_eff > 1.0 && n_eff < slab.n_slab, "n_eff = {n_eff:.3}");
}
#[test]
fn band_gap_center_positive() {
let slab = si_hex_slab();
assert!(slab.band_gap_center() > 0.0);
}
#[test]
fn band_gap_width_nonnegative() {
let slab = si_hex_slab();
assert!(slab.band_gap_width() >= 0.0);
}
#[test]
fn guided_mode_cutoff_positive() {
let slab = si_hex_slab();
assert!(slab.guided_mode_cutoff() > 0.0);
}
#[test]
fn square_lattice_gap_centre_higher_than_hex() {
let hex = PhCSlabStructure::new_hexagonal(420e-9, 126e-9, 220e-9, 3.476);
let sq = PhCSlabStructure::new_square(420e-9, 126e-9, 220e-9, 3.476);
assert!(sq.band_gap_center() > hex.band_gap_center());
}
#[test]
fn quality_factor_estimate_positive() {
let slab = si_hex_slab();
let q = slab.quality_factor_estimate(0.7);
assert!(q > 0.0, "Q = {q}");
}
#[test]
fn fill_fraction_elliptical_hole() {
let mut slab = si_hex_slab();
slab.hole_shape = HoleShape::Elliptical {
rx: 130e-9,
ry: 100e-9,
};
let ff = slab.fill_fraction();
assert!(ff > 0.0 && ff < 1.0, "fill fraction = {ff}");
}
#[test]
fn l3_purcell_factor_large() {
let slab = si_hex_slab();
let cav = PointDefectCavity::new_l3(slab);
let fp = cav.purcell_factor();
assert!(fp > 100.0, "F_P = {fp:.1}");
}
#[test]
fn h1_purcell_factor_positive() {
let slab = si_hex_slab();
let cav = PointDefectCavity::new_h1(slab);
assert!(cav.purcell_factor() > 0.0);
}
#[test]
fn coupling_efficiency_between_zero_and_one() {
let slab = si_hex_slab();
let cav = PointDefectCavity::new_l3(slab);
let eta = cav.coupling_efficiency(0.9);
assert!((0.0..=1.0).contains(&eta), "η = {eta:.4}");
}
#[test]
fn zero_point_field_positive() {
let slab = si_hex_slab();
let cav = PointDefectCavity::new_l3(slab);
let e_zpf = cav.zero_point_field(1550e-9);
assert!(e_zpf > 0.0, "E_zpf = {e_zpf}");
}
#[test]
fn l3_resonance_above_gap_centre() {
let slab = si_hex_slab();
let f0 = slab.band_gap_center();
let cav = PointDefectCavity::new_l3(slab);
assert!(cav.cavity_mode.resonant_freq_normalized > f0 * 0.9);
}
#[test]
fn w1_group_index_increases_near_band_edge() {
let slab = si_hex_slab();
let w1 = W1Waveguide::new(slab, 100e-6);
let f_lower = w1.slow_light_region.0;
let ng_near = w1.group_index(f_lower + 0.002);
let ng_far = w1.group_index(f_lower + 0.06);
assert!(ng_near > ng_far, "ng_near={ng_near:.1} ng_far={ng_far:.1}");
}
#[test]
fn w1_group_index_below_cutoff_large() {
let slab = si_hex_slab();
let w1 = W1Waveguide::new(slab, 100e-6);
let ng = w1.group_index(0.05);
assert!(ng >= 100.0, "ng below cutoff = {ng}");
}
#[test]
fn w1_slow_light_bandwidth_positive() {
let slab = si_hex_slab();
let w1 = W1Waveguide::new(slab, 100e-6);
assert!(w1.slow_light_bandwidth() > 0.0);
}
#[test]
fn w1_transmission_db_nonpositive() {
let slab = si_hex_slab();
let w1 = W1Waveguide::new(slab, 500e-6); assert!(w1.transmission_db() <= 0.0);
}
#[test]
fn w1_propagation_loss_scales_with_roughness() {
let slab = si_hex_slab();
let w1 = W1Waveguide::new(slab, 100e-6);
let loss1 = w1.propagation_loss_db_per_cm(1.0);
let loss2 = w1.propagation_loss_db_per_cm(2.0);
assert_abs_diff_eq!(loss2 / loss1, 4.0, epsilon = 0.1);
}
}