1use std::fmt::{self, Debug, Formatter};
12use g_math::fixed_point::{FixedPoint, FixedVector, FixedMatrix};
13use log::debug;
14use crate::constants;
15
16#[derive(Clone, Copy, Debug)]
18pub struct CpuFeatures {
19 pub avx: bool,
21 pub avx2: bool,
23 pub sse41: bool,
25 pub sse42: bool,
27}
28
29impl CpuFeatures {
30 #[cfg(target_arch = "x86_64")]
32 pub fn detect() -> Self {
33 Self {
34 avx: is_x86_feature_detected!("avx"),
35 avx2: is_x86_feature_detected!("avx2"),
36 sse41: is_x86_feature_detected!("sse4.1"),
37 sse42: is_x86_feature_detected!("sse4.2"),
38 }
39 }
40
41 #[cfg(not(target_arch = "x86_64"))]
43 pub fn detect() -> Self {
44 Self {
45 avx: false,
46 avx2: false,
47 sse41: false,
48 sse42: false,
49 }
50 }
51}
52
53pub struct SimdOptimization {
58 cpu_features: CpuFeatures,
60}
61
62impl SimdOptimization {
63 pub fn new() -> Self {
65 let cpu_features = CpuFeatures::detect();
66 debug!("Detected CPU features: {:?}", cpu_features);
67
68 Self {
69 cpu_features,
70 }
71 }
72
73 pub fn cpu_features(&self) -> CpuFeatures {
75 self.cpu_features
76 }
77
78 pub fn vector_multiply(&self, a: &FixedVector, b: &FixedVector) -> FixedVector {
81 let len = a.len().min(b.len());
82 let mut result = FixedVector::new(len);
83
84 for i in 0..len {
85 result[i] = a[i] * b[i];
86 }
87
88 result
89 }
90
91 pub fn matrix_vector_multiply(&self, m: &FixedMatrix, v: &FixedVector) -> FixedVector {
93 assert_eq!(m.cols(), v.len(), "Matrix columns must match vector length");
94
95 let mut result = FixedVector::new(m.rows());
96
97 for i in 0..m.rows() {
98 let mut sum = FixedPoint::from_int(0);
99 for j in 0..m.cols() {
100 sum = sum + (m.get(i, j) * v[j]);
101 }
102 result[i] = sum;
103 }
104
105 result
106 }
107
108 pub fn hyperbolic_distance(&self,
110 _disk_radius: FixedPoint,
111 p1: &FixedVector,
112 p2: &FixedVector) -> FixedPoint {
113 let euclidean_distance = p1.distance_to(p2);
115
116 let p1_norm_sq = p1.dot(p1);
118 let p2_norm_sq = p2.dot(p2);
119
120 let one = FixedPoint::from_int(1);
121 let two = FixedPoint::from_int(2);
122 let denominator = one - p1_norm_sq * p2_norm_sq;
123
124 if denominator.abs() < constants::epsilon() {
131 return two * constants::safe_atanh(constants::near_boundary());
132 }
133
134 let ratio = euclidean_distance / denominator.sqrt();
136
137 let safe_ratio = if ratio > constants::near_boundary() {
138 constants::near_boundary()
139 } else {
140 ratio
141 };
142
143 two * constants::safe_atanh(safe_ratio)
144 }
145
146 pub fn euclidean_distance(&self, v1: &FixedVector, v2: &FixedVector) -> FixedPoint {
148 v1.distance_to(v2)
149 }
150
151 pub fn vector_norm_squared(&self, v: &FixedVector) -> FixedPoint {
153 v.dot(v)
154 }
155}
156
157impl Debug for SimdOptimization {
158 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
159 f.debug_struct("SimdOptimization")
160 .field("cpu_features", &self.cpu_features)
161 .finish()
162 }
163}
164
165pub struct PerformanceMonitor {
167 timings: std::collections::HashMap<String, Vec<std::time::Duration>>,
169 start_times: std::collections::HashMap<String, std::time::Instant>,
171}
172
173impl PerformanceMonitor {
174 pub fn new() -> Self {
176 Self {
177 timings: std::collections::HashMap::new(),
178 start_times: std::collections::HashMap::new(),
179 }
180 }
181
182 pub fn start(&mut self, operation: &str) {
184 self.start_times.insert(
185 operation.to_string(),
186 std::time::Instant::now()
187 );
188 }
189
190 pub fn stop(&mut self, operation: &str) {
192 if let Some(start_time) = self.start_times.remove(operation) {
193 let duration = start_time.elapsed();
194
195 self.timings
196 .entry(operation.to_string())
197 .or_insert_with(Vec::new)
198 .push(duration);
199 }
200 }
201
202 pub fn average(&self, operation: &str) -> Option<std::time::Duration> {
204 if let Some(timings) = self.timings.get(operation) {
205 if timings.is_empty() {
206 return None;
207 }
208
209 let total = timings.iter().sum::<std::time::Duration>();
210 let count = timings.len() as u32;
211
212 Some(total / count)
213 } else {
214 None
215 }
216 }
217
218 pub fn min(&self, operation: &str) -> Option<std::time::Duration> {
220 if let Some(timings) = self.timings.get(operation) {
221 timings.iter().min().copied()
222 } else {
223 None
224 }
225 }
226
227 pub fn max(&self, operation: &str) -> Option<std::time::Duration> {
229 if let Some(timings) = self.timings.get(operation) {
230 timings.iter().max().copied()
231 } else {
232 None
233 }
234 }
235
236 pub fn stats(&self, operation: &str) -> Option<(std::time::Duration, std::time::Duration, std::time::Duration)> {
238 if let (Some(avg), Some(min), Some(max)) = (
239 self.average(operation),
240 self.min(operation),
241 self.max(operation),
242 ) {
243 Some((avg, min, max))
244 } else {
245 None
246 }
247 }
248
249 pub fn reset(&mut self) {
251 self.timings.clear();
252 self.start_times.clear();
253 }
254
255 pub fn all_stats(&self) -> std::collections::HashMap<String, (std::time::Duration, std::time::Duration, std::time::Duration)> {
257 let mut result = std::collections::HashMap::new();
258
259 for operation in self.timings.keys() {
260 if let Some(stats) = self.stats(operation) {
261 result.insert(operation.clone(), stats);
262 }
263 }
264
265 result
266 }
267}
268
269pub fn format_duration_us(duration: std::time::Duration) -> String {
271 format!("{} µs", duration.as_micros())
272}
273
274pub fn format_duration_ms(duration: std::time::Duration) -> String {
276 format!("{:.2} ms", duration.as_micros() as f64 / 1000.0)
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_cpu_features_detection() {
285 let features = CpuFeatures::detect();
286 println!("Detected CPU features: {:?}", features);
287 }
288
289 #[test]
290 fn test_degenerate_distance_saturates_not_sentinel() {
291 let simd = SimdOptimization::new();
296 let boundary = FixedVector::from_f32_slice(&[0.99999, 0.0]);
297 let d = simd.hyperbolic_distance(FixedPoint::from_int(1), &boundary, &boundary);
298
299 let saturated = FixedPoint::from_int(2) * constants::safe_atanh(constants::near_boundary());
300 assert!(
301 d <= saturated + constants::epsilon(),
302 "degenerate distance {} exceeded the saturated model max {}",
303 d, saturated
304 );
305 }
306
307 #[test]
308 fn test_simd_vector_multiply() {
309 let simd = SimdOptimization::new();
310
311 let a = FixedVector::from_f32_slice(&[1.0, 2.0, 3.0, 4.0]);
312 let b = FixedVector::from_f32_slice(&[5.0, 6.0, 7.0, 8.0]);
313
314 let product = simd.vector_multiply(&a, &b);
315
316 let mut expected = FixedVector::new(4);
317 for i in 0..4 {
318 expected[i] = a[i] * b[i];
319 }
320
321 for i in 0..4 {
322 assert!((product[i] - expected[i]).abs() < constants::epsilon(),
323 "Mismatch at index {}", i);
324 }
325 }
326
327 #[test]
328 fn test_simd_matrix_vector_multiply() {
329 let simd = SimdOptimization::new();
330
331 let mut m = FixedMatrix::new(2, 3);
332 m.set(0, 0, FixedPoint::from_int(1));
333 m.set(0, 1, FixedPoint::from_int(2));
334 m.set(0, 2, FixedPoint::from_int(3));
335 m.set(1, 0, FixedPoint::from_int(4));
336 m.set(1, 1, FixedPoint::from_int(5));
337 m.set(1, 2, FixedPoint::from_int(6));
338
339 let v = FixedVector::from_f32_slice(&[7.0, 8.0, 9.0]);
340
341 let product = simd.matrix_vector_multiply(&m, &v);
342
343 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
344 assert!(product.len() == 2);
345 assert!((product[0] - FixedPoint::from_int(50)).abs() < tolerance);
346 assert!((product[1] - FixedPoint::from_int(122)).abs() < tolerance);
347 }
348
349 #[test]
350 fn test_hyperbolic_distance() {
351 let simd = SimdOptimization::new();
352
353 let origin = FixedVector::from_f32_slice(&[0.0, 0.0]);
354 let point = FixedVector::from_f32_slice(&[0.5, 0.0]);
355
356 let disk_radius = FixedPoint::from_int(1);
357 let distance = simd.hyperbolic_distance(disk_radius, &origin, &point);
358
359 let expected = FixedPoint::from_int(2) * constants::safe_atanh(constants::half());
361 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(10);
362 assert!((distance - expected).abs() < tolerance);
363 }
364
365 #[test]
366 fn test_performance_monitor() {
367 let mut monitor = PerformanceMonitor::new();
368
369 monitor.start("test_op");
370 std::thread::sleep(std::time::Duration::from_millis(10));
371 monitor.stop("test_op");
372
373 let (avg, min, max) = monitor.stats("test_op").unwrap();
374
375 assert!(avg.as_millis() >= 9 && avg.as_millis() <= 20);
376 assert!(min.as_millis() >= 9 && min.as_millis() <= 20);
377 assert!(max.as_millis() >= 9 && max.as_millis() <= 20);
378
379 monitor.reset();
380 assert!(monitor.stats("test_op").is_none());
381 }
382
383 #[test]
384 fn test_format_duration() {
385 let duration = std::time::Duration::from_micros(1234);
386
387 assert_eq!(format_duration_us(duration), "1234 µs");
388 assert_eq!(format_duration_ms(duration), "1.23 ms");
389 }
390}