use std::f64::consts::PI;
use crate::constants::{GAMMA, HBAR, KB, MU_0};
use crate::effect::ishe::InverseSpinHall;
use crate::error::{invalid_param, Result};
use crate::material::{Ferromagnet, SpinInterface};
use crate::transport::diffusion::SpinDiffusion;
use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct AcSpinPumping {
pub interface: SpinInterface,
pub diffusion: SpinDiffusion,
pub t_nm: f64,
pub t_fm: f64,
}
impl AcSpinPumping {
pub fn new(
interface: SpinInterface,
diffusion: SpinDiffusion,
t_nm: f64,
t_fm: f64,
) -> Result<Self> {
if t_nm <= 0.0 {
return Err(invalid_param("t_nm", "NM thickness must be positive"));
}
if t_fm <= 0.0 {
return Err(invalid_param("t_fm", "FM thickness must be positive"));
}
Ok(Self {
interface,
diffusion,
t_nm,
t_fm,
})
}
pub fn yig_pt() -> Self {
Self {
interface: SpinInterface::yig_pt(),
diffusion: SpinDiffusion::platinum(),
t_nm: 7.0e-9, t_fm: 5.0e-6, }
}
pub fn g_nm(&self) -> f64 {
let ratio = self.t_nm / self.diffusion.lambda_sf;
self.diffusion.sigma * ratio.tanh() / self.diffusion.lambda_sf
}
pub fn effective_g_r(&self) -> f64 {
let g_r = self.interface.g_r;
let g_nm = self.g_nm();
g_r / (1.0 + g_r / g_nm)
}
pub fn dc_spin_current(&self, m: Vector3<f64>, dmdt: Vector3<f64>) -> Vector3<f64> {
let cross = m.cross(&dmdt);
cross * (HBAR / (4.0 * PI) * self.effective_g_r())
}
pub fn dc_magnitude_at_fmr(&self, omega_fmr: f64, cone_angle: f64) -> f64 {
let sin_sq = cone_angle.sin().powi(2);
(HBAR / (8.0 * PI)) * self.effective_g_r() * omega_fmr * sin_sq
}
pub fn ac_component_2omega(&self, omega_fmr: f64, cone_angle: f64) -> f64 {
self.dc_magnitude_at_fmr(omega_fmr, cone_angle)
}
pub fn damping_enhancement(&self, material: &Ferromagnet) -> f64 {
let g_eff = self.effective_g_r();
(GAMMA * HBAR * g_eff) / (4.0 * PI * MU_0 * material.ms * self.t_fm)
}
pub fn spin_accumulation_dc(&self, omega_fmr: f64, cone_angle: f64) -> f64 {
let j_s = self.dc_magnitude_at_fmr(omega_fmr, cone_angle);
j_s * self.diffusion.lambda_sf / self.diffusion.sigma
}
}
#[derive(Debug, Clone)]
pub struct SpinBattery {
pub pumping: AcSpinPumping,
pub detector: InverseSpinHall,
pub sample_width: f64,
pub sample_length: f64,
}
impl SpinBattery {
pub fn new(
pumping: AcSpinPumping,
detector: InverseSpinHall,
width: f64,
length: f64,
) -> Result<Self> {
if width <= 0.0 {
return Err(invalid_param("width", "sample width must be positive"));
}
if length <= 0.0 {
return Err(invalid_param("length", "sample length must be positive"));
}
Ok(Self {
pumping,
detector,
sample_width: width,
sample_length: length,
})
}
pub fn yig_pt_battery() -> Self {
Self {
pumping: AcSpinPumping::yig_pt(),
detector: InverseSpinHall::platinum(),
sample_width: 3.0e-3,
sample_length: 10.0e-3,
}
}
pub fn ishe_voltage(&self, omega_fmr: f64, cone_angle: f64) -> f64 {
let j_s = self.pumping.dc_magnitude_at_fmr(omega_fmr, cone_angle);
self.detector.theta_sh * self.detector.rho * j_s * self.sample_length
}
pub fn open_circuit_voltage(&self, m: Vector3<f64>, dmdt: Vector3<f64>) -> f64 {
let j_s_vec = self.pumping.dc_spin_current(m, dmdt);
let j_s_mag = j_s_vec.magnitude();
self.detector.theta_sh * self.detector.rho * j_s_mag * self.sample_length
}
pub fn signal_to_noise_estimate(
&self,
omega_fmr: f64,
cone_angle: f64,
temperature: f64,
) -> f64 {
let v_signal = self.ishe_voltage(omega_fmr, cone_angle);
let d_nm = 10.0e-9_f64; let r_nm = self.detector.rho * self.sample_length / (self.sample_width * d_nm);
let bandwidth = 1.0_f64; let v_noise = (4.0 * KB * temperature * r_nm * bandwidth).sqrt();
if v_noise == 0.0 {
return f64::INFINITY;
}
v_signal.abs() / v_noise
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
fn default_pumping() -> AcSpinPumping {
AcSpinPumping::yig_pt()
}
fn default_battery() -> SpinBattery {
SpinBattery::yig_pt_battery()
}
#[test]
fn test_dc_current_direction() {
let pumping = default_pumping();
let theta = PI / 6.0;
let m = Vector3::new(theta.sin(), 0.0, theta.cos());
let omega = 2.0 * PI * 10.0e9; let dmdt = m.cross(&Vector3::new(0.0, 0.0, 1.0)) * omega;
let j_s = pumping.dc_spin_current(m, dmdt);
let dot_m = j_s.dot(&m).abs();
let dot_dmdt = j_s.dot(&dmdt).abs();
let j_mag = j_s.magnitude();
let m_mag = m.magnitude();
let d_mag = dmdt.magnitude();
assert!(
dot_m < 1.0e-6 * j_mag * m_mag,
"J_s not orthogonal to m: dot = {dot_m:.3e}"
);
assert!(
dot_dmdt < 1.0e-6 * j_mag * d_mag,
"J_s not orthogonal to dm/dt: dot = {dot_dmdt:.3e}"
);
}
#[test]
fn test_backflow_reduces_g_r() {
let pumping = default_pumping();
let g_r_bare = pumping.interface.g_r;
let g_r_eff = pumping.effective_g_r();
assert!(
g_r_eff <= g_r_bare,
"G_r_eff ({g_r_eff:.3e}) should be ≤ G_r ({g_r_bare:.3e})"
);
assert!(g_r_eff > 0.0, "G_r_eff must be positive");
}
#[test]
fn test_damping_enhancement_positive() {
let pumping = default_pumping();
let yig = Ferromagnet::yig();
let delta_alpha = pumping.damping_enhancement(&yig);
assert!(
delta_alpha > 0.0,
"Δα must be positive, got {delta_alpha:.3e}"
);
}
#[test]
fn test_ishe_voltage_positive_at_fmr() {
let battery = default_battery();
let omega_fmr = 10.0e9 * 2.0 * PI; let cone_angle = PI / 4.0;
let v = battery.ishe_voltage(omega_fmr, cone_angle);
assert!(
v > 0.0,
"V_ISHE must be positive for Pt (positive θ_SH), got {v:.3e} V"
);
assert!(v.is_finite(), "V_ISHE must be finite, got {v}");
}
#[test]
fn test_g_nm_formula() {
let pumping = default_pumping();
let g_nm = pumping.g_nm();
assert!(g_nm > 0.0, "G_NM must be positive, got {g_nm:.3e}");
assert!(g_nm.is_finite(), "G_NM must be finite, got {g_nm}");
assert!(
g_nm > 1.0e12 && g_nm < 1.0e18,
"G_NM = {g_nm:.3e} outside plausible range [1e12, 1e18]"
);
}
#[test]
fn test_ac_equals_dc_at_fmr() {
let pumping = default_pumping();
let omega_fmr = 2.0 * PI * 10.0e9;
let cone_angle = PI / 8.0;
let j_dc = pumping.dc_magnitude_at_fmr(omega_fmr, cone_angle);
let j_2w = pumping.ac_component_2omega(omega_fmr, cone_angle);
assert!(
(j_dc - j_2w).abs() < 1.0e-30,
"AC 2ω component ({j_2w:.3e}) must equal DC ({j_dc:.3e}) at resonance"
);
}
#[test]
fn test_new_validates_thicknesses() {
let iface = SpinInterface::yig_pt();
let diff = SpinDiffusion::platinum();
assert!(
AcSpinPumping::new(iface.clone(), diff.clone(), -1.0e-9, 5.0e-6).is_err(),
"Should reject t_nm <= 0"
);
assert!(
AcSpinPumping::new(iface.clone(), diff.clone(), 7.0e-9, 0.0).is_err(),
"Should reject t_fm <= 0"
);
assert!(
AcSpinPumping::new(iface, diff, 7.0e-9, 5.0e-6).is_ok(),
"Should accept valid thicknesses"
);
}
#[test]
fn test_battery_new_validates_dimensions() {
let pumping = AcSpinPumping::yig_pt();
let detector = InverseSpinHall::platinum();
assert!(
SpinBattery::new(pumping.clone(), detector.clone(), -3.0e-3, 10.0e-3).is_err(),
"Should reject non-positive width"
);
assert!(
SpinBattery::new(pumping.clone(), detector.clone(), 3.0e-3, 0.0).is_err(),
"Should reject non-positive length"
);
assert!(
SpinBattery::new(pumping, detector, 3.0e-3, 10.0e-3).is_ok(),
"Should accept valid dimensions"
);
}
#[test]
fn test_spin_accumulation_positive() {
let pumping = default_pumping();
let omega_fmr = 2.0 * PI * 10.0e9;
let cone_angle = PI / 4.0;
let mu_s = pumping.spin_accumulation_dc(omega_fmr, cone_angle);
assert!(
mu_s > 0.0,
"Spin accumulation must be positive, got {mu_s:.3e}"
);
assert!(mu_s.is_finite(), "Spin accumulation must be finite");
}
#[test]
fn test_snr_positive_at_room_temperature() {
let battery = default_battery();
let omega_fmr = 2.0 * PI * 10.0e9;
let cone_angle = PI / 4.0;
let temperature = 300.0;
let snr = battery.signal_to_noise_estimate(omega_fmr, cone_angle, temperature);
assert!(snr > 0.0, "SNR must be positive, got {snr:.3e}");
assert!(snr.is_finite(), "SNR must be finite");
}
#[test]
fn test_dc_magnitude_formula() {
let pumping = default_pumping();
let omega = 2.0 * PI * 10.0e9;
let theta = PI / 4.0;
let j_dc = pumping.dc_magnitude_at_fmr(omega, theta);
let g_eff = pumping.effective_g_r();
let expected = (HBAR / (8.0 * PI)) * g_eff * omega * theta.sin().powi(2);
assert!(
(j_dc - expected).abs() < 1.0e-40,
"DC magnitude {j_dc:.6e} ≠ expected {expected:.6e}"
);
}
}