use std::f64::consts::PI;
use crate::error::{self, Result};
use crate::math::{CMatrix, Complex};
fn diagonalize_1x1(h: &CMatrix) -> Result<(Vec<f64>, CMatrix)> {
let e = h.get(0, 0).re;
let mut vecs = CMatrix::zeros(1);
vecs.set(0, 0, Complex::ONE);
Ok((vec![e], vecs))
}
fn diagonalize_2x2(h: &CMatrix) -> Result<(Vec<f64>, CMatrix)> {
let a = h.get(0, 0).re;
let c = h.get(1, 1).re;
let b = h.get(0, 1);
let half_sum = (a + c) * 0.5;
let half_diff = (a - c) * 0.5;
let disc = (half_diff * half_diff + b.norm_sq()).sqrt();
let e_lo = half_sum - disc;
let e_hi = half_sum + disc;
let (v0, v1) = if b.norm() < 1e-14 {
if a <= c {
(
vec![Complex::ONE, Complex::ZERO],
vec![Complex::ZERO, Complex::ONE],
)
} else {
(
vec![Complex::ZERO, Complex::ONE],
vec![Complex::ONE, Complex::ZERO],
)
}
} else {
let vlo = [b.neg(), Complex::from_real(a - e_lo)];
let norm_lo = (vlo[0].norm_sq() + vlo[1].norm_sq()).sqrt();
let vlo = vec![vlo[0].scale(1.0 / norm_lo), vlo[1].scale(1.0 / norm_lo)];
let vhi = [b.neg(), Complex::from_real(a - e_hi)];
let norm_hi = (vhi[0].norm_sq() + vhi[1].norm_sq()).sqrt();
let vhi = if norm_hi < 1e-14 {
vec![vlo[1].conj(), vlo[0].neg().conj()]
} else {
vec![vhi[0].scale(1.0 / norm_hi), vhi[1].scale(1.0 / norm_hi)]
};
(vlo, vhi)
};
let mut vecs = CMatrix::zeros(2);
vecs.set(0, 0, v0[0]);
vecs.set(1, 0, v0[1]);
vecs.set(0, 1, v1[0]);
vecs.set(1, 1, v1[1]);
Ok((vec![e_lo, e_hi], vecs))
}
fn diagonalize_3x3_hermitian(h: &CMatrix) -> Result<(Vec<f64>, CMatrix)> {
use std::f64::consts::PI;
let h00 = h.get(0, 0).re;
let h11 = h.get(1, 1).re;
let h22 = h.get(2, 2).re;
let h01 = h.get(0, 1);
let h02 = h.get(0, 2);
let h12 = h.get(1, 2);
let tr = h00 + h11 + h22;
let shift = tr / 3.0;
let a00 = h00 - shift;
let a11 = h11 - shift;
let a22 = h22 - shift;
let p_val =
(a00 * a00 + a11 * a11 + a22 * a22 + 2.0 * (h01.norm_sq() + h02.norm_sq() + h12.norm_sq()))
/ 6.0;
if p_val < 1e-28 {
return Ok((vec![shift, shift, shift], CMatrix::eye(3)));
}
let p = p_val.sqrt();
let cof01 = h01.scale(a22).sub(&h12.mul(&h02.conj()));
let cof02 = h01
.mul(&h12.conj())
.sub(&Complex::from_real(a11).mul(&h02.conj()));
let det_a =
a00 * (a11 * a22 - h12.norm_sq()) - h01.conj().mul(&cof01).re + h02.conj().mul(&cof02).re;
let q = det_a / 2.0;
let arg = (q / (p * p * p)).clamp(-1.0, 1.0);
let phi = arg.acos() / 3.0;
let mut evals_a = [
2.0 * p * phi.cos(),
2.0 * p * (phi + 2.0 * PI / 3.0).cos(),
2.0 * p * (phi + 4.0 * PI / 3.0).cos(),
];
evals_a.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let eigenvalues: Vec<f64> = evals_a.iter().map(|&mu| mu + shift).collect();
let eigenvectors = compute_3x3_eigenvectors(h, &eigenvalues)?;
Ok((eigenvalues, eigenvectors))
}
fn compute_3x3_eigenvectors(h: &CMatrix, eigenvalues: &[f64]) -> Result<CMatrix> {
let mut vecs = CMatrix::zeros(3);
let mut basis: Vec<Vec<Complex>> = Vec::with_capacity(3);
for (col, &lambda) in eigenvalues.iter().enumerate() {
let mut rows = [[Complex::ZERO; 3]; 3];
for (i, row) in rows.iter_mut().enumerate() {
for (j, cell) in row.iter_mut().enumerate() {
*cell = h.get(i, j);
if i == j {
*cell = cell.sub(&Complex::from_real(lambda));
}
}
}
let v = cross_product_3c(&rows[0], &rows[1]);
let norm = v.iter().map(|c| c.norm_sq()).sum::<f64>().sqrt();
let mut vec = if norm > 1e-12 {
v.iter().map(|c| c.scale(1.0 / norm)).collect::<Vec<_>>()
} else {
let v2 = cross_product_3c(&rows[0], &rows[2]);
let n2 = v2.iter().map(|c| c.norm_sq()).sum::<f64>().sqrt();
if n2 > 1e-12 {
v2.iter().map(|c| c.scale(1.0 / n2)).collect::<Vec<_>>()
} else {
let v3 = cross_product_3c(&rows[1], &rows[2]);
let n3 = v3.iter().map(|c| c.norm_sq()).sum::<f64>().sqrt();
if n3 > 1e-12 {
v3.iter().map(|c| c.scale(1.0 / n3)).collect::<Vec<_>>()
} else {
standard_basis_complement(&basis, 3)
}
}
};
for prev in &basis {
let dot: Complex = vec
.iter()
.zip(prev.iter())
.map(|(&a, &b)| b.conj().mul(&a))
.fold(Complex::ZERO, |acc, x| acc.add(&x));
for (v_elem, &p_elem) in vec.iter_mut().zip(prev.iter()) {
*v_elem = v_elem.sub(&p_elem.mul(&dot));
}
let n = vec.iter().map(|c| c.norm_sq()).sum::<f64>().sqrt();
if n > 1e-12 {
vec.iter_mut().for_each(|c| *c = c.scale(1.0 / n));
}
}
let n_final = vec.iter().map(|c| c.norm_sq()).sum::<f64>().sqrt();
if n_final > 1e-12 {
vec.iter_mut().for_each(|c| *c = c.scale(1.0 / n_final));
}
for (row, &val) in vec.iter().enumerate() {
vecs.set(row, col, val);
}
basis.push(vec);
}
Ok(vecs)
}
fn cross_product_3c(a: &[Complex; 3], b: &[Complex; 3]) -> Vec<Complex> {
vec![
a[1].mul(&b[2]).sub(&a[2].mul(&b[1])),
a[2].mul(&b[0]).sub(&a[0].mul(&b[2])),
a[0].mul(&b[1]).sub(&a[1].mul(&b[0])),
]
}
fn standard_basis_complement(basis: &[Vec<Complex>], n: usize) -> Vec<Complex> {
let mut best = vec![Complex::ZERO; n];
let mut best_norm = -1.0_f64;
for i in 0..n {
let mut candidate = vec![Complex::ZERO; n];
candidate[i] = Complex::ONE;
for prev in basis {
let dot: Complex = candidate
.iter()
.zip(prev.iter())
.map(|(&a, &b)| b.conj().mul(&a))
.fold(Complex::ZERO, |acc, x| acc.add(&x));
for (c, &p) in candidate.iter_mut().zip(prev.iter()) {
*c = c.sub(&p.mul(&dot));
}
}
let nm = candidate.iter().map(|c| c.norm_sq()).sum::<f64>().sqrt();
if nm > best_norm {
best_norm = nm;
if nm > 1e-12 {
best = candidate.iter().map(|c| c.scale(1.0 / nm)).collect();
} else {
best = candidate;
}
}
}
best
}
const SQRT3: f64 = 1.732_050_808_568_877_3;
#[inline]
fn honeycomb_nn_phase(kx: f64, ky: f64) -> Complex {
let p0 = Complex::ONE;
let p1 = Complex::new(0.0, kx).exp();
let p2 = Complex::new(0.0, kx * 0.5 + ky * SQRT3 * 0.5).exp();
p0.add(&p1).add(&p2)
}
#[inline]
fn honeycomb_nnn_dz(kx: f64, ky: f64, dmi: f64, h_ext: f64) -> f64 {
let phi1 = kx;
let phi2 = kx * 0.5 + ky * SQRT3 * 0.5;
let phi3 = kx * 0.5 - ky * SQRT3 * 0.5;
h_ext + 2.0 * dmi * (phi1.sin() + phi2.sin() + phi3.sin())
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LatticeType {
HoneycombHaldane,
Kagome,
SquareDmi,
}
#[derive(Debug, Clone)]
pub struct MagnonBandModel {
pub lattice: LatticeType,
pub n_bands: usize,
pub j_nn: f64,
pub j_nnn: f64,
pub dmi: f64,
pub h_ext: f64,
pub a_lattice: f64,
}
impl MagnonBandModel {
pub fn honeycomb_haldane(j_nn: f64, j_nnn: f64, dmi: f64, h_ext: f64) -> Result<Self> {
if j_nn <= 0.0 {
return Err(error::invalid_param("j_nn", "must be positive"));
}
Ok(Self {
lattice: LatticeType::HoneycombHaldane,
n_bands: 2,
j_nn,
j_nnn,
dmi,
h_ext,
a_lattice: 3.0e-10, })
}
pub fn kagome(j: f64, dmi: f64, h_ext: f64) -> Result<Self> {
if j <= 0.0 {
return Err(error::invalid_param("j", "must be positive"));
}
Ok(Self {
lattice: LatticeType::Kagome,
n_bands: 3,
j_nn: j,
j_nnn: 0.0,
dmi,
h_ext,
a_lattice: 5.0e-10,
})
}
pub fn square_dmi(j: f64, dmi: f64, h_ext: f64) -> Result<Self> {
if j <= 0.0 {
return Err(error::invalid_param("j", "must be positive"));
}
Ok(Self {
lattice: LatticeType::SquareDmi,
n_bands: 1,
j_nn: j,
j_nnn: 0.0,
dmi,
h_ext,
a_lattice: 4.0e-10,
})
}
#[inline]
pub fn hamiltonian_at(&self, k: (f64, f64)) -> Result<CMatrix> {
let (kx, ky) = k;
match &self.lattice {
LatticeType::HoneycombHaldane => self.hamiltonian_honeycomb(kx, ky),
LatticeType::Kagome => self.hamiltonian_kagome(kx, ky),
LatticeType::SquareDmi => self.hamiltonian_square(kx, ky),
}
}
fn hamiltonian_honeycomb(&self, kx: f64, ky: f64) -> Result<CMatrix> {
let f_k = honeycomb_nn_phase(kx, ky);
let dz = honeycomb_nnn_dz(kx, ky, self.dmi, self.h_ext);
let phi1 = kx;
let phi2 = kx * 0.5 + ky * SQRT3 * 0.5;
let phi3 = kx * 0.5 - ky * SQRT3 * 0.5;
let h0_nnn = -self.j_nnn * (phi1.cos() + phi2.cos() + phi3.cos());
let h00 = Complex::from_real(h0_nnn + dz);
let h11 = Complex::from_real(h0_nnn - dz);
let h01 = f_k.conj().scale(self.j_nn);
let h10 = f_k.scale(self.j_nn);
CMatrix::from_rows(vec![vec![h00, h01], vec![h10, h11]])
}
fn hamiltonian_kagome(&self, kx: f64, ky: f64) -> Result<CMatrix> {
let eps0 = self.h_ext;
let phi12 = kx * 0.5;
let phi13 = kx * 0.25 - ky * SQRT3 * 0.25;
let phi23 = kx * 0.25 + ky * SQRT3 * 0.25;
let h01 = Complex::new(-2.0 * self.j_nn * phi12.cos(), self.dmi * phi12.sin());
let h02 = Complex::new(-2.0 * self.j_nn * phi13.cos(), self.dmi * phi13.sin());
let h12 = Complex::new(-2.0 * self.j_nn * phi23.cos(), self.dmi * phi23.sin());
let diag = Complex::from_real(eps0);
CMatrix::from_rows(vec![
vec![diag, h01, h02],
vec![h01.conj(), diag, h12],
vec![h02.conj(), h12.conj(), diag],
])
}
fn hamiltonian_square(&self, kx: f64, ky: f64) -> Result<CMatrix> {
let eps = self.h_ext + 2.0 * self.j_nn * (kx.cos() + ky.cos());
CMatrix::from_rows(vec![vec![Complex::from_real(eps)]])
}
pub fn diagonalize(&self, k: (f64, f64)) -> Result<(Vec<f64>, CMatrix)> {
let h = self.hamiltonian_at(k)?;
match h.n() {
1 => diagonalize_1x1(&h),
2 => diagonalize_2x2(&h),
3 => diagonalize_3x3_hermitian(&h),
_ => h.hermitian_eigendecomposition(),
}
}
pub fn bands(&self, kx_pts: usize, ky_pts: usize) -> Result<Vec<Vec<Vec<f64>>>> {
if kx_pts < 2 || ky_pts < 2 {
return Err(error::invalid_param("kx_pts/ky_pts", "must be at least 2"));
}
let mut result = 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 (evals, _) = self.diagonalize((kx, ky))?;
row.push(evals);
}
result.push(row);
}
Ok(result)
}
pub fn band_gap(
&self,
band_below: usize,
band_above: usize,
kx_pts: usize,
ky_pts: usize,
) -> Result<f64> {
if band_below >= self.n_bands {
return Err(error::invalid_param("band_below", "index out of range"));
}
if band_above >= self.n_bands {
return Err(error::invalid_param("band_above", "index out of range"));
}
if band_above <= band_below {
return Err(error::invalid_param(
"band_above",
"must be greater than band_below",
));
}
let bs = self.bands(kx_pts, ky_pts)?;
let mut min_gap = f64::INFINITY;
for row in &bs {
for evals in row {
let gap = evals[band_above] - evals[band_below];
if gap < min_gap {
min_gap = gap;
}
}
}
Ok(min_gap)
}
#[inline]
pub fn n_bands(&self) -> usize {
self.n_bands
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn honeycomb_two_bands() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.1, 0.0).unwrap();
assert_eq!(m.n_bands(), 2);
let h = m.hamiltonian_at((0.1, 0.2)).unwrap();
assert_eq!(h.n(), 2);
}
#[test]
fn kagome_three_bands() {
let m = MagnonBandModel::kagome(1.0, 0.05, 0.0).unwrap();
assert_eq!(m.n_bands(), 3);
let h = m.hamiltonian_at((0.0, 0.0)).unwrap();
assert_eq!(h.n(), 3);
}
#[test]
fn square_one_band() {
let m = MagnonBandModel::square_dmi(1.0, 0.0, 0.0).unwrap();
assert_eq!(m.n_bands(), 1);
let h = m.hamiltonian_at((0.5, 0.5)).unwrap();
assert_eq!(h.n(), 1);
}
#[test]
fn hamiltonian_hermitian() {
let models = vec![
MagnonBandModel::honeycomb_haldane(1.0, 0.1, 0.15, 0.2).unwrap(),
MagnonBandModel::kagome(2.0, 0.3, 0.1).unwrap(),
MagnonBandModel::square_dmi(1.5, 0.2, 0.05).unwrap(),
];
let kpoints = [(0.0, 0.0), (PI / 3.0, PI / 4.0), (1.1, -0.7)];
for m in &models {
for &kpt in &kpoints {
let h = m.hamiltonian_at(kpt).unwrap();
let hd = h.conj_transpose();
let diff = h.sub(&hd).unwrap();
assert!(
diff.frobenius_norm() < 1e-12,
"H not Hermitian for {:?} at k={:?}",
m.lattice,
kpt
);
}
}
}
#[test]
fn diagonalize_sorted_ascending() {
let m = MagnonBandModel::kagome(1.0, 0.1, 0.0).unwrap();
for (kx_i, ky_i) in [(0.0f64, 0.0), (0.5, 0.7), (1.2, -0.3)] {
let (evals, _) = m.diagonalize((kx_i, ky_i)).unwrap();
for w in evals.windows(2) {
assert!(w[0] <= w[1] + 1e-12, "Eigenvalues not sorted: {:?}", evals);
}
}
}
#[test]
fn eigenvectors_orthonormal() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.2, 0.1).unwrap();
let (_, vecs) = m.diagonalize((0.3, 0.4)).unwrap();
let n = m.n_bands();
for i in 0..n {
for j in 0..n {
let dot: Complex = (0..n)
.map(|r| vecs.get(r, i).conj().mul(&vecs.get(r, j)))
.fold(Complex::ZERO, |acc, x| acc.add(&x));
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
approx(dot.re, expected, 1e-9),
"V†V[{},{}] re off: got {}",
i,
j,
dot.re
);
assert!(approx(dot.im, 0.0, 1e-9), "V†V[{},{}] im off", i, j);
}
}
}
#[test]
fn honeycomb_zero_dmi_band_gap_zero() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.0, 0.0).unwrap();
let k_x = 4.0 * PI / 3.0;
let (evals, _) = m.diagonalize((k_x, 0.0)).unwrap();
let gap_at_k = evals[1] - evals[0];
assert!(
gap_at_k < 1e-10,
"Expected zero gap at K point without DMI, got {}",
gap_at_k
);
}
#[test]
fn honeycomb_nonzero_dmi_band_gap_positive() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.3, 0.0).unwrap();
let gap = m.band_gap(0, 1, 20, 20).unwrap();
assert!(gap > 0.1, "Expected positive gap with DMI, got {}", gap);
}
#[test]
fn kagome_flat_band_exists() {
let j = 1.0;
let m = MagnonBandModel::kagome(j, 0.0, 0.0).unwrap();
let flat_energy = 2.0 * j;
let kpoints = [
(0.0, 0.0),
(PI, 0.0),
(0.0, PI),
(PI, PI),
(PI / 2.0, PI / 3.0),
(-PI / 3.0, 2.0 * PI / 5.0),
];
for (kx, ky) in &kpoints {
let (evals, _) = m.diagonalize((*kx, *ky)).unwrap();
assert!(
(evals[2] - flat_energy).abs() < 1e-8,
"Flat band at k=({:.4},{:.4}): expected +2J={:.4}, got {:.8}",
kx,
ky,
flat_energy,
evals[2]
);
}
}
#[test]
fn bands_grid_size_correct() {
let m = MagnonBandModel::honeycomb_haldane(1.0, 0.0, 0.1, 0.0).unwrap();
let bs = m.bands(12, 15).unwrap();
assert_eq!(bs.len(), 12);
assert_eq!(bs[0].len(), 15);
assert_eq!(bs[0][0].len(), 2);
}
#[test]
fn band_gap_positive_with_dmi() {
let m = MagnonBandModel::kagome(1.0, 0.5, 0.0).unwrap();
let gap = m.band_gap(0, 1, 15, 15).unwrap();
assert!(gap > -1e-6, "Gap should be non-negative: {}", gap);
}
#[test]
fn square_dispersion_cosine_shape() {
let j = 1.0;
let h = 0.5;
let m = MagnonBandModel::square_dmi(j, 0.0, h).unwrap();
let (e_gamma, _) = m.diagonalize((0.0, 0.0)).unwrap();
let (e_corner, _) = m.diagonalize((PI, PI)).unwrap();
assert!(approx(e_gamma[0], h + 4.0 * j, 1e-10));
assert!(approx(e_corner[0], h - 4.0 * j, 1e-10));
}
#[test]
fn invalid_negative_j_rejected() {
assert!(MagnonBandModel::honeycomb_haldane(-1.0, 0.0, 0.0, 0.0).is_err());
assert!(MagnonBandModel::kagome(-0.5, 0.0, 0.0).is_err());
assert!(MagnonBandModel::square_dmi(-2.0, 0.0, 0.0).is_err());
}
}