use std::f64::consts::PI;
use super::band_model::MagnonBandModel;
use super::berry_curvature::BerryCurvature;
use crate::constants::{HBAR, KB};
use crate::error::{self, Result};
const MEV_TO_J: f64 = 1.602_176_634e-22;
pub struct MagnonHallConductivity<'a> {
pub model: &'a MagnonBandModel,
pub temperature: f64,
}
impl<'a> MagnonHallConductivity<'a> {
pub fn new(model: &'a MagnonBandModel, temperature: f64) -> Result<Self> {
if temperature <= 0.0 {
return Err(error::invalid_param("temperature", "must be positive"));
}
Ok(Self { model, temperature })
}
#[inline]
pub fn c2_function(energy: f64, temperature: f64) -> f64 {
if energy <= 0.0 || temperature <= 0.0 {
return 0.0;
}
let x = energy * MEV_TO_J / (KB * temperature); if x < 1e-4 {
return PI * PI / 3.0; }
if x > 50.0 {
return 0.0; }
let n_b = 1.0 / (x.exp() - 1.0);
let ln_nb = n_b.ln();
let ln_1p_nb = (1.0 + n_b).ln();
let term1 = (1.0 + n_b) * (ln_1p_nb - ln_nb).powi(2);
let term2 = -(ln_nb * ln_nb);
let li2 = Self::dilogarithm(-n_b);
term1 + term2 - 2.0 * li2
}
pub fn dilogarithm(z: f64) -> f64 {
if z == 0.0 {
return 0.0;
}
if z > 0.0 && (z - 1.0).abs() < 1e-14 {
return PI * PI / 6.0; }
if z < -1.0 {
let lnmz = (-z).ln();
return -(PI * PI / 6.0) - 0.5 * lnmz * lnmz - Self::dilogarithm(1.0 / z);
}
if (z - 1.0).abs() < 1e-10 {
return PI * PI / 6.0;
}
let mut sum = 0.0_f64;
let mut z_pow = z;
for n in 1_usize..=100 {
let term = z_pow / ((n * n) as f64);
sum += term;
z_pow *= z;
if term.abs() < 1e-12 * (1.0 + sum.abs()) {
break;
}
}
sum
}
pub fn compute(&self, nx: usize, ny: usize) -> Result<f64> {
if nx < 2 || ny < 2 {
return Err(error::invalid_param("nx/ny", "need at least 2 grid points"));
}
let nb = self.model.n_bands();
let bc = BerryCurvature::new(self.model);
let t = self.temperature;
let dkx = 2.0 * PI / (nx as f64);
let dky = 2.0 * PI / (ny as f64);
let cell_area = dkx * dky;
let mut sum = 0.0_f64;
for band in 0..nb {
for ix in 0..nx {
let kx = -PI + (ix as f64) * dkx;
for iy in 0..ny {
let ky = -PI + (iy as f64) * dky;
let (evals, _) = self.model.diagonalize((kx, ky))?;
let eps = evals[band]; let c2 = Self::c2_function(eps, t);
let omega = bc.curvature_at((kx, ky), band)?;
sum += c2 * omega;
}
}
}
let a = self.model.a_lattice;
let prefactor = -(KB / HBAR) / (2.0 * PI * 2.0 * PI) / (a * a);
Ok(prefactor * sum * cell_area)
}
pub fn temperature_sweep(
&self,
temperatures: &[f64],
nx: usize,
ny: usize,
) -> Result<Vec<(f64, f64)>> {
let mut results = Vec::with_capacity(temperatures.len());
for &t in temperatures {
if t <= 0.0 {
return Err(error::invalid_param(
"temperatures",
"all temperatures must be positive",
));
}
let mhc = Self::new(self.model, t)?;
let kxy = mhc.compute(nx, ny)?;
results.push((t, kxy));
}
Ok(results)
}
pub fn thermal_hall_resistivity(&self, nx: usize, ny: usize) -> Result<f64> {
let kxy = self.compute(nx, ny)?;
if kxy.abs() < 1e-50 {
return Err(error::numerical_error(
"thermal Hall conductivity is zero — model has no Berry curvature",
));
}
Ok(1.0 / kxy)
}
}
#[cfg(test)]
mod tests {
use std::f64::consts::PI;
use super::*;
use crate::topomagnon::band_model::MagnonBandModel;
fn approx(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn c2_classical_limit() {
let c2 = MagnonHallConductivity::c2_function(1e-10, 1e6); assert!(
approx(c2, PI * PI / 3.0, 1e-6),
"Classical c₂ limit: expected {}, got {}",
PI * PI / 3.0,
c2
);
}
#[test]
fn c2_quantum_limit() {
let c2 = MagnonHallConductivity::c2_function(1000.0, 0.001); assert!(c2.abs() < 1e-6, "Quantum c₂ limit: expected ~0, got {}", c2);
}
#[test]
fn dilogarithm_at_zero() {
let li2 = MagnonHallConductivity::dilogarithm(0.0);
assert!(approx(li2, 0.0, 1e-15), "Li₂(0) should be 0, got {}", li2);
}
#[test]
fn dilogarithm_half() {
let li2 = MagnonHallConductivity::dilogarithm(0.5);
let expected = PI * PI / 12.0 - 0.5 * (2.0_f64.ln()).powi(2);
assert!(
approx(li2, expected, 1e-10),
"Li₂(1/2): expected {:.6}, got {:.6}",
expected,
li2
);
}
#[test]
fn compute_zero_with_zero_dmi() {
let m = MagnonBandModel::square_dmi(1.0, 0.0, 0.0).unwrap();
let mhc = MagnonHallConductivity::new(&m, 100.0).unwrap();
let kxy = mhc.compute(8, 8).unwrap();
assert!(
kxy.abs() < 1e-30,
"κ_xy should be zero for trivial band, got {}",
kxy
);
}
#[test]
fn compute_nonzero_with_dmi() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.4, 0.0).unwrap();
let mhc = MagnonHallConductivity::new(&m, 50.0).unwrap();
let kxy = mhc.compute(10, 10).unwrap();
assert!(
kxy.abs() > 1e-20,
"κ_xy should be non-zero with DMI, got {}",
kxy
);
}
#[test]
fn compute_sign_flips_with_dmi_sign() {
let m_pos = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.4, 0.0).unwrap();
let m_neg = MagnonBandModel::honeycomb_haldane(1.0, 0.0, -0.4, 0.0).unwrap();
let kxy_pos = MagnonHallConductivity::new(&m_pos, 50.0)
.unwrap()
.compute(8, 8)
.unwrap();
let kxy_neg = MagnonHallConductivity::new(&m_neg, 50.0)
.unwrap()
.compute(8, 8)
.unwrap();
assert!(
kxy_pos * kxy_neg < 0.0,
"κ_xy sign should flip with DMI sign: {} vs {}",
kxy_pos,
kxy_neg
);
}
#[test]
fn temperature_sweep_correct_length() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.3, 0.0).unwrap();
let temps = vec![10.0, 50.0, 100.0, 200.0];
let mhc = MagnonHallConductivity::new(&m, 100.0).unwrap();
let sweep = mhc.temperature_sweep(&temps, 8, 8).unwrap();
assert_eq!(sweep.len(), 4);
for (i, &t) in temps.iter().enumerate() {
assert!((sweep[i].0 - t).abs() < 1e-10);
}
}
#[test]
fn invalid_temperature_rejected() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.1, 0.0).unwrap();
assert!(MagnonHallConductivity::new(&m, -1.0).is_err());
assert!(MagnonHallConductivity::new(&m, 0.0).is_err());
}
}