use std::f64::consts::PI;
const H_PLANCK: f64 = 6.626_070_15e-34;
const K_B: f64 = 1.380_649e-23;
const MU_B: f64 = 9.274_010_08e-24;
const G_NV: f64 = 2.003;
#[derive(Debug, Clone, PartialEq)]
pub enum NvCharge {
Negative,
Neutral,
}
#[derive(Debug, Clone)]
pub struct NvCenter {
pub charge_state: NvCharge,
pub magnetic_field_gauss: [f64; 3],
pub temperature_k: f64,
pub strain_mhz: f64,
}
impl NvCenter {
pub fn new_nv_minus(temperature_k: f64) -> Self {
Self {
charge_state: NvCharge::Negative,
magnetic_field_gauss: [0.0, 0.0, 0.0],
temperature_k,
strain_mhz: 0.0,
}
}
pub fn zpl_wavelength_nm(&self) -> f64 {
match self.charge_state {
NvCharge::Negative => 637.0,
NvCharge::Neutral => 575.0,
}
}
pub fn debye_waller_factor(&self) -> f64 {
match self.charge_state {
NvCharge::Negative => {
let dw0 = 0.032_f64;
let t_d = 1860.0_f64; let gamma_ph = 2.0_f64;
let t = self.temperature_k;
dw0 * (-gamma_ph * t / t_d).exp()
}
NvCharge::Neutral => 0.05, }
}
pub fn radiative_lifetime_ns(&self) -> f64 {
match self.charge_state {
NvCharge::Negative => {
let tau_0 = 12.0_f64; let t = self.temperature_k;
tau_0 * (1.0 + 0.002 * (t - 4.0).max(0.0) / 100.0)
}
NvCharge::Neutral => 20.0,
}
}
pub fn quantum_efficiency(&self) -> f64 {
match self.charge_state {
NvCharge::Negative => {
let t = self.temperature_k;
if t < 30.0 {
0.95
} else {
0.70 + 0.25 * (-0.02 * t).exp()
}
}
NvCharge::Neutral => 0.80,
}
}
pub fn zero_field_splitting_ghz(&self) -> f64 {
match self.charge_state {
NvCharge::Negative => {
let d0 = 2.877_2_f64; let dd_dt = -74.0e-6_f64; (d0 + dd_dt * self.temperature_k).max(0.0)
}
NvCharge::Neutral => 0.0, }
}
pub fn zeeman_splitting_ghz(&self, b_field_gauss: f64) -> f64 {
let b_tesla = b_field_gauss * 1e-4; G_NV * MU_B * b_tesla / (H_PLANCK * 1e9) }
pub fn odmr_contrast(&self) -> f64 {
match self.charge_state {
NvCharge::Negative => {
let t = self.temperature_k;
if t < 30.0 {
0.30
} else {
0.25
}
}
NvCharge::Neutral => 0.05,
}
}
pub fn t1_spin_ms(&self) -> f64 {
let t = self.temperature_k;
if t < 10.0 {
1000.0 } else if t < 77.0 {
1000.0 * (10.0_f64 / t).powi(5)
} else {
1.0
}
}
pub fn t2_spin_us(&self) -> f64 {
match self.charge_state {
NvCharge::Negative => {
let t = self.temperature_k;
if t < 30.0 {
1000.0
} else {
2.0
}
}
NvCharge::Neutral => 0.1,
}
}
pub fn t2_star_us(&self) -> f64 {
self.t2_spin_us() / 10.0
}
pub fn magnetic_sensitivity_nt_per_sqrthz(&self, collection_efficiency: f64) -> f64 {
let t2_star_s = self.t2_star_us() * 1e-6;
let contrast = self.odmr_contrast();
let tau_rad_s = self.radiative_lifetime_ns() * 1e-9;
let photon_rate = collection_efficiency * self.quantum_efficiency() / tau_rad_s;
let gamma_nv = G_NV * MU_B / (1.054_571_817e-34_f64); if photon_rate > 0.0 && t2_star_s > 0.0 && contrast > 0.0 {
let sens_t = 1.0 / (gamma_nv * t2_star_s * contrast * (photon_rate * t2_star_s).sqrt());
sens_t * 1e9 } else {
f64::INFINITY
}
}
pub fn temperature_sensitivity_mk_per_sqrthz(&self, photon_rate: f64) -> f64 {
let linewidth_hz = 1.0 / (2.0 * PI * self.t2_star_us() * 1e-6); let contrast = self.odmr_contrast();
if photon_rate > 0.0 && contrast > 0.0 {
let df_per_sqrthz = linewidth_hz / (contrast * photon_rate.sqrt());
let dd_dt_hz_per_k = 74.0e3_f64; let dt_per_sqrthz_k = df_per_sqrthz / dd_dt_hz_per_k;
dt_per_sqrthz_k * 1000.0 } else {
f64::INFINITY
}
}
}
#[derive(Debug, Clone)]
pub struct SivCenter {
pub temperature_k: f64,
pub strain_thz: f64,
}
impl SivCenter {
pub fn new(temperature_k: f64) -> Self {
Self {
temperature_k,
strain_thz: 0.0,
}
}
pub fn zpl_wavelength_nm(&self) -> f64 {
738.0_f64
}
pub fn debye_waller_factor(&self) -> f64 {
let dw0 = 0.70_f64;
let t_d = 1860.0_f64; let t = self.temperature_k;
(dw0 * (1.0 - 0.2 * t / t_d)).max(0.4)
}
pub fn radiative_lifetime_ns(&self) -> f64 {
1.0_f64 + 0.05 * self.temperature_k / 300.0 }
pub fn zero_phonon_linewidth_ghz(&self, temperature_k: f64) -> f64 {
let tau_rad_ghz = 1.0 / (2.0 * PI * self.radiative_lifetime_ns() * 1e-9) * 1e-9;
let delta_ghz = 48.0_f64;
let kbt_ghz = K_B * temperature_k / (H_PLANCK * 1e9);
let n_phonon = if temperature_k < 1e-3 {
0.0
} else {
1.0 / ((delta_ghz / kbt_ghz).exp() - 1.0 + 1e-30)
};
let gamma_pure = 0.05 * n_phonon.powi(2); tau_rad_ghz + gamma_pure
}
pub fn indistinguishability(&self) -> f64 {
let t = self.temperature_k;
let lw = self.zero_phonon_linewidth_ghz(t);
let gamma_rad_ghz = 1.0 / (2.0 * PI * self.radiative_lifetime_ns() * 1e-9) * 1e-9;
let gamma_pure = (lw - gamma_rad_ghz).max(0.0);
let denom = gamma_rad_ghz + 2.0 * gamma_pure;
if denom > 0.0 {
gamma_rad_ghz / denom
} else {
1.0
}
}
pub fn inversion_symmetry(&self) -> bool {
true
}
pub fn t2_spin_us(&self) -> f64 {
let t = self.temperature_k;
if t < 0.5 {
500.0
} else if t < 5.0 {
10.0 * (0.5_f64 / t).powi(3)
} else {
0.001 }
}
pub fn operating_temperature_mk(&self) -> f64 {
100.0_f64 }
}
#[derive(Debug, Clone)]
pub struct SnvCenter {
pub temperature_k: f64,
}
impl SnvCenter {
pub fn new(temperature_k: f64) -> Self {
Self { temperature_k }
}
pub fn zpl_wavelength_nm(&self) -> f64 {
619.0_f64
}
pub fn debye_waller_factor(&self) -> f64 {
let dw0 = 0.85_f64;
let t = self.temperature_k;
(dw0 * (1.0 - 0.05 * t / 100.0)).max(0.70)
}
pub fn radiative_lifetime_ns(&self) -> f64 {
6.0_f64 + 0.02 * self.temperature_k / 300.0
}
pub fn ground_state_splitting_thz(&self) -> f64 {
0.850_f64 }
pub fn operating_temperature_k(&self) -> f64 {
let delta_ghz = self.ground_state_splitting_thz() * 1000.0; let kbt_limit_ghz = delta_ghz / 5.0;
kbt_limit_ghz * H_PLANCK * 1e9 / K_B }
}
#[derive(Debug, Clone)]
pub struct HbnDefect {
pub emission_wavelength_nm: f64,
pub temperature_k: f64,
}
impl HbnDefect {
pub fn new(wavelength_nm: f64) -> Self {
Self {
emission_wavelength_nm: wavelength_nm.clamp(400.0, 900.0),
temperature_k: 300.0,
}
}
pub fn debye_waller_factor(&self, temperature_k: f64) -> f64 {
let dw_low = 0.80_f64;
let dw_high = 0.50_f64;
let t_cross = 150.0_f64; let t = temperature_k.max(0.0);
dw_high + (dw_low - dw_high) / (1.0 + (t / t_cross).powi(2))
}
pub fn linewidth_nm(&self, temperature_k: f64) -> f64 {
let lw0 = 0.3_f64; let gamma_ph = 15.0_f64; let t = temperature_k.max(0.0);
lw0 + gamma_ph * (t / 300.0_f64).powi(3)
}
pub fn brightness_mcps(&self) -> f64 {
15.0_f64
}
pub fn g2_zero(&self) -> f64 {
0.04_f64 }
pub fn photostability(&self) -> f64 {
0.85_f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nv_minus_zpl_wavelength() {
let nv = NvCenter::new_nv_minus(300.0);
assert_eq!(nv.zpl_wavelength_nm(), 637.0, "NV⁻ ZPL should be 637 nm");
}
#[test]
fn test_nv_zero_field_splitting() {
let nv = NvCenter::new_nv_minus(300.0);
let d = nv.zero_field_splitting_ghz();
assert!(
(d - 2.87).abs() < 0.02,
"Zero-field splitting should be ~2.87 GHz; got {d:.4}"
);
}
#[test]
fn test_nv_zeeman_splitting_rate() {
let nv = NvCenter::new_nv_minus(300.0);
let dz = nv.zeeman_splitting_ghz(100.0);
assert!(
(dz - 0.280).abs() < 0.005,
"Zeeman splitting at 100 Gauss should be ~0.280 GHz; got {dz:.4}"
);
}
#[test]
fn test_nv_t1_longer_at_low_temperature() {
let nv_rt = NvCenter::new_nv_minus(300.0);
let nv_low = NvCenter::new_nv_minus(4.0);
assert!(
nv_low.t1_spin_ms() > nv_rt.t1_spin_ms(),
"T₁ should be longer at low temperature"
);
}
#[test]
fn test_nv_magnetic_sensitivity_finite() {
let nv = NvCenter::new_nv_minus(300.0);
let sens = nv.magnetic_sensitivity_nt_per_sqrthz(0.01);
assert!(
sens.is_finite() && sens > 0.0,
"Magnetic sensitivity should be positive and finite; got {sens}"
);
}
#[test]
fn test_siv_zpl_wavelength() {
let siv = SivCenter::new(4.0);
assert_eq!(siv.zpl_wavelength_nm(), 738.0, "SiV ZPL should be 738 nm");
}
#[test]
fn test_siv_dw_factor_high() {
let siv = SivCenter::new(4.0);
let dw = siv.debye_waller_factor();
assert!(dw > 0.60, "SiV DW factor should be > 60 %; got {dw:.3}");
}
#[test]
fn test_siv_inversion_symmetry() {
let siv = SivCenter::new(4.0);
assert!(
siv.inversion_symmetry(),
"SiV should have inversion symmetry"
);
}
#[test]
fn test_siv_t2_longer_at_mk() {
let siv_mk = SivCenter::new(0.05);
let siv_4k = SivCenter::new(4.0);
assert!(
siv_mk.t2_spin_us() > siv_4k.t2_spin_us(),
"T₂ should be longer at mK temperatures"
);
}
#[test]
fn test_snv_zpl_and_dw() {
let snv = SnvCenter::new(4.0);
assert_eq!(snv.zpl_wavelength_nm(), 619.0, "SnV ZPL should be 619 nm");
assert!(
snv.debye_waller_factor() > 0.80,
"SnV DW factor should be > 80 %; got {:.3}",
snv.debye_waller_factor()
);
}
#[test]
fn test_snv_operating_temperature_above_1k() {
let snv = SnvCenter::new(4.0);
let t_op = snv.operating_temperature_k();
assert!(
t_op > 1.0,
"SnV operating T should exceed 1 K; got {t_op:.2} K"
);
}
#[test]
fn test_hbn_g2_below_threshold() {
let hbn = HbnDefect::new(600.0);
let g2 = hbn.g2_zero();
assert!(
g2 < 0.5,
"g²(0) should be < 0.5 for single emitter; got {g2}"
);
}
#[test]
fn test_hbn_linewidth_grows_with_temperature() {
let hbn = HbnDefect::new(600.0);
let lw_cold = hbn.linewidth_nm(10.0);
let lw_warm = hbn.linewidth_nm(300.0);
assert!(
lw_warm > lw_cold,
"hBN linewidth should increase with temperature; cold={lw_cold:.3}, warm={lw_warm:.3}"
);
}
#[test]
fn test_hbn_dw_factor_bounded() {
let hbn = HbnDefect::new(620.0);
for &t in &[0.0, 4.0, 77.0, 300.0] {
let dw = hbn.debye_waller_factor(t);
assert!(
(0.0..=1.0).contains(&dw),
"DW factor out of [0,1] at T={t} K: got {dw}"
);
}
}
}