#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CayleyKleinSpace {
pub tau: f32,
}
impl CayleyKleinSpace {
#[inline(always)]
pub const fn new(tau: f32) -> Self {
Self { tau }
}
#[inline(always)]
pub const fn hyperbolic() -> Self {
Self { tau: -1.0 }
}
#[inline(always)]
pub const fn euclidean() -> Self {
Self { tau: 0.0 }
}
#[inline(always)]
pub const fn elliptic() -> Self {
Self { tau: 1.0 }
}
#[inline]
pub fn max_radius(&self) -> f32 {
if self.tau < -1e-7 {
1.0f32 / (-self.tau).sqrt()
} else {
f32::INFINITY
}
}
#[inline]
pub fn conformal_factor(&self, x: &[f32]) -> f32 {
let mut norm_sq = 0.0f32;
for &xi in x {
norm_sq += xi * xi;
}
let denom = 1.0f32 + self.tau * norm_sq;
2.0f32 / denom.max(1e-7)
}
pub fn geodesic_distance(&self, x: &[f32], y: &[f32]) -> f32 {
assert_eq!(x.len(), y.len(), "Dimension mismatch in Cayley-Klein distance");
let mut diff_sq = 0.0f32;
let mut x_sq = 0.0f32;
let mut y_sq = 0.0f32;
for i in 0..x.len() {
let d = x[i] - y[i];
diff_sq += d * d;
x_sq += x[i] * x[i];
y_sq += y[i] * y[i];
}
if self.tau.abs() < 1e-7 {
return diff_sq.sqrt();
}
if self.tau < 0.0 {
let abs_tau = -self.tau;
let inv_sqrt_tau = 1.0f32 / abs_tau.sqrt();
let u_factor = (1.0f32 - abs_tau * x_sq).max(1e-7);
let v_factor = (1.0f32 - abs_tau * y_sq).max(1e-7);
let denom = u_factor * v_factor;
let delta = 1.0f32 + (2.0f32 * abs_tau * diff_sq) / denom;
let delta_clamped = delta.max(1.0f32);
let arcosh = (delta_clamped + (delta_clamped * delta_clamped - 1.0f32).sqrt()).ln();
inv_sqrt_tau * arcosh
} else {
let tau = self.tau;
let inv_sqrt_tau = 1.0f32 / tau.sqrt();
let u_factor = 1.0f32 + tau * x_sq;
let v_factor = 1.0f32 + tau * y_sq;
let denom = (u_factor * v_factor).max(1e-7);
let arg = 1.0f32 - (2.0f32 * tau * diff_sq) / denom;
let arg_clamped = arg.clamp(-1.0f32, 1.0f32);
inv_sqrt_tau * arg_clamped.acos()
}
}
pub fn mobius_add(&self, u: &[f32], v: &[f32]) -> Vec<f32> {
assert_eq!(u.len(), v.len(), "Dimension mismatch in Cayley-Klein Mobius addition");
let n = u.len();
let mut uv_dot = 0.0f32;
let mut u_sq = 0.0f32;
let mut v_sq = 0.0f32;
for i in 0..n {
uv_dot += u[i] * v[i];
u_sq += u[i] * u[i];
v_sq += v[i] * v[i];
}
let tau = self.tau;
let num_u_coeff = 1.0f32 - 2.0f32 * tau * uv_dot - tau * v_sq;
let num_v_coeff = 1.0f32 + tau * u_sq;
let denom = 1.0f32 - 2.0f32 * tau * uv_dot + (tau * tau) * u_sq * v_sq;
let inv_denom = 1.0f32 / denom.max(1e-7);
let mut out = vec![0.0f32; n];
for i in 0..n {
out[i] = (num_u_coeff * u[i] + num_v_coeff * v[i]) * inv_denom;
}
out
}
pub fn project_to_euclidean(&self, x: &[f32]) -> Vec<f32> {
let mut norm_sq = 0.0f32;
for &xi in x {
norm_sq += xi * xi;
}
let denom = 1.0f32 + (self.tau * 0.25f32) * norm_sq;
let scale = 1.0f32 / denom.max(1e-7);
x.iter().map(|&xi| xi * scale).collect()
}
}