Skip to main content

rnn/sphere5d/
sphere5d.rs

1use crate::network::NeuralNetwork;
2
3#[derive(Clone, Copy, Debug)]
4pub struct NeuronPoint {
5    pub layer: usize,
6    pub neuron: usize,
7    pub bias: f32,
8    pub activation: f32,
9    pub position: [f32; 5],
10}
11
12impl Default for NeuronPoint {
13    fn default() -> Self {
14        Self {
15            layer: 0,
16            neuron: 0,
17            bias: 0.0,
18            activation: 0.0,
19            position: [0.0; 5],
20        }
21    }
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum SphereError {
26    InvalidRadius,
27    CapacityTooSmall,
28    InvalidLayout,
29    BiasOutOfBounds,
30}
31
32pub struct Sphere5D<'a> {
33    points: &'a mut [NeuronPoint],
34    len: usize,
35    radius: f32,
36}
37
38impl<'a> Sphere5D<'a> {
39    pub fn new(points: &'a mut [NeuronPoint], radius: f32) -> Result<Self, SphereError> {
40        if !radius.is_finite() || radius <= 0.0 {
41            return Err(SphereError::InvalidRadius);
42        }
43        Ok(Self {
44            points,
45            len: 0,
46            radius,
47        })
48    }
49
50    pub fn from_network<'n>(
51        network: &NeuralNetwork<'n>,
52        points: &'a mut [NeuronPoint],
53        radius: f32,
54    ) -> Result<Self, SphereError> {
55        let mut sphere = Self::new(points, radius)?;
56        sphere.fill_from_network(network)?;
57        Ok(sphere)
58    }
59
60    pub fn radius(&self) -> f32 {
61        self.radius
62    }
63
64    pub fn len(&self) -> usize {
65        self.len
66    }
67
68    pub fn capacity(&self) -> usize {
69        self.points.len()
70    }
71
72    pub fn is_empty(&self) -> bool {
73        self.len == 0
74    }
75
76    pub fn as_slice(&self) -> &[NeuronPoint] {
77        &self.points[..self.len]
78    }
79
80    pub fn as_mut_slice(&mut self) -> &mut [NeuronPoint] {
81        &mut self.points[..self.len]
82    }
83
84    pub fn add_neuron(
85        &mut self,
86        layer: usize,
87        neuron: usize,
88        bias: f32,
89        activation: f32,
90    ) -> Option<usize> {
91        if self.len >= self.points.len() {
92            return None;
93        }
94        let seed = mix_seed(layer as u64, neuron as u64);
95        let position = sphere_pos_from_seed(seed, self.radius);
96        let idx = self.len;
97        self.points[idx] = NeuronPoint {
98            layer,
99            neuron,
100            bias,
101            activation,
102            position,
103        };
104        self.len += 1;
105        Some(idx)
106    }
107
108    pub fn nearest(&self, position: [f32; 5]) -> Option<(usize, f32)> {
109        if self.len == 0 {
110            return None;
111        }
112        let mut best_idx = 0usize;
113        let mut best_d2 = dist2(self.points[0].position, position);
114        for i in 1..self.len {
115            let d2 = dist2(self.points[i].position, position);
116            if d2 < best_d2 {
117                best_d2 = d2;
118                best_idx = i;
119            }
120        }
121        Some((best_idx, crate::math::sqrtf(best_d2)))
122    }
123
124    pub fn neighbors_within(
125        &self,
126        position: [f32; 5],
127        max_distance: f32,
128        out_indices: &mut [usize],
129    ) -> usize {
130        if max_distance < 0.0 || out_indices.is_empty() {
131            return 0;
132        }
133        let max_d2 = max_distance * max_distance;
134        let mut written = 0usize;
135        for i in 0..self.len {
136            if written >= out_indices.len() {
137                break;
138            }
139            if dist2(self.points[i].position, position) <= max_d2 {
140                out_indices[written] = i;
141                written += 1;
142            }
143        }
144        written
145    }
146
147    pub fn fill_from_network<'n>(&mut self, network: &NeuralNetwork<'n>) -> Result<(), SphereError> {
148        if network.layers.len() < 2 {
149            return Err(SphereError::InvalidLayout);
150        }
151        let expected_biases = NeuralNetwork::expected_biases_count(network.layers).ok_or(SphereError::InvalidLayout)?;
152        if expected_biases != network.biases.len() {
153            return Err(SphereError::InvalidLayout);
154        }
155
156        let total_neurons = network.layers.iter().fold(0usize, |acc, &x| acc.saturating_add(x));
157        if total_neurons > self.points.len() {
158            return Err(SphereError::CapacityTooSmall);
159        }
160
161        self.len = 0;
162        let mut bias_cursor = 0usize;
163        for (layer_idx, &layer_size) in network.layers.iter().enumerate() {
164            for neuron_idx in 0..layer_size {
165                let bias = if layer_idx == 0 {
166                    0.0
167                } else {
168                    let b = *network.biases.get(bias_cursor).ok_or(SphereError::BiasOutOfBounds)?;
169                    bias_cursor += 1;
170                    b
171                };
172                self.add_neuron(layer_idx, neuron_idx, bias, 0.0)
173                    .ok_or(SphereError::CapacityTooSmall)?;
174            }
175        }
176        Ok(())
177    }
178}
179
180fn dist2(a: [f32; 5], b: [f32; 5]) -> f32 {
181    let mut acc = 0.0f32;
182    let mut i = 0usize;
183    while i < 5 {
184        let d = a[i] - b[i];
185        acc += d * d;
186        i += 1;
187    }
188    acc
189}
190fn sphere_pos_from_seed(seed: u64, radius: f32) -> [f32; 5] {
191    let mut p = [0.0f32; 5];
192    let mut s = seed;
193    let mut i = 0usize;
194    while i < 5 {
195        s = splitmix64(s);
196        let unit = (s as f64) / (u64::MAX as f64);
197        p[i] = (unit as f32) * 2.0 - 1.0;
198        i += 1;
199    }
200
201    let mut n2 = 0.0f32;
202    i = 0;
203    while i < 5 {
204        n2 += p[i] * p[i];
205        i += 1;
206    }
207
208    if n2 <= 1e-12 {
209        return [radius, 0.0, 0.0, 0.0, 0.0];
210    }
211
212    let inv_n = 1.0 / crate::math::sqrtf(n2);
213    i = 0;
214    while i < 5 {
215        p[i] = p[i] * inv_n * radius;
216        i += 1;
217    }
218    p
219}
220
221fn mix_seed(a: u64, b: u64) -> u64 {
222    splitmix64(a ^ b.rotate_left(17) ^ 0x9E3779B97F4A7C15)
223}
224
225fn splitmix64(mut x: u64) -> u64 {
226    x = x.wrapping_add(0x9E3779B97F4A7C15);
227    x = (x ^ (x >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
228    x = (x ^ (x >> 27)).wrapping_mul(0x94D049BB133111EB);
229    x ^ (x >> 31)
230}