use std::f64::consts::PI;
use super::band_model::MagnonBandModel;
use crate::error::{self, Result};
use crate::math::Complex;
pub struct ChernNumber<'a> {
pub model: &'a MagnonBandModel,
}
impl<'a> ChernNumber<'a> {
pub fn new(model: &'a MagnonBandModel) -> Self {
Self { model }
}
pub fn compute(&self, band_idx: usize, nx: usize, ny: usize) -> Result<i32> {
let nb = self.model.n_bands();
if band_idx >= nb {
return Err(error::invalid_param(
"band_idx",
"index exceeds number of bands",
));
}
if nx < 5 || ny < 5 {
return Err(error::invalid_param(
"nx/ny",
"grid must be at least 5×5 for reliable Chern number",
));
}
let states = self.build_eigenstates(nx, ny, band_idx)?;
let mut flux_sum = 0.0_f64;
for ix in 0..nx {
let ix1 = (ix + 1) % nx;
for iy in 0..ny {
let iy1 = (iy + 1) % ny;
let u_x = link_variable(&states[ix][iy], &states[ix1][iy]);
let u_y = link_variable(&states[ix][iy], &states[ix][iy1]);
let u_x_py = link_variable(&states[ix][iy1], &states[ix1][iy1]);
let u_y_px = link_variable(&states[ix1][iy], &states[ix1][iy1]);
let plaquette = u_x.mul(&u_y_px).mul(&u_x_py.conj()).mul(&u_y.conj());
flux_sum += plaquette.phase();
}
}
let chern_real = flux_sum / (2.0 * PI);
let chern_int = chern_real.round() as i32;
if (chern_real - chern_int as f64).abs() > 0.1 {
return Err(error::numerical_error(&format!(
"Chern number not close to integer: {:.4} (try finer grid)",
chern_real
)));
}
Ok(chern_int)
}
pub fn build_eigenstates(
&self,
nx: usize,
ny: usize,
band_idx: usize,
) -> Result<Vec<Vec<Vec<Complex>>>> {
let mut states = Vec::with_capacity(nx + 1);
for ix in 0..=nx {
let kx = -PI + 2.0 * PI * (ix as f64) / (nx as f64);
let mut row = Vec::with_capacity(ny + 1);
for iy in 0..=ny {
let ky = -PI + 2.0 * PI * (iy as f64) / (ny as f64);
let (_, vecs) = self.model.diagonalize((kx, ky))?;
row.push(vecs.column(band_idx));
}
states.push(row);
}
Ok(states)
}
pub fn wilson_loop(&self, band_idx: usize, kx: f64, nky: usize) -> Result<Complex> {
let nb = self.model.n_bands();
if band_idx >= nb {
return Err(error::invalid_param(
"band_idx",
"index exceeds number of bands",
));
}
if nky < 4 {
return Err(error::invalid_param("nky", "need at least 4 ky points"));
}
let mut states: Vec<Vec<Complex>> = Vec::with_capacity(nky + 1);
for iy in 0..=nky {
let ky = -PI + 2.0 * PI * (iy as f64) / (nky as f64);
let (_, vecs) = self.model.diagonalize((kx, ky))?;
states.push(vecs.column(band_idx));
}
let mut wilson = Complex::ONE;
for iy in 0..nky {
let u = link_variable(&states[iy], &states[iy + 1]);
wilson = wilson.mul(&u);
}
Ok(wilson)
}
pub fn berry_phase_along_path(&self, band_idx: usize, path: &[(f64, f64)]) -> Result<f64> {
let nb = self.model.n_bands();
if band_idx >= nb {
return Err(error::invalid_param(
"band_idx",
"index exceeds number of bands",
));
}
if path.len() < 3 {
return Err(error::invalid_param(
"path",
"need at least 3 k-points for a closed loop",
));
}
let mut states: Vec<Vec<Complex>> = Vec::with_capacity(path.len() + 1);
for &kpt in path {
let (_, vecs) = self.model.diagonalize(kpt)?;
states.push(vecs.column(band_idx));
}
{
let (_, vecs) = self.model.diagonalize(path[0])?;
states.push(vecs.column(band_idx));
}
let mut product = Complex::ONE;
let n = states.len() - 1;
for i in 0..n {
let u = link_variable(&states[i], &states[i + 1]);
product = product.mul(&u);
}
Ok(product.phase())
}
pub fn total_chern_sum(&self, nx: usize, ny: usize) -> Result<i32> {
let nb = self.model.n_bands();
let mut total = 0_i32;
for band in 0..nb {
total += self.compute(band, nx, ny)?;
}
Ok(total)
}
}
fn link_variable(u_a: &[Complex], u_b: &[Complex]) -> Complex {
let mut inner = Complex::ZERO;
for (a, b) in u_a.iter().zip(u_b.iter()) {
inner = inner.add(&a.conj().mul(b));
}
let norm = inner.norm();
if norm < 1e-15 {
Complex::ONE
} else {
inner.scale(1.0 / norm)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::topomagnon::band_model::MagnonBandModel;
#[test]
fn chern_zero_dmi_honeycomb() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.0, 1e-3).unwrap();
let cn = ChernNumber::new(&m);
if let Ok(c) = cn.compute(0, 15, 15) {
assert_eq!(c, 0, "Expected C=0 for near-trivial honeycomb");
}
}
#[test]
fn chern_nonzero_dmi_honeycomb() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.5, 0.0).unwrap();
let cn = ChernNumber::new(&m);
let c = cn.compute(0, 20, 20).unwrap();
assert_eq!(
c.abs(),
1,
"Expected |C|=1 for topological honeycomb, got {}",
c
);
}
#[test]
fn chern_sign_flips_with_dmi_sign() {
let m_pos = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.5, 0.0).unwrap();
let m_neg = MagnonBandModel::honeycomb_haldane(1.0, 0.0, -0.5, 0.0).unwrap();
let c_pos = ChernNumber::new(&m_pos).compute(0, 20, 20).unwrap();
let c_neg = ChernNumber::new(&m_neg).compute(0, 20, 20).unwrap();
assert_eq!(
c_pos, -c_neg,
"Chern sign should flip: {} vs {}",
c_pos, c_neg
);
}
#[test]
fn chern_integer_quantized() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.4, 0.0).unwrap();
let cn = ChernNumber::new(&m);
let states = cn.build_eigenstates(20, 20, 0).unwrap();
let nx = 20;
let ny = 20;
let mut flux_sum = 0.0_f64;
for ix in 0..nx {
let ix1 = (ix + 1) % nx;
for iy in 0..ny {
let iy1 = (iy + 1) % ny;
let u_x = link_variable(&states[ix][iy], &states[ix1][iy]);
let u_y = link_variable(&states[ix][iy], &states[ix][iy1]);
let u_x_py = link_variable(&states[ix][iy1], &states[ix1][iy1]);
let u_y_px = link_variable(&states[ix1][iy], &states[ix1][iy1]);
let plaquette = u_x.mul(&u_y_px).mul(&u_x_py.conj()).mul(&u_y.conj());
flux_sum += plaquette.phase();
}
}
let chern_real = flux_sum / (2.0 * PI);
assert!(
(chern_real - chern_real.round()).abs() < 0.05,
"Chern not quantized: {:.4}",
chern_real
);
}
#[test]
fn total_chern_sum_zero() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.5, 0.0).unwrap();
let cn = ChernNumber::new(&m);
let total = cn.total_chern_sum(20, 20).unwrap();
assert_eq!(total, 0, "Total Chern sum must be zero, got {}", total);
}
#[test]
fn wilson_loop_finite() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.3, 0.0).unwrap();
let cn = ChernNumber::new(&m);
let w = cn.wilson_loop(0, 0.0, 20).unwrap();
assert!(w.norm().is_finite());
assert!(
(w.norm() - 1.0).abs() < 1e-10,
"Wilson loop not unit modulus"
);
}
#[test]
fn berry_phase_closed_loop_multiple_of_pi() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.0, 0.0).unwrap();
let cn = ChernNumber::new(&m);
let path = vec![(0.1, 0.1), (-0.1, 0.1), (-0.1, -0.1), (0.1, -0.1)];
let phase = cn.berry_phase_along_path(0, &path).unwrap();
let normalized = phase.abs() / PI;
let nearest_half = (normalized * 2.0).round() / 2.0;
assert!(
(normalized - nearest_half).abs() < 0.15,
"Berry phase not multiple of π/2: {:.4}π",
phase / PI
);
}
#[test]
fn invalid_band_idx_errors() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.1, 0.0).unwrap();
let cn = ChernNumber::new(&m);
assert!(cn.compute(5, 10, 10).is_err());
assert!(cn.wilson_loop(5, 0.0, 10).is_err());
assert!(cn
.berry_phase_along_path(5, &[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)])
.is_err());
}
#[test]
fn nx_ny_minimum() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.3, 0.0).unwrap();
let cn = ChernNumber::new(&m);
assert!(cn.compute(0, 3, 3).is_err());
assert!(cn.compute(0, 5, 3).is_err());
let _ = cn.compute(0, 5, 5);
}
#[test]
fn kagome_middle_band() {
let m = MagnonBandModel::kagome(1.0, 0.4, 0.0).unwrap();
let cn = ChernNumber::new(&m);
let c = cn.compute(1, 15, 15).unwrap();
assert!(c.abs() <= 5, "Chern number suspiciously large: {}", c);
}
}