use std::f64::consts::PI;
use crate::constants::KB;
use crate::error::{self, Result};
use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct SpinDensityWave {
pub amplitude: f64,
pub nesting_vector: Vector3<f64>,
pub phase: f64,
pub gap: f64,
pub neel_temperature: f64,
}
impl SpinDensityWave {
pub fn new(
amplitude: f64,
nesting_vector: Vector3<f64>,
phase: f64,
gap: f64,
neel_temperature: f64,
) -> Result<Self> {
if amplitude < 0.0 {
return Err(error::invalid_param("amplitude", "must be non-negative"));
}
if gap < 0.0 {
return Err(error::invalid_param("gap", "must be non-negative"));
}
if neel_temperature <= 0.0 {
return Err(error::invalid_param("neel_temperature", "must be positive"));
}
Ok(Self {
amplitude,
nesting_vector,
phase,
gap,
neel_temperature,
})
}
pub fn chromium() -> Result<Self> {
let lattice_constant = 2.88e-10; let delta = 0.05;
let q_magnitude = 2.0 * PI / lattice_constant * (1.0 - delta);
let nesting_vector = Vector3::new(q_magnitude, 0.0, 0.0);
let amplitude = 5.0e4;
Self::new(
amplitude,
nesting_vector,
0.0, 0.12, 311.0, )
}
pub fn magnetization_at(&self, r: &Vector3<f64>) -> f64 {
let q_dot_r = self.nesting_vector.dot(r);
self.amplitude * (q_dot_r + self.phase).cos()
}
pub fn wavelength(&self) -> f64 {
let q_mag = self.nesting_vector.dot(&self.nesting_vector).sqrt();
if q_mag > 0.0 {
2.0 * PI / q_mag
} else {
f64::INFINITY
}
}
pub fn is_commensurate(&self, lattice_constant: f64, tolerance: f64) -> bool {
let q_mag = self.nesting_vector.dot(&self.nesting_vector).sqrt();
let q_ratio = q_mag * lattice_constant / (2.0 * PI);
for denom in 1..=4 {
let numer = (q_ratio * denom as f64).round();
let rational = numer / denom as f64;
if (q_ratio - rational).abs() < tolerance {
return true;
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct SdwGapSolver {
pub coupling: f64,
pub cutoff_energy: f64,
pub neel_temperature: f64,
pub n_points: usize,
}
impl SdwGapSolver {
pub fn new(coupling: f64, cutoff_energy: f64, neel_temperature: f64) -> Result<Self> {
if coupling <= 0.0 {
return Err(error::invalid_param("coupling", "must be positive"));
}
if cutoff_energy <= 0.0 {
return Err(error::invalid_param("cutoff_energy", "must be positive"));
}
if neel_temperature <= 0.0 {
return Err(error::invalid_param("neel_temperature", "must be positive"));
}
Ok(Self {
coupling,
cutoff_energy,
neel_temperature,
n_points: 200,
})
}
pub fn zero_temperature_gap(&self) -> f64 {
2.0 * self.cutoff_energy * (-1.0 / self.coupling).exp()
}
pub fn gap_at_temperature(&self, temperature: f64) -> f64 {
if temperature <= 0.0 {
return self.zero_temperature_gap();
}
if temperature >= self.neel_temperature {
return 0.0;
}
let t_ratio = temperature / self.neel_temperature;
let delta_0 = self.zero_temperature_gap();
let alpha = 1.74;
let arg = alpha * (1.0 / t_ratio - 1.0).sqrt();
delta_0 * arg.tanh()
}
pub fn gap_equation_rhs(&self, gap: f64, temperature: f64) -> f64 {
let n = self.n_points;
let de = self.cutoff_energy / n as f64;
let mut integral = 0.0;
let k_b_t_ev = KB * temperature / (1.602_176_634e-19);
for i in 0..n {
let epsilon = (i as f64 + 0.5) * de;
let e_k = (epsilon * epsilon + gap * gap).sqrt();
let tanh_arg = if k_b_t_ev > 1e-30 {
e_k / (2.0 * k_b_t_ev)
} else {
f64::MAX
};
let tanh_val = if tanh_arg > 30.0 {
1.0
} else {
tanh_arg.tanh()
};
integral += tanh_val / e_k;
}
integral * de
}
pub fn solve_gap(&self, temperature: f64, max_iterations: usize) -> Result<f64> {
if temperature >= self.neel_temperature {
return Ok(0.0);
}
let target = 1.0 / self.coupling;
let delta_0 = self.zero_temperature_gap();
let mut lo = 0.0_f64;
let mut hi = delta_0 * 1.5;
for _ in 0..max_iterations {
let mid = (lo + hi) / 2.0;
let rhs = self.gap_equation_rhs(mid, temperature);
if (rhs - target).abs() < 1e-8 {
return Ok(mid);
}
if rhs > target {
lo = mid;
} else {
hi = mid;
}
if (hi - lo) < 1e-14 * delta_0 {
return Ok((lo + hi) / 2.0);
}
}
Ok((lo + hi) / 2.0)
}
}
pub struct ChromiumSdw;
impl ChromiumSdw {
pub const NEEL_TEMPERATURE: f64 = 311.0;
pub const SPIN_FLIP_TEMPERATURE: f64 = 123.0;
pub const LATTICE_CONSTANT: f64 = 2.88e-10;
pub const INCOMMENSURABILITY: f64 = 0.05;
pub const ZERO_TEMP_GAP: f64 = 0.12;
pub fn nesting_vector() -> Vector3<f64> {
let q = 2.0 * PI / Self::LATTICE_CONSTANT * (1.0 - Self::INCOMMENSURABILITY);
Vector3::new(q, 0.0, 0.0)
}
pub fn nesting_vector_reduced() -> Vector3<f64> {
let factor = 1.0 - Self::INCOMMENSURABILITY;
Vector3::new(factor, 0.0, 0.0)
}
pub fn polarization_type(temperature: f64) -> SdwPolarization {
if temperature > Self::NEEL_TEMPERATURE {
SdwPolarization::None
} else if temperature > Self::SPIN_FLIP_TEMPERATURE {
SdwPolarization::Transverse
} else {
SdwPolarization::Longitudinal
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdwPolarization {
None,
Transverse,
Longitudinal,
}
pub fn resistivity_anomaly(
gap_ev: f64,
neel_temperature: f64,
fermi_surface_fraction: f64,
) -> Result<f64> {
if neel_temperature <= 0.0 {
return Err(error::invalid_param("neel_temperature", "must be positive"));
}
if !(0.0..=1.0).contains(&fermi_surface_fraction) {
return Err(error::invalid_param(
"fermi_surface_fraction",
"must be between 0 and 1",
));
}
let kb_tn_ev = KB * neel_temperature / 1.602_176_634e-19;
if kb_tn_ev < 1e-30 {
return Ok(0.0);
}
let ratio = gap_ev / kb_tn_ev;
let delta_rho = -fermi_surface_fraction * ratio * ratio;
Ok(delta_rho)
}
pub fn condensation_energy(density_of_states: f64, gap_ev: f64) -> Result<f64> {
if density_of_states <= 0.0 {
return Err(error::invalid_param(
"density_of_states",
"must be positive",
));
}
if gap_ev < 0.0 {
return Err(error::invalid_param("gap_ev", "must be non-negative"));
}
let ev_to_j = 1.602_176_634e-19;
let e_cond = -0.5 * density_of_states * gap_ev * gap_ev * ev_to_j;
Ok(e_cond)
}
pub fn elastic_energy(elastic_modulus: f64, strain_amplitude: f64) -> Result<f64> {
if elastic_modulus <= 0.0 {
return Err(error::invalid_param("elastic_modulus", "must be positive"));
}
Ok(0.5 * elastic_modulus * strain_amplitude * strain_amplitude)
}
pub fn total_sdw_energy(
density_of_states: f64,
gap_ev: f64,
elastic_modulus: f64,
strain_amplitude: f64,
) -> Result<f64> {
let e_cond = condensation_energy(density_of_states, gap_ev)?;
let e_elastic = elastic_energy(elastic_modulus, strain_amplitude)?;
Ok(e_cond + e_elastic)
}
pub fn enhanced_susceptibility(
density_of_states: f64,
interaction_u: f64,
cutoff_energy: f64,
temperature: f64,
) -> Result<f64> {
if temperature <= 0.0 {
return Err(error::invalid_param("temperature", "must be positive"));
}
if density_of_states <= 0.0 {
return Err(error::invalid_param(
"density_of_states",
"must be positive",
));
}
let kb_t_ev = KB * temperature / 1.602_176_634e-19;
if kb_t_ev < 1e-30 {
return Err(error::numerical_error(
"temperature too low for susceptibility calculation",
));
}
let log_arg = 1.13 * cutoff_energy / kb_t_ev;
if log_arg <= 0.0 {
return Err(error::numerical_error(
"invalid argument for logarithm in susceptibility",
));
}
let chi_0 = density_of_states * log_arg.ln();
let denominator = 1.0 - interaction_u * chi_0;
if denominator.abs() < 1e-10 {
return Err(error::numerical_error(
"susceptibility diverges at SDW transition",
));
}
Ok(chi_0 / denominator)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sdw_gap_vanishes_at_neel_temperature() {
let solver = SdwGapSolver::new(0.5, 1.0, 311.0).expect("SdwGapSolver::new should succeed");
let gap_at_tn = solver.gap_at_temperature(311.0);
assert!(
gap_at_tn.abs() < 1e-15,
"SDW gap must vanish at T_N, got {}",
gap_at_tn
);
let gap_above = solver.gap_at_temperature(400.0);
assert!(
gap_above.abs() < 1e-15,
"SDW gap must be zero above T_N, got {}",
gap_above
);
}
#[test]
fn test_cr_nesting_vector_approximately_095() {
let q_reduced = ChromiumSdw::nesting_vector_reduced();
assert!(
(q_reduced.x - 0.95).abs() < 0.001,
"Cr nesting vector x-component should be ~0.95 in units of 2π/a, got {}",
q_reduced.x
);
assert!(
q_reduced.y.abs() < 1e-15,
"Cr nesting vector y-component should be 0"
);
assert!(
q_reduced.z.abs() < 1e-15,
"Cr nesting vector z-component should be 0"
);
}
#[test]
fn test_sdw_condensation_energy_is_negative() {
let n0 = 1e28; let gap = 0.12;
let e_cond = condensation_energy(n0, gap).expect("condensation_energy should succeed");
assert!(
e_cond < 0.0,
"SDW condensation energy must be negative (favorable), got {}",
e_cond
);
}
#[test]
fn test_sdw_magnetization_oscillates() {
let sdw = SpinDensityWave::chromium().expect("Chromium SDW creation should succeed");
let wavelength = sdw.wavelength();
assert!(wavelength > 0.0, "SDW wavelength must be positive");
let r0 = Vector3::new(0.0, 0.0, 0.0);
let r_quarter = Vector3::new(wavelength / 4.0, 0.0, 0.0);
let r_half = Vector3::new(wavelength / 2.0, 0.0, 0.0);
let m0 = sdw.magnetization_at(&r0);
let m_quarter = sdw.magnetization_at(&r_quarter);
let m_half = sdw.magnetization_at(&r_half);
assert!(
(m0 - sdw.amplitude).abs() < sdw.amplitude * 1e-10,
"M(0) should equal M₀"
);
assert!(
m_quarter.abs() < sdw.amplitude * 1e-10,
"M(λ/4) should be approximately 0, got {}",
m_quarter
);
assert!(
(m_half + sdw.amplitude).abs() < sdw.amplitude * 1e-10,
"M(λ/2) should equal -M₀"
);
}
#[test]
fn test_chromium_sdw_parameters() {
let sdw = SpinDensityWave::chromium().expect("Chromium SDW creation should succeed");
assert!(
(sdw.neel_temperature - 311.0).abs() < 1e-10,
"Cr T_N should be 311 K"
);
assert!((sdw.gap - 0.12).abs() < 1e-10, "Cr gap should be 0.12 eV");
assert!(sdw.amplitude > 0.0, "Cr SDW amplitude should be positive");
}
#[test]
fn test_chromium_polarization_phases() {
assert_eq!(
ChromiumSdw::polarization_type(100.0),
SdwPolarization::Longitudinal,
"Below T_SF should be longitudinal"
);
assert_eq!(
ChromiumSdw::polarization_type(200.0),
SdwPolarization::Transverse,
"Between T_SF and T_N should be transverse"
);
assert_eq!(
ChromiumSdw::polarization_type(400.0),
SdwPolarization::None,
"Above T_N should be paramagnetic (no SDW)"
);
}
#[test]
fn test_resistivity_anomaly_sign() {
let delta_rho =
resistivity_anomaly(0.12, 311.0, 0.3).expect("resistivity_anomaly should succeed");
assert!(
delta_rho < 0.0,
"Resistivity anomaly should be negative (gap reduces scattering channels)"
);
}
#[test]
fn test_resistivity_anomaly_zero_gap() {
let delta_rho =
resistivity_anomaly(0.0, 311.0, 0.3).expect("resistivity_anomaly should succeed");
assert!(
delta_rho.abs() < 1e-15,
"Resistivity anomaly should be zero when gap is zero"
);
}
#[test]
fn test_elastic_energy_positive() {
let e_el = elastic_energy(3.5e11, 1e-5).expect("elastic_energy should succeed");
assert!(e_el > 0.0, "Elastic energy should be positive");
}
#[test]
fn test_total_sdw_energy() {
let n0 = 1e28;
let gap = 0.12;
let c_modulus = 3.5e11;
let strain = 1e-6;
let e_total =
total_sdw_energy(n0, gap, c_modulus, strain).expect("total_sdw_energy should succeed");
let e_cond = condensation_energy(n0, gap).expect("condensation_energy should succeed");
let e_el = elastic_energy(c_modulus, strain).expect("elastic_energy should succeed");
assert!(
(e_total - (e_cond + e_el)).abs() < 1e-30,
"Total energy should be sum of condensation and elastic"
);
assert!(
e_total < 0.0,
"Total SDW energy should be negative for small strain"
);
}
#[test]
fn test_sdw_gap_temperature_dependence() {
let solver = SdwGapSolver::new(0.5, 1.0, 311.0).expect("SdwGapSolver::new should succeed");
let gap_0 = solver.gap_at_temperature(0.0);
let gap_150 = solver.gap_at_temperature(150.0);
let gap_300 = solver.gap_at_temperature(300.0);
assert!(
gap_0 > gap_150,
"Gap at T=0 should be larger than at T=150K"
);
assert!(
gap_150 > gap_300,
"Gap at T=150K should be larger than at T=300K"
);
assert!(gap_0 > 0.0, "Zero-temperature gap should be positive");
}
#[test]
fn test_sdw_incommensurability() {
let sdw = SpinDensityWave::chromium().expect("Chromium SDW creation should succeed");
let is_comm = sdw.is_commensurate(ChromiumSdw::LATTICE_CONSTANT, 0.01);
assert!(!is_comm, "Cr SDW should be incommensurate (δ = 0.05)");
let comm_sdw = SpinDensityWave::new(
1e4,
Vector3::new(2.0 * PI / 3e-10, 0.0, 0.0),
0.0,
0.1,
300.0,
)
.expect("commensurate SDW creation should succeed");
let is_comm2 = comm_sdw.is_commensurate(3e-10, 0.01);
assert!(is_comm2, "SDW with Q = 2π/a should be commensurate");
}
#[test]
fn test_invalid_sdw_parameters() {
assert!(SpinDensityWave::new(-1.0, Vector3::new(1.0, 0.0, 0.0), 0.0, 0.1, 300.0).is_err());
assert!(SpinDensityWave::new(1.0, Vector3::new(1.0, 0.0, 0.0), 0.0, -0.1, 300.0).is_err());
assert!(SpinDensityWave::new(1.0, Vector3::new(1.0, 0.0, 0.0), 0.0, 0.1, -1.0).is_err());
assert!(SdwGapSolver::new(0.0, 1.0, 311.0).is_err());
assert!(SdwGapSolver::new(0.5, 0.0, 311.0).is_err());
}
}