use std::f64::consts::PI;
use crate::constants::{GAMMA, MU_0};
use crate::error::{self, Result};
use crate::math::Complex;
#[derive(Debug, Clone)]
pub struct TavisCummings {
pub n_spins: usize,
pub omega_cavity: f64,
pub omega_magnon: f64,
pub coupling_g: f64,
pub kappa: f64,
pub gamma_damp: f64,
cavity_amp: Complex,
magnon_amp: Complex,
}
impl TavisCummings {
pub fn new(
n_spins: usize,
omega_cavity: f64,
omega_magnon: f64,
coupling_g: f64,
kappa: f64,
gamma_damp: f64,
) -> Result<Self> {
if n_spins == 0 {
return Err(error::invalid_param(
"n_spins",
"ensemble must contain at least one spin",
));
}
if omega_cavity < 0.0 {
return Err(error::invalid_param(
"omega_cavity",
"cavity frequency must be non-negative",
));
}
if omega_magnon < 0.0 {
return Err(error::invalid_param(
"omega_magnon",
"magnon frequency must be non-negative",
));
}
if coupling_g < 0.0 {
return Err(error::invalid_param(
"coupling_g",
"coupling constant must be non-negative",
));
}
if kappa < 0.0 {
return Err(error::invalid_param(
"kappa",
"cavity decay rate must be non-negative",
));
}
if gamma_damp < 0.0 {
return Err(error::invalid_param(
"gamma_damp",
"spin damping rate must be non-negative",
));
}
Ok(Self {
n_spins,
omega_cavity,
omega_magnon,
coupling_g,
kappa,
gamma_damp,
cavity_amp: Complex::ZERO,
magnon_amp: Complex::ZERO,
})
}
pub fn yig_ensemble(n_spins: usize) -> Self {
let n = n_spins.max(1);
let omega_cavity = 2.0 * PI * 10.0e9;
let h_bias = 8.0e4_f64; let omega_magnon = GAMMA * MU_0 * h_bias;
let coupling_g = 2.0 * PI * 100.0e6;
let kappa = 2.0 * PI * 1.0e6;
let alpha = 0.0001_f64;
let gamma_damp = alpha * omega_magnon;
Self {
n_spins: n,
omega_cavity,
omega_magnon,
coupling_g,
kappa,
gamma_damp,
cavity_amp: Complex::ZERO,
magnon_amp: Complex::ZERO,
}
}
pub fn collective_coupling(&self) -> f64 {
self.coupling_g * (self.n_spins as f64).sqrt()
}
pub fn polariton_frequencies(&self) -> (f64, f64) {
let g_n = self.collective_coupling();
let delta = self.omega_cavity - self.omega_magnon;
let discriminant = ((delta / 2.0).powi(2) + g_n.powi(2)).sqrt();
let center = (self.omega_cavity + self.omega_magnon) / 2.0;
(center - discriminant, center + discriminant)
}
pub fn superradiant_threshold(&self) -> f64 {
(self.omega_cavity * self.omega_magnon).sqrt() / 2.0
}
pub fn cooperativity(&self) -> f64 {
let g_n = self.collective_coupling();
4.0 * g_n.powi(2) / (self.kappa * self.gamma_damp)
}
pub fn detuning(&self) -> f64 {
self.omega_cavity - self.omega_magnon
}
pub fn cavity_amplitude(&self) -> Complex {
self.cavity_amp
}
pub fn magnon_amplitude(&self) -> Complex {
self.magnon_amp
}
pub fn mean_field_evolve(&mut self, dt: f64, drive: Complex) {
let g_n = self.collective_coupling();
let a = self.cavity_amp;
let b = self.magnon_amp;
let da = a
.mul_i()
.scale(-self.omega_cavity)
.sub(&a.scale(self.kappa / 2.0))
.sub(&b.mul_i().scale(g_n))
.add(&drive);
let db = b
.mul_i()
.scale(-self.omega_magnon)
.sub(&b.scale(self.gamma_damp / 2.0))
.sub(&a.mul_i().scale(g_n));
self.cavity_amp = a.add(&da.scale(dt));
self.magnon_amp = b.add(&db.scale(dt));
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rel_close(a: f64, b: f64, tol: f64) -> bool {
if b == 0.0 {
return a.abs() < tol;
}
((a - b) / b).abs() < tol
}
#[test]
fn test_collective_coupling_scales_sqrt_n() {
let n: usize = 100;
let tc_n = TavisCummings::yig_ensemble(n);
let tc_4n = TavisCummings::yig_ensemble(4 * n);
let ratio = tc_4n.collective_coupling() / tc_n.collective_coupling();
assert!((ratio - 2.0).abs() < 1e-10, "ratio = {ratio}, expected 2.0");
}
#[test]
fn test_polariton_anticrossing_at_resonance() {
let omega = 2.0 * PI * 10.0e9;
let g = 2.0 * PI * 100.0e6;
let kappa = 2.0 * PI * 1.0e6;
let gamma = 2.0 * PI * 1.0e6;
let tc = TavisCummings::new(1, omega, omega, g, kappa, gamma).unwrap();
let (lo, hi) = tc.polariton_frequencies();
let g_n = tc.collective_coupling();
assert!(
rel_close(hi - lo, 2.0 * g_n, 1e-12),
"splitting = {}, 2g_N = {}",
hi - lo,
2.0 * g_n
);
}
#[test]
fn test_superradiant_threshold_positive() {
let tc = TavisCummings::yig_ensemble(50);
let g_c = tc.superradiant_threshold();
assert!(g_c > 0.0, "threshold must be positive, got {g_c}");
let expected = (tc.omega_cavity * tc.omega_magnon).sqrt() / 2.0;
assert!(
rel_close(g_c, expected, 1e-14),
"threshold mismatch: {} vs {}",
g_c,
expected
);
}
#[test]
fn test_cooperativity_formula() {
let omega_c = 2.0 * PI * 10.0e9;
let omega_m = 2.0 * PI * 9.0e9;
let g = 2.0 * PI * 50.0e6;
let kappa = 2.0 * PI * 2.0e6;
let gamma = 2.0 * PI * 1.0e6;
let n: usize = 16;
let tc = TavisCummings::new(n, omega_c, omega_m, g, kappa, gamma).unwrap();
let g_n = g * (n as f64).sqrt();
let expected_c = 4.0 * g_n.powi(2) / (kappa * gamma);
assert!(
rel_close(tc.cooperativity(), expected_c, 1e-12),
"C = {}, expected = {}",
tc.cooperativity(),
expected_c
);
}
#[test]
fn test_mean_field_evolve_finite() {
let mut tc = TavisCummings::yig_ensemble(100);
tc.cavity_amp = Complex::new(1.0e-3, 0.0);
let dt = 1.0e-13_f64; for _ in 0..1000 {
tc.mean_field_evolve(dt, Complex::ZERO);
}
assert!(
tc.cavity_amplitude().is_finite(),
"cavity amplitude became non-finite"
);
assert!(
tc.magnon_amplitude().is_finite(),
"magnon amplitude became non-finite"
);
}
#[test]
fn test_yig_ensemble_single_spin_matches_hybrid() {
let tc = TavisCummings::yig_ensemble(1);
let c = tc.cooperativity();
assert!(
c > 1.0,
"expected cooperativity > 1 for single-spin YIG, got {c}"
);
}
#[test]
fn test_n_spins_zero_errors() {
let result = TavisCummings::new(0, 1.0e10, 1.0e10, 1.0e8, 1.0e6, 1.0e5);
assert!(result.is_err(), "expected Err for n_spins=0");
}
#[test]
fn test_large_detuning_polaritons() {
let g = 2.0 * PI * 10.0e6;
let omega_c = 2.0 * PI * 10.0e9;
let omega_m = 2.0 * PI * 8.0e9; let kappa = 2.0 * PI * 1.0e6;
let gamma = 2.0 * PI * 1.0e5;
let tc = TavisCummings::new(1, omega_c, omega_m, g, kappa, gamma).unwrap();
let (lo, hi) = tc.polariton_frequencies();
let gap = omega_c - omega_m;
assert!(
(lo - omega_m).abs() < 0.05 * gap,
"lo polariton {lo:.3e} not near omega_m {omega_m:.3e}"
);
assert!(
(hi - omega_c).abs() < 0.05 * gap,
"hi polariton {hi:.3e} not near omega_c {omega_c:.3e}"
);
}
#[test]
fn test_detuning_sign() {
let omega_c = 2.0 * PI * 10.0e9;
let omega_m = 2.0 * PI * 8.0e9;
let tc = TavisCummings::new(1, omega_c, omega_m, 1.0e8, 1.0e6, 1.0e5).unwrap();
assert!(
tc.detuning() > 0.0,
"detuning should be positive when omega_c > omega_m"
);
}
#[test]
fn test_mean_field_drive_increases_amplitude() {
let mut tc = TavisCummings::yig_ensemble(100);
let drive = Complex::new(1.0e6, 0.0); let dt = 1.0e-13_f64;
let initial_norm = tc.cavity_amplitude().norm();
for _ in 0..200 {
tc.mean_field_evolve(dt, drive);
}
let final_norm = tc.cavity_amplitude().norm();
assert!(
final_norm > initial_norm,
"drive should increase cavity amplitude: initial={initial_norm}, final={final_norm}"
);
}
}