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}