use mermin_core::{BoundaryContour, Real};
pub fn fourier_mode(contour: &BoundaryContour, k: u32) -> (Real, Real) {
let cen = contour.centroid();
let n = contour.n_points() as Real;
let kf = k as Real;
let mut re = 0.0;
let mut im = 0.0;
for p in &contour.points {
let dx = p.x - cen.x;
let dy = p.y - cen.y;
let r = (dx * dx + dy * dy).sqrt();
let theta = dy.atan2(dx);
re += r * (kf * theta).cos();
im -= r * (kf * theta).sin(); }
re /= n;
im /= n;
let amplitude = (re * re + im * im).sqrt();
let phase = im.atan2(re);
(amplitude, phase)
}
pub fn fourier_spectrum(contour: &BoundaryContour) -> [Real; 5] {
let (a0, _) = fourier_mode(contour, 0);
if a0 < 1e-15 {
return [0.0; 5];
}
let ks = [1, 2, 3, 4, 6];
let mut spectrum = [0.0; 5];
for (i, &k) in ks.iter().enumerate() {
let (ak, _) = fourier_mode(contour, k);
spectrum[i] = ak / a0;
}
spectrum
}
#[cfg(test)]
mod tests {
use super::*;
use mermin_core::Point2;
#[test]
fn circle_fourier_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 spec = fourier_spectrum(&contour);
for (i, &v) in spec.iter().enumerate() {
assert!(v < 0.02, "circle fourier[{i}] should be ~0, got {v}");
}
}
#[test]
fn ellipse_k2_dominant() {
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(3.0 * theta.cos(), 1.0 * theta.sin())
})
.collect();
let contour = BoundaryContour::new(pts).unwrap();
let spec = fourier_spectrum(&contour);
assert!(
spec[1] > spec[0],
"ellipse: k=2 ({}) > k=1 ({})",
spec[1],
spec[0]
);
assert!(
spec[1] > spec[2],
"ellipse: k=2 ({}) > k=3 ({})",
spec[1],
spec[2]
);
}
}