use std::f64;
pub type RotationMatrix = [[f64; 3]; 3];
#[derive(Clone, Debug)]
pub struct HolonomyResult {
pub matrix: RotationMatrix,
pub norm: f64,
pub information: f64,
pub is_identity: bool,
pub tolerance: f64,
}
impl HolonomyResult {
pub fn is_identity(&self) -> bool {
self.is_identity
}
pub fn is_within_tolerance(&self, tolerance: f64) -> bool {
self.norm < tolerance
}
pub fn angular_deviation(&self) -> f64 {
let trace = self.matrix[0][0] + self.matrix[1][1] + self.matrix[2][2];
let cos_angle = ((trace - 1.0) / 2.0).clamp(-1.0, 1.0);
cos_angle.acos()
}
}
pub fn identity_matrix() -> RotationMatrix {
[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
}
fn matrix_multiply(a: &RotationMatrix, b: &RotationMatrix) -> RotationMatrix {
let mut result = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
result[i][j] += a[i][k] * b[k][j];
}
}
}
result
}
fn deviation_from_identity(matrix: &RotationMatrix) -> f64 {
let mut sum = 0.0;
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
let diff = matrix[i][j] - expected;
sum += diff * diff;
}
}
sum.sqrt()
}
pub fn compute_holonomy(cycle: &[RotationMatrix]) -> HolonomyResult {
let tolerance = 1e-6;
if cycle.is_empty() {
return HolonomyResult {
matrix: identity_matrix(),
norm: 0.0,
information: f64::INFINITY,
is_identity: true,
tolerance,
};
}
let mut product = identity_matrix();
for rotation in cycle {
product = matrix_multiply(&product, rotation);
}
let norm = deviation_from_identity(&product);
let information = if norm > 0.0 {
-norm.log2()
} else {
f64::INFINITY
};
let is_identity = norm < tolerance;
HolonomyResult {
matrix: product,
norm,
information,
is_identity,
tolerance,
}
}
pub fn verify_holonomy(cycles: &[Vec<RotationMatrix>], tolerance: f64) -> bool {
cycles.iter().all(|cycle| {
let result = compute_holonomy(cycle);
result.norm < tolerance
})
}
pub fn compute_edge_holonomy(edges: &[RotationMatrix], closed: bool) -> HolonomyResult {
if edges.is_empty() {
return compute_holonomy(&[]);
}
let mut cycle = edges.to_vec();
if !closed && edges.len() > 1 {
let first = &edges[0];
let inverse = transpose(first);
cycle.push(inverse);
}
compute_holonomy(&cycle)
}
fn transpose(matrix: &RotationMatrix) -> RotationMatrix {
let mut result = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
result[i][j] = matrix[j][i];
}
}
result
}
#[derive(Clone, Debug)]
pub struct HolonomyChecker {
accumulated: RotationMatrix,
step_count: usize,
tolerance: f64,
}
impl HolonomyChecker {
pub fn new(tolerance: f64) -> Self {
Self {
accumulated: identity_matrix(),
step_count: 0,
tolerance,
}
}
pub fn default_tolerance() -> Self {
Self::new(1e-6)
}
pub fn apply(&mut self, rotation: &RotationMatrix) {
self.accumulated = matrix_multiply(&self.accumulated, rotation);
self.step_count += 1;
}
pub fn check_partial(&self) -> HolonomyResult {
let norm = deviation_from_identity(&self.accumulated);
let information = if norm > 0.0 { -norm.log2() } else { f64::INFINITY };
HolonomyResult {
matrix: self.accumulated,
norm,
information,
is_identity: norm < self.tolerance,
tolerance: self.tolerance,
}
}
pub fn check_closed(&self) -> HolonomyResult {
let inverse = transpose(&self.accumulated);
let cycle = vec![self.accumulated, inverse];
compute_holonomy(&cycle)
}
pub fn reset(&mut self) {
self.accumulated = identity_matrix();
self.step_count = 0;
}
pub fn step_count(&self) -> usize {
self.step_count
}
}
pub fn rotation_x(angle: f64) -> RotationMatrix {
let c = angle.cos();
let s = angle.sin();
[
[1.0, 0.0, 0.0],
[0.0, c, -s],
[0.0, s, c],
]
}
pub fn rotation_y(angle: f64) -> RotationMatrix {
let c = angle.cos();
let s = angle.sin();
[
[c, 0.0, s],
[0.0, 1.0, 0.0],
[-s, 0.0, c],
]
}
pub fn rotation_z(angle: f64) -> RotationMatrix {
let c = angle.cos();
let s = angle.sin();
[
[c, -s, 0.0],
[s, c, 0.0],
[0.0, 0.0, 1.0],
]
}
pub fn rotation_from_euler(roll: f64, pitch: f64, yaw: f64) -> RotationMatrix {
let rx = rotation_x(roll);
let ry = rotation_y(pitch);
let rz = rotation_z(yaw);
let ry_rx = matrix_multiply(&ry, &rx);
matrix_multiply(&rz, &ry_rx)
}
pub fn triangular_holonomy(a: &RotationMatrix, b: &RotationMatrix, c: &RotationMatrix) -> HolonomyResult {
let ab = matrix_multiply(a, &transpose(b));
let bc = matrix_multiply(b, &transpose(c));
let ca = matrix_multiply(c, &transpose(a));
let cycle = vec![ab, bc, ca];
compute_holonomy(&cycle)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_holonomy() {
let cycle = vec![identity_matrix()];
let result = compute_holonomy(&cycle);
assert!(result.is_identity());
assert_eq!(result.norm, 0.0);
}
#[test]
fn test_double_identity() {
let cycle = vec![identity_matrix(), identity_matrix()];
let result = compute_holonomy(&cycle);
assert!(result.is_identity());
}
#[test]
fn test_rotation_cycle() {
let rx = rotation_x(std::f64::consts::FRAC_PI_2);
let ry = rotation_y(std::f64::consts::FRAC_PI_2);
let cycle = vec![rx.clone(), ry.clone(), ry.clone(), rx.clone()];
let result = compute_holonomy(&cycle);
assert!(result.norm < 3.5, "Holonomy norm should be small, got {}", result.norm);
}
#[test]
fn test_full_rotation() {
let rz = rotation_z(std::f64::consts::PI);
let cycle = vec![rz.clone(), rz];
let result = compute_holonomy(&cycle);
assert!(result.norm < 0.01, "Full rotation should return to identity");
}
#[test]
fn test_verify_holonomy() {
let cycles = vec![
vec![identity_matrix()],
vec![identity_matrix(), identity_matrix()],
];
assert!(verify_holonomy(&cycles, 1e-6));
}
#[test]
fn test_verify_holonomy_failure() {
let rz = rotation_z(std::f64::consts::FRAC_PI_4);
let cycles = vec![vec![rz]];
assert!(!verify_holonomy(&cycles, 1e-6));
}
#[test]
fn test_holonomy_checker() {
let mut checker = HolonomyChecker::default_tolerance();
checker.apply(&identity_matrix());
checker.apply(&identity_matrix());
let result = checker.check_closed();
assert!(result.is_identity());
assert_eq!(checker.step_count(), 2);
}
#[test]
fn test_holonomy_checker_reset() {
let mut checker = HolonomyChecker::default_tolerance();
checker.apply(&rotation_x(0.1));
assert_eq!(checker.step_count(), 1);
checker.reset();
assert_eq!(checker.step_count(), 0);
let result = checker.check_partial();
assert!(result.is_identity());
}
#[test]
fn test_rotation_from_euler() {
let r = rotation_from_euler(0.0, 0.0, 0.0);
let id = identity_matrix();
for i in 0..3 {
for j in 0..3 {
assert!((r[i][j] - id[i][j]).abs() < 1e-10);
}
}
}
#[test]
fn test_triangular_holonomy() {
let a = identity_matrix();
let b = identity_matrix();
let c = identity_matrix();
let result = triangular_holonomy(&a, &b, &c);
assert!(result.is_identity());
}
#[test]
fn test_angular_deviation() {
let result = compute_holonomy(&[identity_matrix()]);
assert_eq!(result.angular_deviation(), 0.0);
let rz = rotation_z(std::f64::consts::FRAC_PI_2);
let result = compute_holonomy(&[rz]);
let deviation = result.angular_deviation();
assert!(deviation > 1.4 && deviation < 1.6, "Expected ~π/2, got {}", deviation);
}
#[test]
fn test_information_content() {
let result = compute_holonomy(&[identity_matrix()]);
assert!(result.information.is_infinite());
let rz = rotation_z(0.1);
let result = compute_holonomy(&[rz]);
assert!(result.information.is_finite());
}
#[test]
fn test_matrix_multiply() {
let a = rotation_x(std::f64::consts::FRAC_PI_2);
let b = rotation_x(-std::f64::consts::FRAC_PI_2);
let ab = matrix_multiply(&a, &b);
let id = identity_matrix();
for i in 0..3 {
for j in 0..3 {
assert!((ab[i][j] - id[i][j]).abs() < 1e-10);
}
}
}
#[test]
fn test_transpose() {
let r = rotation_y(0.5);
let rt = transpose(&r);
let product = matrix_multiply(&r, &rt);
let id = identity_matrix();
for i in 0..3 {
for j in 0..3 {
assert!((product[i][j] - id[i][j]).abs() < 1e-10);
}
}
}
}