use std::f64::consts::PI;
#[derive(Debug, Clone, Copy)]
pub struct QwzModel {
pub u: f64,
}
impl QwzModel {
pub fn new(u: f64) -> Self {
Self { u }
}
pub fn d_vec(&self, kx: f64, ky: f64) -> [f64; 3] {
[kx.sin(), ky.sin(), self.u + kx.cos() + ky.cos()]
}
pub fn hamiltonian(&self, kx: f64, ky: f64) -> [[f64; 2]; 2] {
let d = self.d_vec(kx, ky);
[[d[2], d[0]], [d[0], -d[2]]]
}
pub fn energies(&self, kx: f64, ky: f64) -> (f64, f64) {
let d = self.d_vec(kx, ky);
let mag = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
(-mag, mag)
}
pub fn berry_curvature(&self, kx: f64, ky: f64) -> f64 {
let eps = 1e-5;
let d = self.d_vec(kx, ky);
let d_pkx = self.d_vec(kx + eps, ky);
let d_pky = self.d_vec(kx, ky + eps);
let dd_dkx = [
(d_pkx[0] - d[0]) / eps,
(d_pkx[1] - d[1]) / eps,
(d_pkx[2] - d[2]) / eps,
];
let dd_dky = [
(d_pky[0] - d[0]) / eps,
(d_pky[1] - d[1]) / eps,
(d_pky[2] - d[2]) / eps,
];
let cross = [
dd_dkx[1] * dd_dky[2] - dd_dkx[2] * dd_dky[1],
dd_dkx[2] * dd_dky[0] - dd_dkx[0] * dd_dky[2],
dd_dkx[0] * dd_dky[1] - dd_dkx[1] * dd_dky[0],
];
let d_norm = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
if d_norm < 1e-30 {
return 0.0;
}
let d_hat = [d[0] / d_norm, d[1] / d_norm, d[2] / d_norm];
0.5 * (d_hat[0] * cross[0] + d_hat[1] * cross[1] + d_hat[2] * cross[2])
}
pub fn chern_number(&self, n_k: usize) -> i32 {
if n_k == 0 {
return 0;
}
let dk = 2.0 * PI / n_k as f64;
let mut integral = 0.0_f64;
for ix in 0..n_k {
for iy in 0..n_k {
let kx = -PI + ix as f64 * dk;
let ky = -PI + iy as f64 * dk;
integral += self.berry_curvature(kx, ky) * dk * dk;
}
}
(integral / (2.0 * PI)).round() as i32
}
pub fn phase_diagram(u_range: (f64, f64), n_points: usize) -> Vec<(f64, i32)> {
if n_points == 0 {
return Vec::new();
}
let (u_min, u_max) = u_range;
let du = if n_points > 1 {
(u_max - u_min) / (n_points - 1) as f64
} else {
0.0
};
(0..n_points)
.map(|i| {
let u = u_min + i as f64 * du;
let model = QwzModel::new(u);
(u, model.chern_number(30))
})
.collect()
}
pub fn band_gap(&self) -> f64 {
let special_k = [
(0.0_f64, 0.0_f64), (PI, PI), (PI, 0.0), (0.0, PI), ];
let min_gap = special_k
.iter()
.map(|&(kx, ky)| {
let d = self.d_vec(kx, ky);
2.0 * (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
})
.fold(f64::INFINITY, f64::min);
min_gap.max(0.0)
}
pub fn hall_conductance(&self) -> f64 {
self.chern_number(50) as f64
}
pub fn n_edge_states(&self) -> i32 {
self.chern_number(50).abs()
}
}
pub fn berry_curvature_map(model: &QwzModel, n_k: usize) -> Vec<Vec<f64>> {
if n_k == 0 {
return Vec::new();
}
let dk = 2.0 * PI / n_k as f64;
(0..n_k)
.map(|ix| {
let kx = -PI + ix as f64 * dk;
(0..n_k)
.map(|iy| {
let ky = -PI + iy as f64 * dk;
model.berry_curvature(kx, ky)
})
.collect()
})
.collect()
}
pub fn chern_from_curvature_map(map: &[Vec<f64>], dk: f64) -> i32 {
let integral: f64 = map
.iter()
.flat_map(|row| row.iter())
.map(|&v| v * dk * dk)
.sum();
(integral / (2.0 * PI)).round() as i32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn qwz_chern_number_trivial_large_u() {
let m = QwzModel::new(3.0); assert_eq!(m.chern_number(40), 0);
}
#[test]
fn qwz_chern_number_trivial_negative_large_u() {
let m = QwzModel::new(-3.0); assert_eq!(m.chern_number(40), 0);
}
#[test]
fn qwz_chern_number_topological_u_positive() {
let m = QwzModel::new(1.0); let c = m.chern_number(40).abs();
assert_eq!(c, 1, "Expected |C|=1, got C={c}");
}
#[test]
fn qwz_chern_number_topological_u_negative() {
let m = QwzModel::new(-1.0); let c = m.chern_number(40).abs();
assert_eq!(c, 1, "Expected |C|=1, got C={c}");
}
#[test]
fn qwz_energies_symmetric() {
let m = QwzModel::new(1.0);
let (em, ep) = m.energies(0.5, 0.3);
assert!((em + ep).abs() < 1e-12, "E+ + E- = {}", em + ep);
assert!(ep >= 0.0);
}
#[test]
fn qwz_berry_curvature_finite() {
let m = QwzModel::new(1.0);
let omega = m.berry_curvature(0.5, 0.5);
assert!(omega.is_finite());
}
#[test]
fn qwz_band_gap_positive_nontrivial() {
let m = QwzModel::new(1.0);
assert!(m.band_gap() > 0.0);
}
#[test]
fn qwz_band_gap_closes_at_boundary() {
let m = QwzModel::new(2.0);
let gap = m.band_gap();
assert!(gap < 1e-10, "Gap at u=2 should be ~0, got {gap}");
}
#[test]
fn qwz_phase_diagram_length() {
let pd = QwzModel::phase_diagram((-3.0, 3.0), 7);
assert_eq!(pd.len(), 7);
}
#[test]
fn qwz_phase_diagram_trivial_endpoints() {
let pd = QwzModel::phase_diagram((-3.0, 3.0), 7);
assert_eq!(pd[0].1, 0, "u={}: C should be 0", pd[0].0);
assert_eq!(pd[6].1, 0, "u={}: C should be 0", pd[6].0);
}
#[test]
fn berry_curvature_map_dimensions() {
let m = QwzModel::new(1.0);
let map = berry_curvature_map(&m, 10);
assert_eq!(map.len(), 10);
for row in &map {
assert_eq!(row.len(), 10);
}
}
#[test]
fn chern_from_curvature_map_trivial() {
let map: Vec<Vec<f64>> = vec![vec![0.0; 5]; 5];
let dk = 2.0 * PI / 5.0;
assert_eq!(chern_from_curvature_map(&map, dk), 0);
}
#[test]
fn qwz_hall_conductance_trivial_is_zero() {
let m = QwzModel::new(3.0);
assert_eq!(m.hall_conductance() as i32, 0);
}
#[test]
fn qwz_n_edge_states_nontrivial() {
let m = QwzModel::new(1.0);
assert_eq!(m.n_edge_states(), 1);
}
}