use std::f64::consts::PI;
use crate::error::{self, Result};
use crate::math::{CMatrix, Complex};
const SQRT3: f64 = 1.732_050_808_568_877_3;
#[inline]
fn nn_phase_factor(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 nnn_soc_factor(kx: f64, ky: f64) -> f64 {
let phi1 = kx;
let phi2 = kx * 0.5 + ky * SQRT3 * 0.5;
2.0 * (phi1.sin() - phi2.sin() + (phi2 - phi1).sin())
}
#[inline]
fn rashba_ab_coupling(kx: f64, ky: f64, lambda_r: f64) -> Complex {
if lambda_r.abs() < 1e-15 {
return Complex::ZERO;
}
let p1 = Complex::ONE; let p2 = Complex::new(0.0, kx).exp(); let p3 = Complex::new(0.0, kx * 0.5 + ky * SQRT3 * 0.5).exp();
let chi1 = Complex::new(0.0, -1.0); let chi2 = Complex::new(-SQRT3 * 0.5, 0.5);
let chi3 = Complex::new(SQRT3 * 0.5, 0.5);
let sum = p1.mul(&chi1).add(&p2.mul(&chi2)).add(&p3.mul(&chi3));
sum.scale(lambda_r)
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct KaneMeleModel {
pub t: f64,
pub lambda_so: f64,
pub lambda_r: f64,
pub lambda_v: f64,
pub a_lattice: f64,
}
impl KaneMeleModel {
pub fn new(t: f64, lambda_so: f64, lambda_r: f64, lambda_v: f64) -> Result<Self> {
if t <= 0.0 {
return Err(error::invalid_param("t", "NN hopping must be positive"));
}
if !t.is_finite() {
return Err(error::invalid_param("t", "must be finite"));
}
if !lambda_so.is_finite() {
return Err(error::invalid_param("lambda_so", "must be finite"));
}
if !lambda_r.is_finite() {
return Err(error::invalid_param("lambda_r", "must be finite"));
}
if !lambda_v.is_finite() {
return Err(error::invalid_param("lambda_v", "must be finite"));
}
Ok(Self {
t,
lambda_so,
lambda_r,
lambda_v,
a_lattice: 2.46e-10,
})
}
pub fn graphene_with_soc(lambda_so: f64) -> Self {
Self {
t: 2.8,
lambda_so,
lambda_r: 0.0,
lambda_v: 0.0,
a_lattice: 2.46e-10,
}
}
pub fn topological_phase() -> Self {
Self {
t: 1.0,
lambda_so: 0.1,
lambda_r: 0.0,
lambda_v: 0.0,
a_lattice: 2.46e-10,
}
}
pub fn trivial_phase() -> Self {
Self {
t: 1.0,
lambda_so: 0.05,
lambda_r: 0.0,
lambda_v: 0.5,
a_lattice: 2.46e-10,
}
}
pub fn hamiltonian_at(&self, kx: f64, ky: f64) -> CMatrix {
let f_k = nn_phase_factor(kx, ky);
let g_k = nnn_soc_factor(kx, ky);
let r_ab = rashba_ab_coupling(kx, ky, self.lambda_r);
let huu_00 = Complex::from_real(self.lambda_v + self.lambda_so * g_k);
let huu_11 = Complex::from_real(-self.lambda_v - self.lambda_so * g_k);
let huu_01 = f_k.conj().scale(-self.t);
let huu_10 = f_k.scale(-self.t);
let hdd_00 = Complex::from_real(self.lambda_v - self.lambda_so * g_k);
let hdd_11 = Complex::from_real(-self.lambda_v + self.lambda_so * g_k);
let hdd_01 = f_k.conj().scale(-self.t);
let hdd_10 = f_k.scale(-self.t);
let r_conj = r_ab.conj();
let mut h = CMatrix::zeros(4);
h.set(0, 0, huu_00);
h.set(0, 1, huu_01);
h.set(1, 0, huu_10);
h.set(1, 1, huu_11);
h.set(2, 2, hdd_00);
h.set(2, 3, hdd_01);
h.set(3, 2, hdd_10);
h.set(3, 3, hdd_11);
h.set(0, 3, r_ab); h.set(1, 2, r_conj.neg()); h.set(2, 1, r_ab.neg()); h.set(3, 0, r_conj);
h
}
pub fn energy_bands(&self, kx: f64, ky: f64) -> Result<Vec<f64>> {
let h = self.hamiltonian_at(kx, ky);
let (evals, _) = h.hermitian_eigendecomposition()?;
Ok(evals)
}
pub fn band_gap(&self) -> Result<f64> {
let n_grid = 30;
let mut min_gap = f64::INFINITY;
for ix in 0..n_grid {
let kx = -PI + 2.0 * PI * (ix as f64) / (n_grid as f64);
for iy in 0..n_grid {
let ky = -PI + 2.0 * PI * (iy as f64) / (n_grid as f64);
let evals = self.energy_bands(kx, ky)?;
let gap = evals[2] - evals[1];
if gap < min_gap {
min_gap = gap;
}
}
}
Ok(min_gap)
}
pub fn z2_invariant(&self) -> Result<i32> {
if self.lambda_r.abs() < 1e-12 {
self.z2_from_spin_chern()
} else {
self.z2_from_trim_pfaffian()
}
}
fn z2_from_spin_chern(&self) -> Result<i32> {
let b1x = 2.0 * PI;
let b1y = -2.0 * PI / SQRT3;
let b2x = 0.0_f64;
let b2y = 4.0 * PI / SQRT3;
let n = 30_usize; let mut states: Vec<Vec<[Complex; 2]>> = Vec::with_capacity(n + 1);
for ix in 0..=n {
let s = (ix as f64) / (n as f64);
let mut row = Vec::with_capacity(n + 1);
for iy in 0..=n {
let u = (iy as f64) / (n as f64);
let kx = s * b1x + u * b2x;
let ky = s * b1y + u * b2y;
let v = self.spin_up_lower_eigenvec(kx, ky)?;
row.push(v);
}
states.push(row);
}
let mut flux_sum = 0.0_f64;
for ix in 0..n {
let ix1 = ix + 1;
for iy in 0..n {
let iy1 = iy + 1;
let u_x = link_2(states[ix][iy], states[ix1][iy]);
let u_y = link_2(states[ix][iy], states[ix][iy1]);
let u_x_py = link_2(states[ix][iy1], states[ix1][iy1]);
let u_y_px = link_2(states[ix1][iy], states[ix1][iy1]);
let plaq = u_x.mul(&u_y_px).mul(&u_x_py.conj()).mul(&u_y.conj());
flux_sum += plaq.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.15 {
return Err(error::numerical_error(&format!(
"Spin Chern number not quantized: {chern_real:.4} (try parameter adjustment)"
)));
}
Ok(chern_int.abs() % 2)
}
fn spin_up_lower_eigenvec(&self, kx: f64, ky: f64) -> Result<[Complex; 2]> {
let g_k = nnn_soc_factor(kx, ky);
let f_k = nn_phase_factor(kx, ky);
let alpha = self.lambda_v + self.lambda_so * g_k; let beta = f_k.scale(-self.t); let beta_conj = f_k.conj().scale(-self.t);
let disc = (alpha * alpha + beta.norm_sq()).sqrt();
let e_lo = -disc;
let v = if beta.norm_sq() > 1e-28 {
let raw = [beta_conj.neg(), Complex::from_real(alpha - e_lo)];
let norm = (raw[0].norm_sq() + raw[1].norm_sq()).sqrt();
if norm < 1e-15 {
return Err(error::numerical_error(
"degenerate eigenvector in spin-up block",
));
}
[raw[0].scale(1.0 / norm), raw[1].scale(1.0 / norm)]
} else {
if alpha <= 0.0 {
[Complex::ONE, Complex::ZERO]
} else {
[Complex::ZERO, Complex::ONE]
}
};
Ok(v)
}
fn z2_from_trim_pfaffian(&self) -> Result<i32> {
let trim_points = [(0.0, 0.0), (PI, 0.0), (0.0, PI), (PI, PI)];
let mut delta_product = 1.0_f64;
for &(kx, ky) in &trim_points {
let h = self.hamiltonian_at(kx, ky);
let (evals, vecs) = h.hermitian_eigendecomposition()?;
let u0: Vec<Complex> = (0..4).map(|r| vecs.get(r, 0)).collect();
let u1: Vec<Complex> = (0..4).map(|r| vecs.get(r, 1)).collect();
let gap_01 = evals[2] - evals[1];
if gap_01 < 1e-8 {
return Err(error::numerical_error(&format!(
"Band gap too small at TRIM ({kx:.3},{ky:.3}): gap={gap_01:.2e}. \
System may be at a topological phase transition."
)));
}
let theta_u0 = apply_time_reversal(&u0);
let theta_u1 = apply_time_reversal(&u1);
let m01 = inner_product(&u0, &theta_u1);
let m10 = inner_product(&u1, &theta_u0);
let pf = m01;
let det_m = m01.mul(&m10).neg();
let sqrt_det = det_m.re.abs().sqrt();
if sqrt_det < 1e-12 {
return Err(error::numerical_error(
"Sewing matrix singular at TRIM point; cannot compute Z₂",
));
}
let delta_re = pf.re / sqrt_det;
delta_product *= delta_re.signum();
}
Ok(if delta_product < 0.0 { 1 } else { 0 })
}
pub fn is_topological(&self) -> bool {
self.z2_invariant().is_ok_and(|z2| z2 == 1)
}
pub fn edge_spectrum(
&self,
n_cells: usize,
kx_min: f64,
kx_max: f64,
n_kx: usize,
) -> Result<Vec<(f64, Vec<f64>)>> {
if n_cells < 2 {
return Err(error::invalid_param(
"n_cells",
"strip width must be at least 2",
));
}
let dim = n_cells * 4;
if dim > CMatrix::MAX_DIM {
return Err(error::invalid_param(
"n_cells",
"n_cells * 4 exceeds CMatrix::MAX_DIM (64); use n_cells ≤ 16",
));
}
if n_kx < 2 {
return Err(error::invalid_param("n_kx", "need at least 2 kx points"));
}
let delta_ky = PI / (n_cells.max(4) as f64);
let mut result = Vec::with_capacity(n_kx);
for i in 0..n_kx {
let kx = if n_kx == 1 {
kx_min
} else {
kx_min + (kx_max - kx_min) * (i as f64) / ((n_kx - 1) as f64)
};
let h_strip = self.build_strip_hamiltonian(kx, n_cells, delta_ky)?;
let (evals, _) = h_strip.hermitian_eigendecomposition()?;
result.push((kx, evals));
}
Ok(result)
}
fn build_strip_hamiltonian(&self, kx: f64, n_cells: usize, delta_ky: f64) -> Result<CMatrix> {
let nb = 4; let dim = n_cells * nb;
let h_intra = self.hamiltonian_at(kx, 0.0);
let h_at_dky = self.hamiltonian_at(kx, delta_ky);
let mut h_strip = CMatrix::zeros(dim);
for iy in 0..n_cells {
for i in 0..nb {
for j in 0..nb {
let row = iy * nb + i;
let col = iy * nb + j;
let cur = h_strip.get(row, col);
h_strip.set(row, col, cur.add(&h_intra.get(i, j)));
}
}
}
for iy in 0..(n_cells - 1) {
for i in 0..nb {
for j in 0..nb {
let t_ij = {
let dh = h_at_dky.get(i, j).sub(&h_intra.get(i, j));
dh.scale(0.5)
};
let r_up = iy * nb + i;
let c_up = (iy + 1) * nb + j;
let cur_up = h_strip.get(r_up, c_up);
h_strip.set(r_up, c_up, cur_up.add(&t_ij));
let r_dn = (iy + 1) * nb + i;
let c_dn = iy * nb + j;
let cur_dn = h_strip.get(r_dn, c_dn);
h_strip.set(r_dn, c_dn, cur_dn.add(&t_ij.conj()));
}
}
}
Ok(h_strip)
}
}
fn apply_time_reversal(u: &[Complex]) -> Vec<Complex> {
debug_assert_eq!(u.len(), 4);
vec![u[2], u[3], u[0].neg(), u[1].neg()]
}
fn inner_product(a: &[Complex], b: &[Complex]) -> Complex {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(ai, bi)| ai.conj().mul(bi))
.fold(Complex::ZERO, |acc, x| acc.add(&x))
}
#[inline]
fn link_2(a: [Complex; 2], b: [Complex; 2]) -> Complex {
let inner = a[0].conj().mul(&b[0]).add(&a[1].conj().mul(&b[1]));
let norm = inner.norm();
if norm < 1e-15 {
Complex::ONE
} else {
inner.scale(1.0 / norm)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn topological_phase_has_positive_gap() {
let model = KaneMeleModel::topological_phase();
let gap = model.band_gap().unwrap();
assert!(
gap > 0.0,
"Topological phase should have positive band gap, got {gap}"
);
}
#[test]
fn trivial_phase_has_positive_gap() {
let model = KaneMeleModel::trivial_phase();
let gap = model.band_gap().unwrap();
assert!(
gap > 0.0,
"Trivial phase should also have positive band gap, got {gap}"
);
}
#[test]
fn topological_and_trivial_differ_in_z2() {
let topo = KaneMeleModel::topological_phase();
let trivial = KaneMeleModel::trivial_phase();
let z2_topo = topo.z2_invariant().unwrap();
let z2_trivial = trivial.z2_invariant().unwrap();
assert_ne!(
z2_topo, z2_trivial,
"Topological (Z₂={z2_topo}) and trivial (Z₂={z2_trivial}) should differ"
);
}
#[test]
fn hamiltonian_is_hermitian() {
let models = [
KaneMeleModel::new(1.0, 0.1, 0.0, 0.0).unwrap(),
KaneMeleModel::new(1.0, 0.1, 0.05, 0.1).unwrap(),
KaneMeleModel::new(2.8, 0.05, 0.02, 0.0).unwrap(),
];
let kpoints = [(0.0, 0.0), (0.5, 0.8), (-1.2, 1.7)];
for model in &models {
for &(kx, ky) in &kpoints {
let h = model.hamiltonian_at(kx, ky);
let hd = h.conj_transpose();
let diff = h.sub(&hd).unwrap();
assert!(
diff.frobenius_norm() < 1e-12,
"H not Hermitian at k=({kx},{ky}): ||H-H†||={:.2e}",
diff.frobenius_norm()
);
}
}
}
#[test]
fn energy_bands_returns_four_sorted_values() {
let model = KaneMeleModel::topological_phase();
let evals = model.energy_bands(0.3, 0.7).unwrap();
assert_eq!(evals.len(), 4, "Must have exactly 4 bands");
for w in evals.windows(2) {
assert!(w[0] <= w[1] + 1e-10, "Eigenvalues not sorted: {evals:?}");
}
}
#[test]
fn gamma_point_kramers_degeneracy() {
let model = KaneMeleModel::topological_phase();
let evals = model.energy_bands(0.0, 0.0).unwrap();
assert_eq!(evals.len(), 4);
assert!(
approx(evals[0], evals[1], 1e-10),
"Γ: Kramers pair 1 not degenerate: {} vs {}",
evals[0],
evals[1]
);
assert!(
approx(evals[2], evals[3], 1e-10),
"Γ: Kramers pair 2 not degenerate: {} vs {}",
evals[2],
evals[3]
);
}
#[test]
fn is_topological_classifies_correctly() {
let topo = KaneMeleModel::topological_phase();
let trivial = KaneMeleModel::trivial_phase();
assert!(
topo.is_topological(),
"topological_phase() should be classified as topological"
);
assert!(
!trivial.is_topological(),
"trivial_phase() should be classified as trivial"
);
}
#[test]
fn gap_decreases_as_lambda_v_increases() {
let lso = 0.1;
let lv_values = [0.0, 0.2, 0.4];
let mut gaps = Vec::new();
for &lv in &lv_values {
let m = KaneMeleModel::new(1.0, lso, 0.0, lv).unwrap();
gaps.push(m.band_gap().unwrap());
}
assert!(
gaps[0] > gaps[1],
"Gap should decrease with λ_v: gap(0.0)={} > gap(0.2)={} failed",
gaps[0],
gaps[1]
);
assert!(
gaps[1] > gaps[2],
"Gap should decrease with λ_v: gap(0.2)={} > gap(0.4)={} failed",
gaps[1],
gaps[2]
);
}
#[test]
fn graphene_no_soc_is_gapless() {
let model = KaneMeleModel::graphene_with_soc(0.0);
let k_x = 4.0 * PI / 3.0;
let evals = model.energy_bands(k_x, 0.0).unwrap();
let gap_at_k = evals[2] - evals[1];
assert!(
gap_at_k < 1e-6,
"Graphene without SOC: gap at K should be ~0, got {gap_at_k}"
);
}
#[test]
fn graphene_with_soc_opens_gap() {
let model = KaneMeleModel::graphene_with_soc(0.1);
let gap = model.band_gap().unwrap();
assert!(
gap > 0.0,
"SOC should open a band gap in graphene, got gap={gap}"
);
assert!(
gap > 0.1,
"Gap with λ_SO=0.1 should be substantial (>0.1 eV), got {gap}"
);
}
#[test]
fn edge_spectrum_correct_kx_count() {
let model = KaneMeleModel::topological_phase();
let n_kx = 15;
let n_cells = 6;
let result = model.edge_spectrum(n_cells, -PI, PI, n_kx).unwrap();
assert_eq!(
result.len(),
n_kx,
"Expected {n_kx} kx points, got {}",
result.len()
);
let expected_bands = 4 * n_cells;
for (kx, bands) in &result {
assert_eq!(
bands.len(),
expected_bands,
"At kx={kx:.3}: expected {expected_bands} bands, got {}",
bands.len()
);
}
}
#[test]
fn hamiltonian_is_bz_periodic() {
let model = KaneMeleModel::new(1.0, 0.1, 0.03, 0.05).unwrap();
let kx_test = 0.4;
let ky_test = 0.3;
let b1x = 2.0 * PI;
let b1y = -2.0 * PI / SQRT3;
let h_k = model.hamiltonian_at(kx_test, ky_test);
let h_kpb1 = model.hamiltonian_at(kx_test + b1x, ky_test + b1y);
let diff1 = h_k.sub(&h_kpb1).unwrap();
assert!(
diff1.frobenius_norm() < 1e-10,
"b₁ periodicity violated: ||H(k) - H(k+b₁)||={:.2e}",
diff1.frobenius_norm()
);
let b2x = 0.0;
let b2y = 4.0 * PI / SQRT3;
let h_kpb2 = model.hamiltonian_at(kx_test + b2x, ky_test + b2y);
let diff2 = h_k.sub(&h_kpb2).unwrap();
assert!(
diff2.frobenius_norm() < 1e-10,
"b₂ periodicity violated: ||H(k) - H(k+b₂)||={:.2e}",
diff2.frobenius_norm()
);
}
#[test]
fn time_reversal_symmetry() {
let model = KaneMeleModel::new(1.0, 0.08, 0.0, 0.06).unwrap();
let test_points = [(0.5, 0.3), (-0.7, 0.9), (1.1, -0.4)];
for &(kx, ky) in &test_points {
let h_k = model.hamiltonian_at(kx, ky);
let h_mk = model.hamiltonian_at(-kx, -ky);
let mut h_conj = CMatrix::zeros(4);
for i in 0..4 {
for j in 0..4 {
h_conj.set(i, j, h_k.get(i, j).conj());
}
}
let theta_sign: [f64; 4] = [1.0, 1.0, -1.0, -1.0];
let theta_map: [usize; 4] = [2, 3, 0, 1];
let mut thr = CMatrix::zeros(4);
for i in 0..4 {
for j in 0..4 {
let ai = theta_map[i];
let bj = theta_map[j];
let val = h_conj.get(ai, bj).scale(theta_sign[i] * theta_sign[j]);
thr.set(i, j, val);
}
}
let diff = thr.sub(&h_mk).unwrap();
assert!(
diff.frobenius_norm() < 1e-10,
"TRS violated at k=({kx},{ky}): ||Θ H(k)* Θ^T - H(-k)||={:.2e}",
diff.frobenius_norm()
);
}
}
}