use mermin_core::{BoundaryContour, Real};
pub fn katic_shape_amplitude(contour: &BoundaryContour, k: u32) -> Real {
let pts = &contour.points;
let n = pts.len();
let mut re = 0.0;
let mut im = 0.0;
let mut total_length = 0.0;
for i in 0..n {
let j = (i + 1) % n;
let dx = pts[j].x - pts[i].x;
let dy = pts[j].y - pts[i].y;
let edge_len = (dx * dx + dy * dy).sqrt();
if edge_len < 1e-15 {
continue;
}
let phi = dy.atan2(-dx);
let kf = k as Real;
re += edge_len * (kf * phi).cos();
im += edge_len * (kf * phi).sin();
total_length += edge_len;
}
if total_length < 1e-15 {
return 0.0;
}
(re * re + im * im).sqrt() / total_length
}
pub fn katic_shape_spectrum(contour: &BoundaryContour) -> [Real; 4] {
[
katic_shape_amplitude(contour, 1),
katic_shape_amplitude(contour, 2),
katic_shape_amplitude(contour, 4),
katic_shape_amplitude(contour, 6),
]
}
#[cfg(test)]
mod tests {
use super::*;
use mermin_core::Point2;
#[test]
fn regular_hexagon_k6() {
let pts: Vec<Point2> = (0..6)
.map(|i| {
let theta = std::f64::consts::FRAC_PI_3 * i as f64;
Point2::new(theta.cos(), theta.sin())
})
.collect();
let contour = BoundaryContour::new(pts).unwrap();
let q6 = katic_shape_amplitude(&contour, 6);
assert!(q6 > 0.9, "regular hexagon q6 should be ~1.0, got {q6}");
}
#[test]
fn circle_isotropic() {
let n = 200;
let pts: Vec<Point2> = (0..n)
.map(|i| {
let theta = 2.0 * std::f64::consts::PI * i as f64 / n as f64;
Point2::new(theta.cos(), theta.sin())
})
.collect();
let contour = BoundaryContour::new(pts).unwrap();
let spectrum = katic_shape_spectrum(&contour);
for (i, &q) in spectrum.iter().enumerate() {
assert!(q < 0.05, "circle q[{i}] should be ~0, got {q}");
}
}
#[test]
fn rectangle_nematic() {
let contour = BoundaryContour::new(vec![
Point2::new(0.0, 0.0),
Point2::new(4.0, 0.0),
Point2::new(4.0, 1.0),
Point2::new(0.0, 1.0),
])
.unwrap();
let q2 = katic_shape_amplitude(&contour, 2);
assert!(q2 > 0.4, "rectangle q2 should be significant, got {q2}");
}
}