use std::f64::consts::PI;
use super::band_model::MagnonBandModel;
use crate::error::{self, Result};
use crate::math::{CMatrix, Complex};
pub struct BerryCurvature<'a> {
pub model: &'a MagnonBandModel,
pub dk: f64,
}
impl<'a> BerryCurvature<'a> {
pub fn new(model: &'a MagnonBandModel) -> Self {
Self { model, dk: 1e-4 }
}
pub fn with_step(mut self, dk: f64) -> Self {
self.dk = dk;
self
}
pub fn curvature_at(&self, k: (f64, f64), band_idx: usize) -> Result<f64> {
let nb = self.model.n_bands();
if band_idx >= nb {
return Err(error::invalid_param(
"band_idx",
"index exceeds number of bands",
));
}
let (kx, ky) = k;
let dk = self.dk;
let (evals, vecs) = self.model.diagonalize(k)?;
let h_px = self.model.hamiltonian_at((kx + dk, ky))?;
let h_mx = self.model.hamiltonian_at((kx - dk, ky))?;
let h_py = self.model.hamiltonian_at((kx, ky + dk))?;
let h_my = self.model.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)
}
pub fn compute_grid(
&self,
kx_pts: usize,
ky_pts: usize,
band_idx: usize,
) -> Result<Vec<Vec<f64>>> {
if band_idx >= self.model.n_bands() {
return Err(error::invalid_param(
"band_idx",
"index exceeds number of bands",
));
}
if kx_pts < 2 || ky_pts < 2 {
return Err(error::invalid_param("kx_pts/ky_pts", "must be at least 2"));
}
let mut grid = Vec::with_capacity(kx_pts);
for ix in 0..kx_pts {
let kx = -PI + 2.0 * PI * (ix as f64) / (kx_pts as f64);
let mut row = Vec::with_capacity(ky_pts);
for iy in 0..ky_pts {
let ky = -PI + 2.0 * PI * (iy as f64) / (ky_pts as f64);
let omega = self.curvature_at((kx, ky), band_idx)?;
row.push(omega);
}
grid.push(row);
}
Ok(grid)
}
pub fn integrate_brillouin(
&self,
kx_pts: usize,
ky_pts: usize,
band_idx: usize,
) -> Result<f64> {
let grid = self.compute_grid(kx_pts, ky_pts, band_idx)?;
let dkx = 2.0 * PI / (kx_pts as f64);
let dky = 2.0 * PI / (ky_pts as f64);
let cell_area = dkx * dky;
let mut sum = 0.0_f64;
for row in &grid {
for &omega in row {
sum += omega;
}
}
Ok(sum * cell_area / (2.0 * PI))
}
pub fn at(&self, band: usize, kx: f64, ky: f64) -> Result<f64> {
self.curvature_at((kx, ky), band)
}
pub fn chern_number_approx(&self, band: usize, n_grid: usize) -> Result<f64> {
self.integrate_brillouin(n_grid, n_grid, band)
}
}
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::topomagnon::band_model::MagnonBandModel;
#[test]
fn curvature_at_returns_finite() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.2, 0.1).unwrap();
let bc = BerryCurvature::new(&m);
let omega = bc.curvature_at((0.3, 0.4), 0).unwrap();
assert!(omega.is_finite(), "Berry curvature must be finite");
}
#[test]
fn curvature_antisymmetry() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.0, 0.0).unwrap();
let bc = BerryCurvature::new(&m);
let k = (0.5, 0.3);
let omega_k = bc.curvature_at(k, 0).unwrap();
let omega_mk = bc.curvature_at((-k.0, -k.1), 0).unwrap();
assert!(
(omega_k + omega_mk).abs() < 0.1,
"TR antisymmetry violated: Ω(k)={}, Ω(-k)={}",
omega_k,
omega_mk
);
}
#[test]
fn curvature_zero_for_trivial_band() {
let m = MagnonBandModel::square_dmi(1.0, 0.0, 0.0).unwrap();
let bc = BerryCurvature::new(&m);
let omega = bc.curvature_at((0.5, 0.7), 0).unwrap();
assert!(
omega.abs() < 1e-10,
"1-band curvature must be zero, got {}",
omega
);
}
#[test]
fn compute_grid_size_correct() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.1, 0.0).unwrap();
let bc = BerryCurvature::new(&m);
let grid = bc.compute_grid(10, 12, 0).unwrap();
assert_eq!(grid.len(), 10);
assert_eq!(grid[0].len(), 12);
}
#[test]
fn integrate_matches_chern_within_half() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.5, 0.0).unwrap();
let bc = BerryCurvature::new(&m);
let integral = bc.integrate_brillouin(25, 25, 0).unwrap();
let nearest_int = integral.round();
assert!(
(integral - nearest_int).abs() < 0.4,
"BZ integral {} not close to integer",
integral
);
}
#[test]
fn curvature_opposite_sign_bands() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.3, 0.0).unwrap();
let bc = BerryCurvature::new(&m);
let i0 = bc.integrate_brillouin(15, 15, 0).unwrap();
let i1 = bc.integrate_brillouin(15, 15, 1).unwrap();
assert!(
(i0 + i1).abs() < 0.3,
"Band sum rule violated: Ω_0+Ω_1 integral = {}",
i0 + i1
);
}
#[test]
fn invalid_band_idx_errors() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.1, 0.0).unwrap();
let bc = BerryCurvature::new(&m);
assert!(bc.curvature_at((0.0, 0.0), 5).is_err());
assert!(bc.compute_grid(5, 5, 5).is_err());
assert!(bc.integrate_brillouin(5, 5, 5).is_err());
}
#[test]
fn with_step_sets_dk() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.1, 0.0).unwrap();
let bc = BerryCurvature::new(&m).with_step(1e-5);
assert!((bc.dk - 1e-5).abs() < 1e-20);
}
}