ruvector_sparse_inference/pi/
angular.rs1use crate::precision::PrecisionLane;
19use std::f32::consts::PI;
20
21#[derive(Debug, Clone)]
23pub struct AngularEmbedding {
24 lane: PrecisionLane,
26 dimension: usize,
28 phase_scale: f32,
30 velocity: Vec<f32>,
32}
33
34impl AngularEmbedding {
35 pub fn new(lane: PrecisionLane) -> Self {
37 let phase_scale = match lane {
38 PrecisionLane::Bit3 => PI / 4.0,
39 PrecisionLane::Bit5 => PI / 16.0,
40 PrecisionLane::Bit7 => PI / 64.0,
41 PrecisionLane::Float32 => 1.0,
42 };
43
44 Self {
45 lane,
46 dimension: 0,
47 phase_scale,
48 velocity: Vec::new(),
49 }
50 }
51
52 pub fn project(&self, values: &[f32]) -> Vec<f32> {
54 let magnitude = values.iter()
56 .map(|x| x * x)
57 .sum::<f32>()
58 .sqrt()
59 .max(1e-10);
60
61 values.iter()
63 .map(|&x| {
64 let normalized = x / magnitude;
65 normalized * PI * self.phase_scale
67 })
68 .collect()
69 }
70
71 pub fn unproject(&self, angles: &[f32], target_magnitude: f32) -> Vec<f32> {
73 angles.iter()
74 .map(|&angle| {
75 let normalized = angle / (PI * self.phase_scale);
76 normalized * target_magnitude
77 })
78 .collect()
79 }
80
81 pub fn angular_distance(&self, a: &[f32], b: &[f32]) -> f32 {
83 if a.len() != b.len() || a.is_empty() {
84 return f32::MAX;
85 }
86
87 let angles_a = self.project(a);
88 let angles_b = self.project(b);
89
90 let mut total_distance = 0.0f32;
92 for (&a, &b) in angles_a.iter().zip(angles_b.iter()) {
93 let diff = (a - b).abs();
94 let wrapped_diff = if diff > PI { 2.0 * PI - diff } else { diff };
96 total_distance += wrapped_diff * wrapped_diff;
97 }
98
99 total_distance.sqrt()
100 }
101
102 pub fn update_velocity(&mut self, previous: &[f32], current: &[f32]) {
104 if previous.len() != current.len() {
105 return;
106 }
107
108 let prev_angles = self.project(previous);
109 let curr_angles = self.project(current);
110
111 if self.velocity.is_empty() {
112 self.velocity = vec![0.0; current.len()];
113 self.dimension = current.len();
114 }
115
116 let momentum = 0.9f32;
118 for i in 0..self.dimension.min(self.velocity.len()) {
119 let delta = curr_angles[i] - prev_angles[i];
120 let wrapped_delta = if delta > PI {
122 delta - 2.0 * PI
123 } else if delta < -PI {
124 delta + 2.0 * PI
125 } else {
126 delta
127 };
128 self.velocity[i] = momentum * self.velocity[i] + (1.0 - momentum) * wrapped_delta;
129 }
130 }
131
132 pub fn get_velocity(&self) -> &[f32] {
134 &self.velocity
135 }
136
137 pub fn predict_next(&self, current: &[f32]) -> Vec<f32> {
139 let angles = self.project(current);
140 if self.velocity.is_empty() {
141 return current.to_vec();
142 }
143
144 let predicted_angles: Vec<f32> = angles.iter()
145 .zip(self.velocity.iter())
146 .map(|(&a, &v)| {
147 let mut next = a + v;
148 while next > PI { next -= 2.0 * PI; }
150 while next < -PI { next += 2.0 * PI; }
151 next
152 })
153 .collect();
154
155 let magnitude = current.iter().map(|x| x * x).sum::<f32>().sqrt();
157 self.unproject(&predicted_angles, magnitude)
158 }
159}
160
161#[derive(Debug, Clone)]
163pub struct PhaseEncoder {
164 base_frequency: f32,
166 harmonics: usize,
168 lut: Option<Vec<f32>>,
170}
171
172impl PhaseEncoder {
173 pub fn new(base_frequency: f32, harmonics: usize) -> Self {
175 Self {
176 base_frequency,
177 harmonics,
178 lut: None,
179 }
180 }
181
182 pub fn with_lut(mut self, levels: usize) -> Self {
184 let mut lut = Vec::with_capacity(levels);
185 for i in 0..levels {
186 let normalized = (i as f32) / (levels - 1) as f32;
187 let phase = normalized * 2.0 * PI * self.base_frequency;
188 lut.push(phase.sin());
189 }
190 self.lut = Some(lut);
191 self
192 }
193
194 pub fn encode(&self, value: f32) -> f32 {
196 let mut encoded = 0.0f32;
197 for h in 0..self.harmonics {
198 let freq = self.base_frequency * (h + 1) as f32;
199 let weight = 1.0 / (h + 1) as f32; encoded += weight * (value * freq * PI).sin();
201 }
202 encoded
203 }
204
205 pub fn encode_quantized(&self, level: usize) -> f32 {
207 if let Some(ref lut) = self.lut {
208 lut.get(level).copied().unwrap_or(0.0)
209 } else {
210 let normalized = level as f32 / 255.0; self.encode(normalized)
212 }
213 }
214
215 pub fn decode(&self, phase: f32) -> f32 {
217 phase.asin() / (self.base_frequency * PI)
219 }
220}
221
222#[derive(Debug, Clone)]
224pub struct HypersphericalProjection {
225 input_dim: usize,
227 output_dim: usize,
229 lane: PrecisionLane,
231}
232
233impl HypersphericalProjection {
234 pub fn new(dimension: usize, lane: PrecisionLane) -> Self {
236 Self {
237 input_dim: dimension,
238 output_dim: dimension.saturating_sub(1),
239 lane,
240 }
241 }
242
243 pub fn to_spherical(&self, cartesian: &[f32]) -> Vec<f32> {
245 if cartesian.len() < 2 {
246 return vec![];
247 }
248
249 let n = cartesian.len();
250 let mut angles = Vec::with_capacity(n - 1);
251
252 let r = cartesian.iter().map(|x| x * x).sum::<f32>().sqrt();
254 if r < 1e-10 {
255 return vec![0.0; n - 1];
256 }
257
258 let phi_1 = cartesian[1].atan2(cartesian[0]);
264 angles.push(phi_1);
265
266 for k in 1..(n - 1) {
268 let tail_sum: f32 = cartesian[k..].iter().map(|x| x * x).sum();
269 let tail_r = tail_sum.sqrt();
270 if tail_r < 1e-10 {
271 angles.push(0.0);
272 } else {
273 let phi_k = (cartesian[k] / tail_r).clamp(-1.0, 1.0).acos();
274 angles.push(phi_k);
275 }
276 }
277
278 angles
279 }
280
281 pub fn to_cartesian(&self, angles: &[f32], radius: f32) -> Vec<f32> {
283 if angles.is_empty() {
284 return vec![];
285 }
286
287 let n = angles.len() + 1;
288 let mut cartesian = Vec::with_capacity(n);
289
290 let mut sin_product = radius;
296 for &angle in angles.iter().rev().skip(1) {
297 sin_product *= angle.sin();
298 }
299
300 cartesian.push(sin_product * angles[0].cos());
302 cartesian.push(sin_product * angles[0].sin());
303
304 sin_product = radius;
306 for i in (1..angles.len()).rev() {
307 sin_product *= angles[i].sin();
308 cartesian.push(sin_product * angles[i - 1].cos());
309 }
310
311 cartesian.push(radius * angles.last().unwrap_or(&0.0).cos());
313
314 cartesian.truncate(n);
316 cartesian
317 }
318
319 pub fn geodesic_distance(&self, a: &[f32], b: &[f32]) -> f32 {
321 if a.len() != b.len() || a.is_empty() {
322 return f32::MAX;
323 }
324
325 let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-10);
327 let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-10);
328
329 let dot: f32 = a.iter()
331 .zip(b.iter())
332 .map(|(&x, &y)| (x / norm_a) * (y / norm_b))
333 .sum();
334
335 dot.clamp(-1.0, 1.0).acos()
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn test_angular_embedding_project() {
346 let embedding = AngularEmbedding::new(PrecisionLane::Bit5);
347 let values = vec![1.0, 2.0, 3.0, 4.0];
348 let angles = embedding.project(&values);
349
350 assert_eq!(angles.len(), values.len());
351 for &angle in &angles {
353 assert!(angle.abs() <= PI);
354 }
355 }
356
357 #[test]
358 fn test_angular_embedding_roundtrip() {
359 let embedding = AngularEmbedding::new(PrecisionLane::Bit7);
360 let values = vec![1.0, 2.0, 3.0, 4.0];
361 let magnitude = values.iter().map(|x| x * x).sum::<f32>().sqrt();
362
363 let angles = embedding.project(&values);
364 let recovered = embedding.unproject(&angles, magnitude);
365
366 for (&orig, &rec) in values.iter().zip(recovered.iter()) {
368 assert!((orig - rec).abs() < 0.1, "orig={}, rec={}", orig, rec);
369 }
370 }
371
372 #[test]
373 fn test_angular_distance() {
374 let embedding = AngularEmbedding::new(PrecisionLane::Bit5);
375
376 let a = vec![1.0, 0.0, 0.0];
377 let b = vec![0.0, 1.0, 0.0];
378 let c = vec![1.0, 0.0, 0.0];
379
380 let dist_ab = embedding.angular_distance(&a, &b);
381 let dist_ac = embedding.angular_distance(&a, &c);
382
383 assert!(dist_ac < 0.001); assert!(dist_ab > 0.0); }
386
387 #[test]
388 fn test_phase_encoder() {
389 let encoder = PhaseEncoder::new(1.0, 3);
390
391 let e1 = encoder.encode(0.0);
392 let e2 = encoder.encode(0.5);
393 let e3 = encoder.encode(1.0);
394
395 assert!(e1 != e2);
397 assert!(e2 != e3);
398 }
399
400 #[test]
401 fn test_phase_encoder_lut() {
402 let encoder = PhaseEncoder::new(1.0, 1).with_lut(16);
403
404 let e1 = encoder.encode_quantized(0);
405 let e2 = encoder.encode_quantized(8);
406 let e3 = encoder.encode_quantized(15);
407
408 assert!(e1 != e2);
409 assert!(e2 != e3);
410 }
411
412 #[test]
413 fn test_hyperspherical_projection() {
414 let proj = HypersphericalProjection::new(3, PrecisionLane::Bit5);
415
416 let cartesian = vec![1.0, 0.0, 0.0];
417 let spherical = proj.to_spherical(&cartesian);
418
419 assert_eq!(spherical.len(), 2);
420 }
421
422 #[test]
423 fn test_geodesic_distance() {
424 let proj = HypersphericalProjection::new(3, PrecisionLane::Bit5);
425
426 let a = vec![1.0, 0.0, 0.0];
427 let b = vec![0.0, 1.0, 0.0];
428 let c = vec![1.0, 0.0, 0.0];
429
430 let dist_ab = proj.geodesic_distance(&a, &b);
431 let dist_ac = proj.geodesic_distance(&a, &c);
432
433 assert!(dist_ac < 0.001); assert!((dist_ab - PI / 2.0).abs() < 0.001); }
436}