use std::f64::consts::PI;
use crate::error::Result;
use crate::math::{CMatrix, Complex};
#[derive(Debug, Clone, PartialEq)]
pub enum LatticeType3D {
CubicHaldane,
PyrochloreTopological,
}
#[derive(Debug, Clone)]
pub struct MagnonBandModel3D {
pub lattice: LatticeType3D,
pub n_bands: usize,
pub j_nn: f64,
pub j_nnn: f64,
pub dmi: f64,
pub h_ext: f64,
pub a_lattice: f64,
}
impl MagnonBandModel3D {
pub fn cubic_haldane(j_nn: f64, j_nnn: f64, dmi: f64, h_ext: f64) -> Self {
Self {
lattice: LatticeType3D::CubicHaldane,
n_bands: 2,
j_nn,
j_nnn,
dmi,
h_ext,
a_lattice: 1.0,
}
}
pub fn pyrochlore_topological(j: f64, d: f64) -> Self {
Self {
lattice: LatticeType3D::PyrochloreTopological,
n_bands: 4,
j_nn: j,
j_nnn: 0.0,
dmi: d,
h_ext: 0.0,
a_lattice: 1.0,
}
}
pub fn hamiltonian_at(&self, kx: f64, ky: f64, kz: f64) -> CMatrix {
match self.lattice {
LatticeType3D::CubicHaldane => self.cubic_haldane_hamiltonian(kx, ky, kz),
LatticeType3D::PyrochloreTopological => self.pyrochlore_hamiltonian(kx, ky, kz),
}
}
fn cubic_haldane_hamiltonian(&self, kx: f64, ky: f64, kz: f64) -> CMatrix {
let diag_re = self.j_nn * (kx.cos() + ky.cos() + kz.cos());
let dx = self.dmi * kx.sin();
let dy = self.dmi * ky.sin();
let dz = self.dmi * kz.sin() + self.h_ext;
let mut mat = CMatrix::zeros(2);
mat.set(0, 0, Complex::from_real(diag_re + dz));
mat.set(1, 1, Complex::from_real(diag_re - dz));
mat.set(0, 1, Complex::new(dx, -dy));
mat.set(1, 0, Complex::new(dx, dy));
mat
}
fn pyrochlore_hamiltonian(&self, kx: f64, ky: f64, kz: f64) -> CMatrix {
let bonds: [(usize, usize, f64, f64, f64); 6] = [
(0, 1, 1.0, 0.0, 0.0),
(0, 2, 0.0, 1.0, 0.0),
(0, 3, 0.0, 0.0, 1.0),
(1, 2, -1.0, 1.0, 0.0),
(1, 3, -1.0, 0.0, 1.0),
(2, 3, 0.0, -1.0, 1.0),
];
let mut mat = CMatrix::zeros(4);
for i in 0..4 {
mat.set(i, i, Complex::from_real(self.h_ext + self.dmi));
}
for &(a, b, dx, dy, dz) in &bonds {
let phi = kx * dx + ky * dy + kz * dz;
let hop = Complex::from_real(self.j_nn).add(&Complex::from_polar(self.j_nn, phi));
mat.set(a, b, hop);
mat.set(b, a, hop.conj());
}
mat
}
pub fn diagonalize_3d(&self, kx: f64, ky: f64, kz: f64) -> Result<(Vec<f64>, CMatrix)> {
let h = self.hamiltonian_at(kx, ky, kz);
h.hermitian_eigendecomposition()
}
#[inline]
pub fn n_bands(&self) -> usize {
self.n_bands
}
pub fn band_gap_3d(&self, n: usize) -> f64 {
let n = n.max(4);
let mut min_gap = f64::INFINITY;
for ix in 0..n {
let kx = 2.0 * PI * (ix as f64) / (n as f64);
for iy in 0..n {
let ky = 2.0 * PI * (iy as f64) / (n as f64);
for iz in 0..n {
let kz = 2.0 * PI * (iz as f64) / (n as f64);
if let Ok((evals, _)) = self.diagonalize_3d(kx, ky, kz) {
let nb = evals.len();
if nb >= 2 {
let gap = evals[nb / 2] - evals[nb / 2 - 1];
if gap < min_gap {
min_gap = gap;
}
}
}
}
}
}
if min_gap.is_infinite() {
0.0
} else {
min_gap
}
}
pub fn is_topological_cubic(&self) -> bool {
matches!(self.lattice, LatticeType3D::CubicHaldane) && self.dmi.abs() > self.j_nn.abs()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cubic_haldane_n_bands_is_2() {
let m = MagnonBandModel3D::cubic_haldane(1.0, 0.0, 0.5, 0.0);
assert_eq!(m.n_bands(), 2);
}
#[test]
fn pyrochlore_n_bands_is_4() {
let m = MagnonBandModel3D::pyrochlore_topological(1.0, 0.3);
assert_eq!(m.n_bands(), 4);
}
#[test]
fn cubic_haldane_hamiltonian_hermitian() {
let m = MagnonBandModel3D::cubic_haldane(1.0, 0.0, 0.5, 0.1);
for (kx, ky, kz) in [(0.1, 0.2, 0.3), (PI / 3.0, PI / 4.0, 0.5)] {
let h = m.hamiltonian_at(kx, ky, kz);
let hd = h.conj_transpose();
let diff = h.sub(&hd).unwrap();
assert!(
diff.frobenius_norm() < 1e-12,
"H not Hermitian at ({kx},{ky},{kz})"
);
}
}
#[test]
fn pyrochlore_hamiltonian_hermitian() {
let m = MagnonBandModel3D::pyrochlore_topological(1.0, 0.3);
for (kx, ky, kz) in [(0.0, 0.0, 0.0), (0.5, 0.7, 1.0)] {
let h = m.hamiltonian_at(kx, ky, kz);
let hd = h.conj_transpose();
let diff = h.sub(&hd).unwrap();
assert!(
diff.frobenius_norm() < 1e-12,
"Pyrochlore H not Hermitian at ({kx},{ky},{kz})"
);
}
}
#[test]
fn cubic_haldane_diagonalizes_correctly() {
let m = MagnonBandModel3D::cubic_haldane(1.0, 0.0, 0.5, 0.0);
let (evals, _) = m.diagonalize_3d(0.0, 0.0, 0.0).unwrap();
assert_eq!(evals.len(), 2);
assert!((evals[0] - 3.0).abs() < 1e-10, "E[0]={}", evals[0]);
assert!((evals[1] - 3.0).abs() < 1e-10, "E[1]={}", evals[1]);
}
#[test]
fn pyrochlore_diagonalizes_four_bands() {
let m = MagnonBandModel3D::pyrochlore_topological(1.0, 0.2);
let (evals, evecs) = m.diagonalize_3d(0.1, 0.2, 0.3).unwrap();
assert_eq!(evals.len(), 4);
assert_eq!(evecs.n(), 4);
for i in 0..3 {
assert!(
evals[i] <= evals[i + 1],
"Eigenvalues not sorted: {evals:?}"
);
}
}
#[test]
fn cubic_haldane_gap_finite_with_field() {
let m = MagnonBandModel3D::cubic_haldane(1.0, 0.0, 0.0, 0.5);
let gap = m.band_gap_3d(6);
assert!(gap >= 0.0, "Gap negative: {gap}");
}
#[test]
fn cubic_haldane_topological_phase_detected() {
let m = MagnonBandModel3D::cubic_haldane(0.5, 0.0, 1.0, 0.0);
assert!(m.is_topological_cubic());
let m2 = MagnonBandModel3D::cubic_haldane(1.0, 0.0, 0.3, 0.0);
assert!(!m2.is_topological_cubic());
}
}