use crate::error::{self, Result};
use crate::math::{CMatrix, Complex};
use super::bloch_hamiltonian::BlochHamiltonian;
pub struct KuboBerry<'a> {
pub hamiltonian: &'a dyn BlochHamiltonian,
pub dk: f64,
}
impl<'a> KuboBerry<'a> {
pub fn new(hamiltonian: &'a dyn BlochHamiltonian) -> Self {
Self {
hamiltonian,
dk: 1e-4,
}
}
pub fn with_step(mut self, dk: f64) -> Self {
self.dk = dk;
self
}
pub fn curvature_at(&self, kx: f64, ky: f64, band_idx: usize) -> Result<f64> {
let nb = self.hamiltonian.n_bands();
if band_idx >= nb {
return Err(error::invalid_param(
"band_idx",
"index exceeds number of bands",
));
}
let dk = self.dk;
let (evals, vecs) = self.hamiltonian.diagonalize_at(kx, ky)?;
let h_px = self.hamiltonian.hamiltonian_at(kx + dk, ky)?;
let h_mx = self.hamiltonian.hamiltonian_at(kx - dk, ky)?;
let h_py = self.hamiltonian.hamiltonian_at(kx, ky + dk)?;
let h_my = self.hamiltonian.hamiltonian_at(kx, ky - dk)?;
let dh_x = h_px.sub(&h_mx)?.scale_real(1.0 / (2.0 * dk));
let dh_y = h_py.sub(&h_my)?.scale_real(1.0 / (2.0 * dk));
let eps_n = evals[band_idx];
let mut omega = 0.0_f64;
for (m, &eps_m) in evals.iter().enumerate() {
if m == band_idx {
continue;
}
let denom = eps_n - eps_m;
if denom.abs() < 1e-10 {
continue; }
let v_n = vecs.column(band_idx);
let v_m = vecs.column(m);
let mx = matrix_element(&v_n, &dh_x, &v_m, nb);
let my = matrix_element(&v_m, &dh_y, &v_n, nb);
let prod = mx.mul(&my);
omega += -2.0 * prod.im / (denom * denom);
}
Ok(omega)
}
}
fn matrix_element(v_bra: &[Complex], m: &CMatrix, v_ket: &[Complex], n: usize) -> Complex {
let mut mv = vec![Complex::ZERO; n];
for (i, mv_i) in mv.iter_mut().enumerate() {
*mv_i = v_ket
.iter()
.enumerate()
.fold(Complex::ZERO, |acc, (j, &vkj)| {
acc.add(&m.get(i, j).mul(&vkj))
});
}
v_bra
.iter()
.zip(mv.iter())
.fold(Complex::ZERO, |acc, (&bra_i, &mv_i)| {
acc.add(&bra_i.conj().mul(&mv_i))
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::altermagnet::band_model::{AltermagnetBandModel, Band, Spin};
use crate::altermagnet::bloch_hamiltonian::AltermagnetSpinHamiltonian;
use crate::altermagnet::materials::AltermagneticSymmetry;
fn soc_model() -> AltermagnetBandModel {
AltermagnetBandModel::new(
AltermagneticSymmetry::DWave,
1.0,
0.3,
0.8,
0.6,
0.4,
0.5,
0.0,
1.0,
)
.expect("parameters are valid")
}
fn no_soc_model() -> AltermagnetBandModel {
AltermagnetBandModel::new(
AltermagneticSymmetry::DWave,
1.0,
0.3,
0.8,
0.6,
0.0,
0.5,
0.0,
1.0,
)
.expect("parameters are valid")
}
#[test]
fn curvature_at_returns_finite() {
let model = soc_model();
let h = AltermagnetSpinHamiltonian::new(&model, Spin::Up);
let kubo = KuboBerry::new(&h);
let omega = kubo.curvature_at(0.4, 0.15, 0).expect("valid");
assert!(omega.is_finite());
}
#[test]
fn invalid_band_idx_errors() {
let model = soc_model();
let h = AltermagnetSpinHamiltonian::new(&model, Spin::Up);
let kubo = KuboBerry::new(&h);
assert!(kubo.curvature_at(0.0, 0.0, 2).is_err());
assert!(kubo.curvature_at(0.0, 0.0, 5).is_err());
}
#[test]
fn with_step_sets_dk() {
let model = soc_model();
let h = AltermagnetSpinHamiltonian::new(&model, Spin::Up);
let kubo = KuboBerry::new(&h).with_step(1e-5);
assert!((kubo.dk - 1e-5).abs() < 1e-20);
}
#[test]
fn kubo_matches_closed_form_with_soc() {
let model = soc_model();
for spin in [Spin::Up, Spin::Down] {
let h = AltermagnetSpinHamiltonian::new(&model, spin);
let kubo = KuboBerry::new(&h);
for &(kx, ky) in &[(0.4, 0.15), (0.9, -0.6), (-0.3, 0.5)] {
let closed_lower = model.berry_curvature(kx, ky, spin, Band::Lower);
let closed_upper = model.berry_curvature(kx, ky, spin, Band::Upper);
let numeric_lower = kubo.curvature_at(kx, ky, 0).expect("valid");
let numeric_upper = kubo.curvature_at(kx, ky, 1).expect("valid");
let scale = closed_lower.abs().max(closed_upper.abs()).max(1e-6);
assert!(
(closed_lower - numeric_lower).abs() / scale < 1e-3,
"lower band mismatch at k=({kx},{ky}), spin={spin:?}: closed={closed_lower}, kubo={numeric_lower}"
);
assert!(
(closed_upper - numeric_upper).abs() / scale < 1e-3,
"upper band mismatch at k=({kx},{ky}), spin={spin:?}: closed={closed_upper}, kubo={numeric_upper}"
);
}
}
}
#[test]
fn kubo_and_closed_form_both_vanish_without_soc() {
let model = no_soc_model();
let h = AltermagnetSpinHamiltonian::new(&model, Spin::Up);
let kubo = KuboBerry::new(&h);
for &(kx, ky) in &[(0.4, 0.15), (0.9, -0.6)] {
let closed = model.berry_curvature(kx, ky, Spin::Up, Band::Lower);
let numeric = kubo.curvature_at(kx, ky, 0).expect("valid");
assert!(closed.abs() < 1e-9, "closed form should vanish: {closed}");
assert!(
numeric.abs() < 1e-6,
"Kubo cross-check should vanish: {numeric}"
);
}
}
}