Spaces:
Sleeping
Sleeping
| //! Lenia spatial kernel — 2D ring-shaped bell curve. | |
| //! | |
| //! K(r) = exp(-((r - 0.5) / sigma)^2 / 2) | |
| //! Center zeroed (a weight doesn't influence itself). | |
| //! Normalized to sum to 1. | |
| /// Precomputed 2D kernel for convolution. | |
| pub struct Kernel2D { | |
| pub data: Vec<f32>, | |
| pub size: usize, // side length = 2*radius + 1 | |
| pub radius: usize, | |
| } | |
| impl Kernel2D { | |
| /// Create a ring-shaped Lenia kernel. | |
| pub fn new(radius: usize, sigma: f32) -> Self { | |
| let size = 2 * radius + 1; | |
| let mut data = vec![0.0f32; size * size]; | |
| let r = radius as f32; | |
| let mut sum = 0.0f32; | |
| for iy in 0..size { | |
| for ix in 0..size { | |
| let dy = iy as f32 - r; | |
| let dx = ix as f32 - r; | |
| let dist = (dx * dx + dy * dy).sqrt() / r; | |
| // Ring kernel: peak at dist ~0.5 | |
| let val = (-(dist - 0.5).powi(2) / (2.0 * sigma * sigma)).exp(); | |
| data[iy * size + ix] = val; | |
| sum += val; | |
| } | |
| } | |
| // Zero center | |
| data[radius * size + radius] = 0.0; | |
| sum -= data[radius * size + radius]; // was already subtracted above since we set it after | |
| // Recompute sum after zeroing center | |
| sum = data.iter().sum(); | |
| // Normalize | |
| if sum > 1e-8 { | |
| for v in data.iter_mut() { | |
| *v /= sum; | |
| } | |
| } | |
| Kernel2D { data, size, radius } | |
| } | |
| /// Apply 2D convolution (same-size output, zero-padded). | |
| /// input: row-major f32 array of shape (h, w) | |
| /// output: same shape, each element = sum of kernel-weighted neighborhood | |
| pub fn convolve(&self, input: &[f32], h: usize, w: usize, output: &mut [f32]) { | |
| let r = self.radius as isize; | |
| let ksize = self.size; | |
| for iy in 0..h { | |
| for ix in 0..w { | |
| let mut acc = 0.0f32; | |
| for ky in 0..ksize { | |
| let sy = iy as isize + ky as isize - r; | |
| if sy < 0 || sy >= h as isize { | |
| continue; | |
| } | |
| for kx in 0..ksize { | |
| let sx = ix as isize + kx as isize - r; | |
| if sx < 0 || sx >= w as isize { | |
| continue; | |
| } | |
| acc += input[sy as usize * w + sx as usize] | |
| * self.data[ky * ksize + kx]; | |
| } | |
| } | |
| output[iy * w + ix] = acc; | |
| } | |
| } | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| fn test_kernel_creation() { | |
| let k = Kernel2D::new(3, 1.0); | |
| assert_eq!(k.size, 7); | |
| assert_eq!(k.data.len(), 49); | |
| // Center should be zero | |
| assert_eq!(k.data[3 * 7 + 3], 0.0); | |
| // Should sum to ~1.0 (normalized) | |
| let sum: f32 = k.data.iter().sum(); | |
| assert!((sum - 1.0).abs() < 0.01, "Kernel sum: {}", sum); | |
| } | |
| fn test_convolution() { | |
| let k = Kernel2D::new(1, 0.5); | |
| // 4x4 input, all ones | |
| let input = vec![1.0f32; 16]; | |
| let mut output = vec![0.0f32; 16]; | |
| k.convolve(&input, 4, 4, &mut output); | |
| // Interior elements should be ~1.0 (uniform input, normalized kernel) | |
| // Edge elements will be less (zero padding) | |
| assert!(output[5] > 0.5, "Interior value: {}", output[5]); | |
| } | |
| } | |