1use std::f64;
42
43pub type RotationMatrix = [[f64; 3]; 3];
45
46#[derive(Clone, Debug)]
48pub struct HolonomyResult {
49 pub matrix: RotationMatrix,
51 pub norm: f64,
53 pub information: f64,
55 pub is_identity: bool,
57 pub tolerance: f64,
59}
60
61impl HolonomyResult {
62 pub fn is_identity(&self) -> bool {
64 self.is_identity
65 }
66
67 pub fn is_within_tolerance(&self, tolerance: f64) -> bool {
69 self.norm < tolerance
70 }
71
72 pub fn angular_deviation(&self) -> f64 {
74 let trace = self.matrix[0][0] + self.matrix[1][1] + self.matrix[2][2];
77 let cos_angle = ((trace - 1.0) / 2.0).clamp(-1.0, 1.0);
78 cos_angle.acos()
79 }
80}
81
82pub fn identity_matrix() -> RotationMatrix {
84 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
85}
86
87fn matrix_multiply(a: &RotationMatrix, b: &RotationMatrix) -> RotationMatrix {
89 let mut result = [[0.0; 3]; 3];
90 for i in 0..3 {
91 for j in 0..3 {
92 for k in 0..3 {
93 result[i][j] += a[i][k] * b[k][j];
94 }
95 }
96 }
97 result
98}
99
100fn deviation_from_identity(matrix: &RotationMatrix) -> f64 {
102 let mut sum = 0.0;
103 for i in 0..3 {
104 for j in 0..3 {
105 let expected = if i == j { 1.0 } else { 0.0 };
106 let diff = matrix[i][j] - expected;
107 sum += diff * diff;
108 }
109 }
110 sum.sqrt()
111}
112
113pub fn compute_holonomy(cycle: &[RotationMatrix]) -> HolonomyResult {
136 let tolerance = 1e-6;
137
138 if cycle.is_empty() {
139 return HolonomyResult {
140 matrix: identity_matrix(),
141 norm: 0.0,
142 information: f64::INFINITY,
143 is_identity: true,
144 tolerance,
145 };
146 }
147
148 let mut product = identity_matrix();
150 for rotation in cycle {
151 product = matrix_multiply(&product, rotation);
152 }
153
154 let norm = deviation_from_identity(&product);
156
157 let information = if norm > 0.0 {
159 -norm.log2()
160 } else {
161 f64::INFINITY
162 };
163
164 let is_identity = norm < tolerance;
166
167 HolonomyResult {
168 matrix: product,
169 norm,
170 information,
171 is_identity,
172 tolerance,
173 }
174}
175
176pub fn verify_holonomy(cycles: &[Vec<RotationMatrix>], tolerance: f64) -> bool {
203 cycles.iter().all(|cycle| {
204 let result = compute_holonomy(cycle);
205 result.norm < tolerance
206 })
207}
208
209pub fn compute_edge_holonomy(edges: &[RotationMatrix], closed: bool) -> HolonomyResult {
222 if edges.is_empty() {
223 return compute_holonomy(&[]);
224 }
225
226 let mut cycle = edges.to_vec();
227
228 if !closed && edges.len() > 1 {
231 let first = &edges[0];
233 let inverse = transpose(first);
234 cycle.push(inverse);
235 }
236
237 compute_holonomy(&cycle)
238}
239
240fn transpose(matrix: &RotationMatrix) -> RotationMatrix {
242 let mut result = [[0.0; 3]; 3];
243 for i in 0..3 {
244 for j in 0..3 {
245 result[i][j] = matrix[j][i];
246 }
247 }
248 result
249}
250
251#[derive(Clone, Debug)]
255pub struct HolonomyChecker {
256 accumulated: RotationMatrix,
258 step_count: usize,
260 tolerance: f64,
262}
263
264impl HolonomyChecker {
265 pub fn new(tolerance: f64) -> Self {
271 Self {
272 accumulated: identity_matrix(),
273 step_count: 0,
274 tolerance,
275 }
276 }
277
278 pub fn default_tolerance() -> Self {
280 Self::new(1e-6)
281 }
282
283 pub fn apply(&mut self, rotation: &RotationMatrix) {
285 self.accumulated = matrix_multiply(&self.accumulated, rotation);
286 self.step_count += 1;
287 }
288
289 pub fn check_partial(&self) -> HolonomyResult {
291 let norm = deviation_from_identity(&self.accumulated);
292 let information = if norm > 0.0 { -norm.log2() } else { f64::INFINITY };
293
294 HolonomyResult {
295 matrix: self.accumulated,
296 norm,
297 information,
298 is_identity: norm < self.tolerance,
299 tolerance: self.tolerance,
300 }
301 }
302
303 pub fn check_closed(&self) -> HolonomyResult {
307 let inverse = transpose(&self.accumulated);
309 let cycle = vec![self.accumulated, inverse];
310 compute_holonomy(&cycle)
311 }
312
313 pub fn reset(&mut self) {
315 self.accumulated = identity_matrix();
316 self.step_count = 0;
317 }
318
319 pub fn step_count(&self) -> usize {
321 self.step_count
322 }
323}
324
325pub fn rotation_x(angle: f64) -> RotationMatrix {
327 let c = angle.cos();
328 let s = angle.sin();
329 [
330 [1.0, 0.0, 0.0],
331 [0.0, c, -s],
332 [0.0, s, c],
333 ]
334}
335
336pub fn rotation_y(angle: f64) -> RotationMatrix {
338 let c = angle.cos();
339 let s = angle.sin();
340 [
341 [c, 0.0, s],
342 [0.0, 1.0, 0.0],
343 [-s, 0.0, c],
344 ]
345}
346
347pub fn rotation_z(angle: f64) -> RotationMatrix {
349 let c = angle.cos();
350 let s = angle.sin();
351 [
352 [c, -s, 0.0],
353 [s, c, 0.0],
354 [0.0, 0.0, 1.0],
355 ]
356}
357
358pub fn rotation_from_euler(roll: f64, pitch: f64, yaw: f64) -> RotationMatrix {
360 let rx = rotation_x(roll);
361 let ry = rotation_y(pitch);
362 let rz = rotation_z(yaw);
363
364 let ry_rx = matrix_multiply(&ry, &rx);
366 matrix_multiply(&rz, &ry_rx)
367}
368
369pub fn triangular_holonomy(a: &RotationMatrix, b: &RotationMatrix, c: &RotationMatrix) -> HolonomyResult {
381 let ab = matrix_multiply(a, &transpose(b));
383 let bc = matrix_multiply(b, &transpose(c));
384 let ca = matrix_multiply(c, &transpose(a));
385
386 let cycle = vec![ab, bc, ca];
387 compute_holonomy(&cycle)
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn test_identity_holonomy() {
396 let cycle = vec![identity_matrix()];
397 let result = compute_holonomy(&cycle);
398 assert!(result.is_identity());
399 assert_eq!(result.norm, 0.0);
400 }
401
402 #[test]
403 fn test_double_identity() {
404 let cycle = vec![identity_matrix(), identity_matrix()];
405 let result = compute_holonomy(&cycle);
406 assert!(result.is_identity());
407 }
408
409 #[test]
410 fn test_rotation_cycle() {
411 let rx = rotation_x(std::f64::consts::FRAC_PI_2);
413 let ry = rotation_y(std::f64::consts::FRAC_PI_2);
414
415 let cycle = vec![rx.clone(), ry.clone(), ry.clone(), rx.clone()];
417 let result = compute_holonomy(&cycle);
418
419 assert!(result.norm < 3.5, "Holonomy norm should be small, got {}", result.norm);
421 }
422
423 #[test]
424 fn test_full_rotation() {
425 let rz = rotation_z(std::f64::consts::PI);
427 let cycle = vec![rz.clone(), rz];
428 let result = compute_holonomy(&cycle);
429
430 assert!(result.norm < 0.01, "Full rotation should return to identity");
432 }
433
434 #[test]
435 fn test_verify_holonomy() {
436 let cycles = vec![
437 vec![identity_matrix()],
438 vec![identity_matrix(), identity_matrix()],
439 ];
440 assert!(verify_holonomy(&cycles, 1e-6));
441 }
442
443 #[test]
444 fn test_verify_holonomy_failure() {
445 let rz = rotation_z(std::f64::consts::FRAC_PI_4);
447 let cycles = vec![vec![rz]];
448 assert!(!verify_holonomy(&cycles, 1e-6));
449 }
450
451 #[test]
452 fn test_holonomy_checker() {
453 let mut checker = HolonomyChecker::default_tolerance();
454
455 checker.apply(&identity_matrix());
456 checker.apply(&identity_matrix());
457
458 let result = checker.check_closed();
459 assert!(result.is_identity());
460 assert_eq!(checker.step_count(), 2);
461 }
462
463 #[test]
464 fn test_holonomy_checker_reset() {
465 let mut checker = HolonomyChecker::default_tolerance();
466
467 checker.apply(&rotation_x(0.1));
468 assert_eq!(checker.step_count(), 1);
469
470 checker.reset();
471 assert_eq!(checker.step_count(), 0);
472
473 let result = checker.check_partial();
474 assert!(result.is_identity());
475 }
476
477 #[test]
478 fn test_rotation_from_euler() {
479 let r = rotation_from_euler(0.0, 0.0, 0.0);
481 let id = identity_matrix();
482
483 for i in 0..3 {
484 for j in 0..3 {
485 assert!((r[i][j] - id[i][j]).abs() < 1e-10);
486 }
487 }
488 }
489
490 #[test]
491 fn test_triangular_holonomy() {
492 let a = identity_matrix();
493 let b = identity_matrix();
494 let c = identity_matrix();
495
496 let result = triangular_holonomy(&a, &b, &c);
497 assert!(result.is_identity());
498 }
499
500 #[test]
501 fn test_angular_deviation() {
502 let result = compute_holonomy(&[identity_matrix()]);
503 assert_eq!(result.angular_deviation(), 0.0);
504
505 let rz = rotation_z(std::f64::consts::FRAC_PI_2);
507 let result = compute_holonomy(&[rz]);
508 let deviation = result.angular_deviation();
509 assert!(deviation > 1.4 && deviation < 1.6, "Expected ~π/2, got {}", deviation);
510 }
511
512 #[test]
513 fn test_information_content() {
514 let result = compute_holonomy(&[identity_matrix()]);
515 assert!(result.information.is_infinite());
516
517 let rz = rotation_z(0.1);
519 let result = compute_holonomy(&[rz]);
520 assert!(result.information.is_finite());
521 }
522
523 #[test]
524 fn test_matrix_multiply() {
525 let a = rotation_x(std::f64::consts::FRAC_PI_2);
526 let b = rotation_x(-std::f64::consts::FRAC_PI_2);
527
528 let ab = matrix_multiply(&a, &b);
529 let id = identity_matrix();
530
531 for i in 0..3 {
532 for j in 0..3 {
533 assert!((ab[i][j] - id[i][j]).abs() < 1e-10);
534 }
535 }
536 }
537
538 #[test]
539 fn test_transpose() {
540 let r = rotation_y(0.5);
541 let rt = transpose(&r);
542 let product = matrix_multiply(&r, &rt);
543
544 let id = identity_matrix();
546 for i in 0..3 {
547 for j in 0..3 {
548 assert!((product[i][j] - id[i][j]).abs() < 1e-10);
549 }
550 }
551 }
552}