use super::lattice::FrustratedLattice;
use crate::error::{Error, Result};
use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct KagomeMagnonBands {
pub k_point: (f64, f64),
pub bands: [f64; 3],
}
#[derive(Debug, Clone)]
pub struct KagomeMagnonConfig {
pub coupling_j: f64,
pub spin_s: f64,
pub dmi_d: f64,
}
impl Default for KagomeMagnonConfig {
fn default() -> Self {
Self {
coupling_j: 1.0,
spin_s: 0.5,
dmi_d: 0.0,
}
}
}
pub fn kagome_magnon_bands(kx: f64, ky: f64, config: &KagomeMagnonConfig) -> KagomeMagnonBands {
let j = config.coupling_j;
let s = config.spin_s;
let sqrt3 = 3.0_f64.sqrt();
let gamma_01 = (kx / 2.0).cos();
let gamma_02 = (kx / 4.0 + sqrt3 * ky / 4.0).cos();
let gamma_12 = (-kx / 4.0 + sqrt3 * ky / 4.0).cos();
let det = gamma_01 * gamma_02 * gamma_12;
let g01_sq = gamma_01 * gamma_01;
let g02_sq = gamma_02 * gamma_02;
let g12_sq = gamma_12 * gamma_12;
let p = -(g01_sq + g02_sq + g12_sq);
let q = -2.0 * det;
let eigenvalues = solve_depressed_cubic(p, q);
let prefactor = 2.0 * j * s;
let mut bands = [0.0; 3];
for (i, &ev) in eigenvalues.iter().enumerate() {
let arg = 1.0 - ev;
bands[i] = if arg > 0.0 {
prefactor * arg.sqrt()
} else {
0.0
};
}
bands.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
if config.dmi_d.abs() > 1e-30 {
let dm_shift = config.dmi_d * s * sqrt3;
bands[0] += dm_shift.abs(); }
KagomeMagnonBands {
k_point: (kx, ky),
bands,
}
}
fn solve_depressed_cubic(p: f64, q: f64) -> [f64; 3] {
if p.abs() < 1e-30 {
let root = if q >= 0.0 { -(q.cbrt()) } else { (-q).cbrt() };
return [root, root, root];
}
let discriminant = -4.0 * p * p * p - 27.0 * q * q;
if discriminant >= 0.0 {
let m = 2.0 * (-p / 3.0).sqrt();
let theta = (3.0 * q / (p * m + 1e-300)).acos() / 3.0;
let mut roots = [
m * theta.cos(),
m * (theta - 2.0 * std::f64::consts::PI / 3.0).cos(),
m * (theta + 2.0 * std::f64::consts::PI / 3.0).cos(),
];
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
roots
} else {
let sqrt_disc = ((q / 2.0).powi(2) + (p / 3.0).powi(3)).sqrt();
let u = (-q / 2.0 + sqrt_disc).cbrt();
let v = (-q / 2.0 - sqrt_disc).cbrt();
let real_root = u + v;
let complex_real = -real_root / 2.0;
let mut roots = [complex_real, complex_real, real_root];
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
roots
}
}
pub fn kagome_band_path(
config: &KagomeMagnonConfig,
n_points: usize,
) -> Result<Vec<(f64, [f64; 3])>> {
if n_points == 0 {
return Err(Error::InvalidParameter {
param: "n_points".to_string(),
reason: "number of k-points must be nonzero".to_string(),
});
}
let pi = std::f64::consts::PI;
let sqrt3 = 3.0_f64.sqrt();
let gamma = (0.0, 0.0);
let m_point = (pi, pi / sqrt3);
let k_point = (4.0 * pi / 3.0, 0.0);
let segments: [((f64, f64), (f64, f64)); 3] =
[(gamma, m_point), (m_point, k_point), (k_point, gamma)];
let mut result = Vec::with_capacity(3 * n_points);
let mut k_dist = 0.0;
for (start, end) in &segments {
let seg_len = ((end.0 - start.0).powi(2) + (end.1 - start.1).powi(2)).sqrt();
for i in 0..n_points {
let t = i as f64 / n_points as f64;
let kx = start.0 + t * (end.0 - start.0);
let ky = start.1 + t * (end.1 - start.1);
let bands = kagome_magnon_bands(kx, ky, config);
result.push((k_dist, bands.bands));
k_dist += seg_len / n_points as f64;
}
}
Ok(result)
}
pub fn neel_120_order_parameter(lattice: &FrustratedLattice) -> Result<f64> {
if lattice.lattice_type != super::lattice::LatticeType::Kagome {
return Err(Error::InvalidParameter {
param: "lattice_type".to_string(),
reason: "120° order parameter requires kagome lattice".to_string(),
});
}
let sqrt3_half = 3.0_f64.sqrt() / 2.0;
let ideal_dirs = [
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(-0.5, sqrt3_half, 0.0),
Vector3::new(-0.5, -sqrt3_half, 0.0),
];
let n_cells = lattice.num_sites() / 3;
if n_cells == 0 {
return Ok(0.0);
}
let mut order = 0.0;
for (i, spin) in lattice.spins.iter().enumerate() {
let sub = i % 3;
order += spin.dot(&ideal_dirs[sub]);
}
Ok((order / lattice.num_sites() as f64).abs())
}
pub fn is_spin_liquid_candidate(lattice: &FrustratedLattice, threshold: f64) -> bool {
let m_uniform = lattice.average_magnetization().magnitude();
let m_stagger = staggered_magnetization(lattice);
m_uniform < threshold && m_stagger < threshold
}
fn staggered_magnetization(lattice: &FrustratedLattice) -> f64 {
let n = lattice.num_sites();
if n == 0 {
return 0.0;
}
let mut m_stag = Vector3::zero();
for (i, spin) in lattice.spins.iter().enumerate() {
let sign = if i % 2 == 0 { 1.0 } else { -1.0 };
m_stag = m_stag + *spin * sign;
}
m_stag.magnitude() / n as f64
}
pub fn spin_correlation_function(
lattice: &FrustratedLattice,
max_distance: f64,
n_bins: usize,
) -> Result<Vec<(f64, f64)>> {
if n_bins == 0 {
return Err(Error::InvalidParameter {
param: "n_bins".to_string(),
reason: "number of bins must be nonzero".to_string(),
});
}
let bin_width = max_distance / n_bins as f64;
let mut counts = vec![0usize; n_bins];
let mut sums = vec![0.0_f64; n_bins];
let n = lattice.num_sites();
for i in 0..n {
for j in (i + 1)..n {
let dr = lattice.positions[j] - lattice.positions[i];
let dist = dr.magnitude();
let bin = (dist / bin_width) as usize;
if bin < n_bins {
let corr = lattice.spins[i].dot(&lattice.spins[j]);
sums[bin] += corr;
counts[bin] += 1;
}
}
}
let mut result = Vec::with_capacity(n_bins);
for bin in 0..n_bins {
let r = (bin as f64 + 0.5) * bin_width;
let c = if counts[bin] > 0 {
sums[bin] / counts[bin] as f64
} else {
0.0
};
result.push((r, c));
}
Ok(result)
}
pub fn dirac_magnon_energy(config: &KagomeMagnonConfig) -> f64 {
let pi = std::f64::consts::PI;
let k_x = 4.0 * pi / 3.0;
let k_y = 0.0;
let bands = kagome_magnon_bands(k_x, k_y, config);
bands.bands[1] }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kagome_flat_band_exists() {
let config = KagomeMagnonConfig::default();
let k_points = [
(0.0, 0.0),
(1.0, 0.0),
(0.5, 0.5),
(std::f64::consts::PI, 0.0),
(0.0, std::f64::consts::PI),
];
for (kx, ky) in &k_points {
let bands = kagome_magnon_bands(*kx, *ky, &config);
assert!(
bands.bands[0] < 0.3,
"flat band at ({}, {}) = {}, expected near zero",
kx,
ky,
bands.bands[0]
);
}
}
#[test]
fn test_kagome_magnon_gamma_point() {
let config = KagomeMagnonConfig::default();
let bands = kagome_magnon_bands(0.0, 0.0, &config);
assert!(
bands.bands[0] < 0.1,
"lowest band at Γ = {}, expected ~0",
bands.bands[0]
);
}
#[test]
fn test_dirac_magnon_at_k_point() {
let config = KagomeMagnonConfig::default();
let e_dirac = dirac_magnon_energy(&config);
assert!(
e_dirac > 0.0,
"Dirac magnon energy = {}, expected positive",
e_dirac
);
}
#[test]
fn test_120_degree_order_parameter() {
let mut lat =
FrustratedLattice::kagome(6, 6, 1e-21, 5e-10).expect("failed to create kagome lattice");
lat.set_120_degree_order();
let op = neel_120_order_parameter(&lat).expect("failed to compute order parameter");
assert!(op > 0.9, "120° order parameter = {}, expected ~1.0", op);
}
#[test]
fn test_120_degree_order_parameter_wrong_lattice() {
let lat =
FrustratedLattice::triangular(4, 4, 1e-21, 3e-10).expect("failed to create lattice");
let result = neel_120_order_parameter(&lat);
assert!(result.is_err());
}
#[test]
fn test_spin_liquid_candidate_disordered() {
let mut lat =
FrustratedLattice::kagome(4, 4, 1e-21, 5e-10).expect("failed to create lattice");
let mut rng = super::super::lattice::Xorshift64::new(42).expect("failed to create rng");
for spin in lat.spins.iter_mut() {
*spin = rng.random_unit_vector();
}
let is_candidate = is_spin_liquid_candidate(&lat, 0.5);
let _ = is_candidate;
}
#[test]
fn test_kagome_band_path() {
let config = KagomeMagnonConfig::default();
let path = kagome_band_path(&config, 10).expect("failed to compute band path");
assert_eq!(path.len(), 30); for (_, bands) in &path {
assert!(bands[0] <= bands[1], "bands not sorted");
assert!(bands[1] <= bands[2], "bands not sorted");
}
}
#[test]
fn test_spin_correlation_function() {
let mut lat =
FrustratedLattice::kagome(4, 4, 1e-21, 5e-10).expect("failed to create lattice");
lat.set_120_degree_order();
let corr =
spin_correlation_function(&lat, 5e-9, 10).expect("failed to compute correlations");
assert_eq!(corr.len(), 10);
}
#[test]
fn test_dmi_lifts_flat_band() {
let config_no_dm = KagomeMagnonConfig {
coupling_j: 1.0,
spin_s: 0.5,
dmi_d: 0.0,
};
let config_with_dm = KagomeMagnonConfig {
coupling_j: 1.0,
spin_s: 0.5,
dmi_d: 0.1,
};
let bands_no_dm = kagome_magnon_bands(1.0, 0.5, &config_no_dm);
let bands_with_dm = kagome_magnon_bands(1.0, 0.5, &config_with_dm);
assert!(
bands_with_dm.bands[0] >= bands_no_dm.bands[0],
"DM should lift flat band: {} >= {}",
bands_with_dm.bands[0],
bands_no_dm.bands[0]
);
}
}