use std::f64::consts::PI;
const H_PLANCK: f64 = 6.626_070_15e-34;
const H_BAR: f64 = H_PLANCK / (2.0 * PI);
const C: f64 = 2.997_924_58e8;
const E_CHARGE: f64 = 1.602_176_634e-19;
const K_B: f64 = 1.380_649e-23;
#[derive(Debug, Clone)]
pub struct InAsQd {
pub dot_height_nm: f64,
pub dot_diameter_nm: f64,
pub indium_fraction: f64,
pub strain: f64,
pub temperature_k: f64,
}
impl InAsQd {
pub fn new(height_nm: f64, diameter_nm: f64) -> Self {
Self {
dot_height_nm: height_nm,
dot_diameter_nm: diameter_nm,
indium_fraction: 1.0,
strain: -0.07, temperature_k: 4.0,
}
}
fn inas_bandgap_ev(&self) -> f64 {
let alpha = 2.76e-4_f64; let beta = 83.0_f64; let eg0 = 0.415_f64;
let t = self.temperature_k;
eg0 - alpha * t * t / (t + beta)
}
fn gaas_bandgap_ev(&self) -> f64 {
let alpha = 5.405e-4_f64;
let beta = 204.0_f64;
let eg0 = 1.519_f64;
let t = self.temperature_k;
eg0 - alpha * t * t / (t + beta)
}
fn alloy_bandgap_ev(&self) -> f64 {
let x = self.indium_fraction.clamp(0.0, 1.0);
let e_inas = self.inas_bandgap_ev();
let e_gaas = self.gaas_bandgap_ev();
let b = 0.477_f64; x * e_inas + (1.0 - x) * e_gaas - b * x * (1.0 - x)
}
fn electron_confinement_ev(&self) -> f64 {
let m_e_0 = 9.109_383_7e-31; let m_star_e = 0.023 * m_e_0;
let h_m = self.dot_height_nm * 1e-9;
let d_m = self.dot_diameter_nm * 1e-9;
let e_vert = H_BAR * H_BAR * PI * PI / (2.0 * m_star_e * h_m * h_m);
let e_lat = H_BAR * H_BAR * PI * PI / (2.0 * m_star_e * (d_m / 2.0).powi(2));
(e_vert + e_lat) / E_CHARGE }
fn heavy_hole_confinement_ev(&self) -> f64 {
let m_e_0 = 9.109_383_7e-31;
let m_star_hh_z = 0.35 * m_e_0;
let m_star_hh_xy = 0.32 * m_e_0;
let h_m = self.dot_height_nm * 1e-9;
let d_m = self.dot_diameter_nm * 1e-9;
let e_vert = H_BAR * H_BAR * PI * PI / (2.0 * m_star_hh_z * h_m * h_m);
let e_lat = H_BAR * H_BAR * PI * PI / (2.0 * m_star_hh_xy * (d_m / 2.0).powi(2));
(e_vert + e_lat) / E_CHARGE
}
fn strain_shift_conduction_ev(&self) -> f64 {
let a_c = -7.17_f64;
let eps = self.strain;
let nu = 0.352_f64;
let eps_zz = -2.0 * nu / (1.0 - nu) * eps;
a_c * (2.0 * eps + eps_zz)
}
fn strain_shift_valence_ev(&self) -> f64 {
let a_v = 1.0_f64; let b = -1.8_f64; let eps = self.strain;
let nu = 0.352_f64;
let eps_zz = -2.0 * nu / (1.0 - nu) * eps;
let p_eps = -a_v * (2.0 * eps + eps_zz);
let q_eps = -b / 2.0 * (eps - eps_zz);
-p_eps - q_eps
}
pub fn transition_energy_ev(&self) -> f64 {
let e_gap = self.alloy_bandgap_ev();
let d_ec = self.strain_shift_conduction_ev();
let d_ev = self.strain_shift_valence_ev();
let e_e = self.electron_confinement_ev();
let e_h = self.heavy_hole_confinement_ev();
let e_x_bind = self.exciton_binding_energy_mev() * 1e-3;
(e_gap + d_ec - d_ev + e_e + e_h - e_x_bind).max(0.1)
}
pub fn wavelength_nm(&self) -> f64 {
let energy_j = self.transition_energy_ev() * E_CHARGE;
if energy_j > 0.0 {
H_PLANCK * C / energy_j * 1e9
} else {
f64::INFINITY
}
}
pub fn exciton_binding_energy_mev(&self) -> f64 {
let eps_r = 12.4_f64;
let m_e = 0.023_f64; let m_hh = 0.35_f64;
let mu = m_e * m_hh / (m_e + m_hh); let a_b_star_nm = eps_r / mu * 0.052_918;
let r_nm = (self.dot_diameter_nm / 2.0).max(1.0);
let e_c_rydberg_mev = 13_605.0 * mu / (eps_r * eps_r); let overlap = (a_b_star_nm / r_nm).min(1.0);
(e_c_rydberg_mev * overlap).abs()
}
pub fn fine_structure_splitting_uev(&self) -> f64 {
let h = self.dot_height_nm.max(1.0);
let d = self.dot_diameter_nm.max(1.0);
let aspect = d / h;
let fss_0 = 50.0_f64; (fss_0 * (aspect - 1.0).abs()).max(0.0)
}
pub fn radiative_lifetime_ns(&self) -> f64 {
let h_ref = 8.0_f64; let d_ref = 25.0_f64; let vol_ref = h_ref * d_ref * d_ref; let vol = self.dot_height_nm * self.dot_diameter_nm * self.dot_diameter_nm;
let tau_ref = 1.0_f64; (tau_ref * vol_ref / vol.max(1.0)).clamp(0.2, 5.0)
}
pub fn phonon_sideband_fraction(&self, temperature_k: f64) -> f64 {
1.0 - self.debye_waller_factor(temperature_k)
}
pub fn debye_waller_factor(&self, temperature_k: f64) -> f64 {
let s0 = 0.04_f64;
let omega_c_mev = 1.0_f64; let omega_c = omega_c_mev * 1e-3 * E_CHARGE / H_BAR; let n_b = if temperature_k < 1e-6 {
0.0
} else {
let hbar_omega = H_BAR * omega_c;
let k_b_t = K_B * temperature_k;
1.0 / ((hbar_omega / k_b_t).exp() - 1.0 + 1e-30)
};
let s_t = s0 * (2.0 * n_b + 1.0);
(-s_t).exp()
}
pub fn pure_dephasing_rate_ghz(&self, temperature_k: f64) -> f64 {
let t = temperature_k.max(0.0);
if t < 30.0 {
0.005 * (t / 10.0_f64).powi(7) + 1e-5
} else {
0.1 * t
}
}
pub fn linewidth_ghz(&self, temperature_k: f64) -> f64 {
let tau_s = self.radiative_lifetime_ns() * 1e-9;
let gamma_rad = 1.0 / (2.0 * PI * tau_s) * 1e-9; let gamma_pure = self.pure_dephasing_rate_ghz(temperature_k);
gamma_rad + gamma_pure
}
pub fn indistinguishability(&self, temperature_k: f64) -> f64 {
let tau_s = self.radiative_lifetime_ns() * 1e-9;
let gamma_rad = 1.0 / tau_s * 1e-9; let gamma_pure = self.pure_dephasing_rate_ghz(temperature_k);
let denom = gamma_rad + 2.0 * gamma_pure;
if denom > 0.0 {
gamma_rad / denom
} else {
1.0
}
}
pub fn stark_shift_nm(&self, electric_field_kv_per_cm: f64) -> f64 {
let f = electric_field_kv_per_cm;
let beta_uev_per_kvcm_sq = 20.0_f64; let delta_e_uev = -beta_uev_per_kvcm_sq * f * f;
let delta_e_ev = delta_e_uev * 1e-6;
let hc_ev_nm = 1239.84_f64; let lambda_nm = self.wavelength_nm();
-lambda_nm * lambda_nm * delta_e_ev / hc_ev_nm
}
pub fn strain_tuning_nm(&self, voltage_kv: f64) -> f64 {
let d_lambda_per_kv = 0.3_f64; d_lambda_per_kv * voltage_kv
}
}
#[derive(Debug, Clone)]
pub struct QdEnsemble {
pub n_dots: usize,
pub center_wavelength_nm: f64,
pub inhomogeneous_width_nm: f64,
pub dot: InAsQd,
}
impl QdEnsemble {
pub fn new(center_nm: f64, width_nm: f64, n: usize) -> Self {
Self {
n_dots: n,
center_wavelength_nm: center_nm,
inhomogeneous_width_nm: width_nm.max(0.01),
dot: InAsQd::new(6.0, 20.0),
}
}
pub fn pl_spectrum(&self, wavelength_nm: f64) -> f64 {
let sigma = self.inhomogeneous_width_nm / (2.0 * (2.0 * 2_f64.ln()).sqrt());
let delta = wavelength_nm - self.center_wavelength_nm;
(-(delta * delta) / (2.0 * sigma * sigma)).exp()
}
pub fn homogeneous_linewidth_ghz(&self, temperature_k: f64) -> f64 {
self.dot.linewidth_ghz(temperature_k)
}
pub fn spectral_diffusion_ghz(&self) -> f64 {
3.0_f64
}
pub fn ple_window_ghz(&self) -> f64 {
let t = self.dot.temperature_k;
let lw = self.homogeneous_linewidth_ghz(t) + self.spectral_diffusion_ghz();
6.0 * lw
}
}
#[derive(Debug, Clone)]
pub struct BiexcitonCascade {
pub dot: InAsQd,
pub biexciton_binding_energy_mev: f64,
pub fss_uev: f64,
}
impl BiexcitonCascade {
pub fn new(dot: InAsQd) -> Self {
let fss = dot.fine_structure_splitting_uev();
Self {
biexciton_binding_energy_mev: -2.0,
fss_uev: fss,
dot,
}
}
pub fn biexciton_wavelength_nm(&self) -> f64 {
let e_x_ev = self.dot.transition_energy_ev();
let delta_ev = self.biexciton_binding_energy_mev * 1e-3 / 2.0;
let e_xx_ev = (e_x_ev - delta_ev).max(0.1);
let hc_ev_nm = 1239.84_f64;
hc_ev_nm / e_xx_ev
}
pub fn entanglement_fidelity(&self) -> f64 {
let fss_j = self.fss_uev * 1e-6 * E_CHARGE; let tau_s = self.dot.radiative_lifetime_ns() * 1e-9;
let phi = fss_j * tau_s / H_BAR;
0.5 * (1.0 + (-phi).exp())
}
pub fn concurrence(&self) -> f64 {
let fss_j = self.fss_uev * 1e-6 * E_CHARGE;
let tau_s = self.dot.radiative_lifetime_ns() * 1e-9;
let phi = fss_j * tau_s / H_BAR;
let c = (-phi).exp();
c.clamp(0.0, 1.0)
}
pub fn pair_rate_per_s(&self, excitation_power_uw: f64) -> f64 {
let tau_xx_ns = self.dot.radiative_lifetime_ns() / 2.0; let gamma_xx = 1.0 / (tau_xx_ns * 1e-9); let p_sat_uw = 100.0_f64;
let x = excitation_power_uw / p_sat_uw;
gamma_xx * x / (1.0 + x)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wavelength_in_inas_gaas_range() {
let dot = InAsQd::new(8.0, 25.0);
let wl = dot.wavelength_nm();
assert!(
wl > 800.0 && wl < 1200.0,
"Unexpected wavelength {wl:.1} nm; expected 800–1200 nm"
);
}
#[test]
fn test_indistinguishability_approaches_unity_at_zero_k() {
let dot = InAsQd::new(8.0, 25.0);
let m = dot.indistinguishability(0.01);
assert!(
m > 0.95,
"Indistinguishability should be near 1 at ~0 K; got {m:.4}"
);
}
#[test]
fn test_indistinguishability_decreases_with_temperature() {
let dot = InAsQd::new(8.0, 25.0);
let m_4k = dot.indistinguishability(4.0);
let m_77k = dot.indistinguishability(77.0);
assert!(
m_4k > m_77k,
"Indistinguishability should decrease with temperature; M(4K)={m_4k:.4}, M(77K)={m_77k:.4}"
);
}
#[test]
fn test_debye_waller_factor_bounded() {
let dot = InAsQd::new(8.0, 25.0);
for &t in &[0.0, 4.0, 10.0, 77.0, 300.0] {
let dw = dot.debye_waller_factor(t);
assert!(
(0.0..=1.0).contains(&dw),
"DW factor out of [0,1] at T={t} K: got {dw}"
);
}
}
#[test]
fn test_fss_grows_with_asymmetry() {
let round_dot = InAsQd::new(8.0, 8.0); let elongated = InAsQd::new(2.0, 40.0); let fss_round = round_dot.fine_structure_splitting_uev();
let fss_elon = elongated.fine_structure_splitting_uev();
assert!(
fss_elon > fss_round,
"Elongated dot should have larger FSS; round={fss_round:.1}, elongated={fss_elon:.1} μeV"
);
}
#[test]
fn test_biexciton_entanglement_fidelity_at_zero_fss() {
let dot = InAsQd::new(8.0, 25.0);
let mut cascade = BiexcitonCascade::new(dot);
cascade.fss_uev = 0.0;
let f = cascade.entanglement_fidelity();
assert!(
(f - 1.0).abs() < 1e-10,
"Fidelity should be 1 at FSS=0; got {f}"
);
}
#[test]
fn test_pl_spectrum_peaks_at_center() {
let ensemble = QdEnsemble::new(920.0, 10.0, 1000);
let peak = ensemble.pl_spectrum(920.0);
let wing = ensemble.pl_spectrum(940.0);
assert!(peak > wing, "PL should peak at centre wavelength");
assert!((peak - 1.0).abs() < 1e-10, "PL peak should normalise to 1");
}
#[test]
fn test_pair_rate_increases_with_power() {
let dot = InAsQd::new(8.0, 25.0);
let cascade = BiexcitonCascade::new(dot);
let r_low = cascade.pair_rate_per_s(1.0);
let r_high = cascade.pair_rate_per_s(10.0);
assert!(
r_high > r_low,
"Pair rate should increase with excitation power"
);
}
}