use std::{marker::PhantomData, ops::Mul};
use crate::{
complex::Complex,
coords::Coords,
impl_group_via_mul, impl_lie_group_via_quotient, impl_tangent_bundle_via_bounded,
traits::{
Bounded, BuildNodes, Chart, Euclidean, ExpMap, InnerProduct, Interval, LieGroup, Metric,
NerveComplexParameters, Quotient, Real, Smooth, TangentBundle,
},
};
use num_traits::{Inv, NumCast, One, Zero, real::Real as _};
#[derive(Debug, PartialEq, Clone)]
pub struct Sphere<const N: usize, V: Euclidean> {
real: V::F,
imag: V,
}
#[derive(Clone, Debug)]
pub struct Stereographic<V: Euclidean>(StereographicPole, PhantomData<V>);
impl<V: Euclidean> Stereographic<V> {
pub const fn south_pole() -> Self {
Self(StereographicPole::SouthPole, PhantomData)
}
pub const fn north_pole() -> Self {
Self(StereographicPole::NorthPole, PhantomData)
}
}
#[derive(Clone, Debug)]
enum StereographicPole {
SouthPole,
NorthPole,
}
pub const EPSILON: f64 = 1e-3;
impl<const N: usize, V: Euclidean> Chart<Sphere<N, V>, V> for Stereographic<V> {
fn to_local(&self, point: &Sphere<N, V>) -> Option<V> {
let first = match self.0 {
StereographicPole::NorthPole => point.real,
StereographicPole::SouthPole => -point.real,
};
let epsilon = <V::F as NumCast>::from(EPSILON).unwrap();
let denom = V::F::one() - first;
if denom.abs() < epsilon {
return None;
}
let recip = denom.recip();
Some(point.imag * recip)
}
fn to_global(&self, coord: V) -> Sphere<N, V> {
let two = V::F::one() + V::F::one();
let r_sq = coord.norm_squared();
let denom = V::F::one() + r_sq;
Sphere::new(
match self.0 {
StereographicPole::NorthPole => (r_sq - V::F::one()) / denom,
StereographicPole::SouthPole => (V::F::one() - r_sq) / denom,
},
coord * (two / denom),
)
}
fn chart_at(p: &Sphere<N, V>) -> Self {
if p.real > V::F::zero() {
Self::south_pole()
} else {
Self::north_pole()
}
}
}
impl<const N: usize, V: Euclidean> Sphere<N, V> {
pub fn real(&self) -> V::F {
self.real
}
pub fn imag(&self) -> V {
self.imag
}
fn normalised(self) -> Self {
let real = self.real;
let imag = self.imag;
let sum = real * real + imag.iter().fold(V::F::zero(), |acc, &v| acc + v * v);
assert!(sum != V::F::zero());
let q_rsqrt = V::F::sqrt(sum).recip();
Self {
real: real * q_rsqrt,
imag: imag * q_rsqrt,
}
}
fn identity() -> Self {
Sphere::new(V::F::one(), V::zero())
}
fn is_identity(&self) -> bool {
self.real.is_zero() && self.imag.is_zero()
}
pub fn new(real: V::F, imag: V) -> Self {
let sphere = Sphere { real, imag };
sphere.normalised()
}
fn geodesic_distance(&self, other: &Self) -> V::F {
let cos_d = self.real * other.real + self.imag.dot(&other.imag);
let w_real = other.real - cos_d * self.real;
let w_imag = other.imag - self.imag * cos_d;
let sin_d = (w_real * w_real + w_imag.norm_squared()).sqrt();
V::F::atan2(sin_d, cos_d) }
}
impl<const N: usize, V: Euclidean> Smooth<V> for Sphere<N, V> {
fn exp(&self, v: V) -> Self {
let eps = <V::F as NumCast>::from(EPSILON).unwrap();
let alpha = v.norm();
let (sin_a, cos_a) = alpha.sin_cos();
let sinc = sinc_from(alpha, sin_a, eps);
self.transport_from_identity(cos_a, v * sinc)
}
fn log(&self, other: &Self) -> Option<V> {
let one = V::F::one();
let eps = <V::F as NumCast>::from(EPSILON).unwrap();
let p = self.transport_to_identity(other.real, other.imag);
if (p.real + one).abs() < eps {
return None; }
let alpha = V::F::atan2(p.imag.norm(), p.real);
let sinc_recip = sinc_recip(alpha, eps);
Some(p.imag * sinc_recip)
}
}
fn sinc_from<F: Real>(alpha: F, sin_a: F, eps: F) -> F {
let one = F::one();
if alpha < eps {
let six = (one + one) * (one + one + one);
one - alpha * alpha / six
} else {
sin_a / alpha
}
}
fn sinc_recip<F: Real>(alpha: F, eps: F) -> F {
let one = F::one();
if alpha < eps {
let six = (one + one) * (one + one + one);
one + alpha * alpha / six
} else {
alpha / alpha.sin()
}
}
impl<const N: usize, V: Euclidean> Sphere<N, V> {
fn far_pole_sign(&self) -> V::F {
if self.real > V::F::zero() {
-V::F::one()
} else {
V::F::one()
}
}
fn reflect(&self, s: V::F, x_real: V::F, x_imag: V) -> (V::F, V) {
let two = V::F::one() + V::F::one();
let u_real = self.real - s; let u_imag = self.imag;
let u_dot_u = u_real * u_real + u_imag.norm_squared(); let u_dot_x = u_real * x_real + u_imag.dot(&x_imag);
let c = two * u_dot_x / u_dot_u;
(x_real - c * u_real, x_imag - u_imag * c)
}
fn transport_to_identity(&self, x_real: V::F, x_imag: V) -> Self {
let s = self.far_pole_sign();
let (r, im) = self.reflect(s, x_real, x_imag); if s < V::F::zero() {
Sphere::new(-r, im)
} else {
Sphere::new(r, im)
} }
fn transport_from_identity(&self, x_real: V::F, x_imag: V) -> Self {
let s = self.far_pole_sign();
let (x_real, x_imag) = if s < V::F::zero() {
(-x_real, x_imag)
} else {
(x_real, x_imag)
};
let (r, im) = self.reflect(s, x_real, x_imag);
Sphere::new(r, im)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct S0<V: Euclidean>(pub Sphere<0, V>);
impl_group_via_mul!(S0<V>, V: Euclidean);
#[derive(Debug, Clone, PartialEq)]
pub struct UnitComplex<V: Euclidean>(pub Sphere<1, V>);
impl_group_via_mul!(UnitComplex<V>, V: Euclidean);
#[derive(Debug, Clone, PartialEq)]
pub struct S3<V: Euclidean>(pub Sphere<3, V>);
impl_group_via_mul!(S3<V>, V: Euclidean);
impl<V: Euclidean> Interval for S0<V> {
type R = V::F;
fn interval_squared(&self, other: &Self) -> V::F {
self.0.interval_squared(&other.0)
}
}
impl<V: Euclidean> Metric for S0<V> {}
impl<V: Euclidean> Interval for UnitComplex<V> {
type R = V::F;
fn interval_squared(&self, other: &Self) -> V::F {
self.0.interval_squared(&other.0)
}
}
impl<V: Euclidean> Metric for UnitComplex<V> {}
impl<V: Euclidean> Interval for S3<V> {
type R = V::F;
fn interval_squared(&self, other: &Self) -> V::F {
self.0.interval_squared(&other.0)
}
}
impl<V: Euclidean> Metric for S3<V> {}
impl<V: Euclidean> One for S0<V> {
fn one() -> Self {
Self(Sphere::identity())
}
fn is_one(&self) -> bool {
self.0.is_identity()
}
}
impl<V: Euclidean> Mul for S0<V> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self(Sphere::new(self.0.real * rhs.0.real, V::zero()))
}
}
impl<V: Euclidean> Inv for S0<V> {
type Output = Self;
fn inv(self) -> Self::Output {
Self(Sphere::new(self.0.real, V::zero()))
}
}
impl<V: Euclidean> LieGroup<V> for S0<V> {
fn identity_exp(_: V) -> Self {
Self::one()
}
fn identity_log(p: &Self) -> Option<V> {
if p.0.real > V::F::zero() {
Some(V::zero())
} else {
None
}
}
}
impl<V: Euclidean> One for UnitComplex<V> {
fn one() -> Self {
Self(Sphere::identity())
}
fn is_one(&self) -> bool {
self.0.is_identity()
}
}
impl<V: Euclidean> Mul for UnitComplex<V> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let (a1, [b1]) = (self.0.real, self.0.imag.to_array());
let (a2, [b2]) = (rhs.0.real, rhs.0.imag.to_array());
Self(Sphere::new(
a1 * a2 - b1 * b2,
V::from_array([a1 * b2 + a2 * b1]),
))
}
}
impl<V: Euclidean> Inv for UnitComplex<V> {
type Output = Self;
fn inv(self) -> Self::Output {
Self(Sphere::new(self.0.real, -self.0.imag))
}
}
impl<V: Euclidean> LieGroup<V> for UnitComplex<V> {
fn identity_exp(v: V) -> Self {
let alpha = v[0];
UnitComplex(Sphere::new(alpha.cos(), V::from_array([alpha.sin()])))
}
fn identity_log(p: &Self) -> Option<V> {
Some(V::from_array([V::F::atan2(p.0.imag[0], p.0.real)]))
}
}
impl<V: Euclidean> One for S3<V> {
fn one() -> Self {
Self(Sphere::identity())
}
fn is_one(&self) -> bool {
self.0.is_identity()
}
}
impl<V: Euclidean> Mul for S3<V> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let (a1, [b1, c1, d1]) = (self.0.real, self.0.imag.to_array());
let (a2, [b2, c2, d2]) = (rhs.0.real, rhs.0.imag.to_array());
Self(Sphere::new(
a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2,
V::from_array([
a1 * b2 + b1 * a2 + c1 * d2 - d1 * c2,
a1 * c2 - b1 * d2 + c1 * a2 + d1 * b2,
a1 * d2 + b1 * c2 - c1 * b2 + d1 * a2,
]),
))
}
}
impl<V: Euclidean> Inv for S3<V> {
type Output = Self;
fn inv(self) -> Self::Output {
let (a, [b, c, d]) = (self.0.real, self.0.imag.to_array());
Self(Sphere::new(a, V::from_array([-b, -c, -d])))
}
}
impl<V: Euclidean> LieGroup<V> for S3<V> {
fn identity_exp(v: V) -> Self {
let alpha = V::F::sqrt(v.iter().fold(V::F::zero(), |acc, &x| acc + x * x));
let (sin, cos) = alpha.sin_cos();
let sinc = sinc_from(alpha, sin, <V::F as NumCast>::from(EPSILON).unwrap());
Self(Sphere::new(cos, v * sinc))
}
fn identity_log(p: &Self) -> Option<V> {
let eps = <V::F as NumCast>::from(EPSILON).unwrap();
if (p.0.real + V::F::one()).abs() < eps {
return None; }
let imag_norm = p.0.imag.norm();
let alpha = V::F::atan2(imag_norm, p.0.real);
let sinc_recip = sinc_recip(alpha, eps);
Some(p.0.imag * sinc_recip)
}
}
impl<const N: usize, V: Euclidean> Interval for Sphere<N, V> {
type R = V::F;
fn interval(&self, other: &Self) -> Complex<V::F> {
self.geodesic_distance(other).into()
}
fn interval_squared(&self, other: &Self) -> V::F {
let d = self.geodesic_distance(other);
d * d
}
}
impl<const N: usize, V: Euclidean> Metric for Sphere<N, V> {}
#[derive(Clone, Debug, PartialEq)]
pub struct So3<V: Euclidean>(S3<V>);
impl<V: Euclidean> Quotient<S3<V>, S0<V>, V> for So3<V> {
fn new(g: S3<V>) -> Self {
match g
.0
.real()
.partial_cmp(&V::F::zero())
.unwrap()
.then(g.0.imag().iter().partial_cmp(V::zero().iter()).unwrap())
{
std::cmp::Ordering::Less => So3(S3(Sphere::new(-g.0.real(), -g.0.imag()))),
std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => So3(g),
}
}
fn lift(&self) -> S3<V> {
self.0.clone()
}
fn embed(h: S0<V>) -> S3<V> {
S3(Sphere::new(h.0.real(), V::zero()))
}
}
impl_lie_group_via_quotient!(So3<V>, S3<V>, S0<V>, V, V: Euclidean);
use crate::epsilon_metric::R64;
#[derive(PartialEq, Debug, Clone)]
pub struct S1Cover(UnitComplex<Coords<R64, 1>>);
impl Bounded<UnitComplex<Coords<R64, 1>>, UnitComplex<Coords<R64, 1>>, Coords<R64, 1>> for S1Cover {
fn sdf(&self, v: &Coords<R64, 1>) -> R64 {
v.norm() - R64(std::f64::consts::PI / 6.0 + 0.05)
}
}
impl From<UnitComplex<Coords<R64, 1>>> for S1Cover {
fn from(value: UnitComplex<Coords<R64, 1>>) -> Self {
Self(value)
}
}
impl AsRef<UnitComplex<Coords<R64, 1>>> for S1Cover {
fn as_ref(&self) -> &UnitComplex<Coords<R64, 1>> {
&self.0
}
}
impl_tangent_bundle_via_bounded!(
S1Cover, UnitComplex<Coords<R64, 1>>, UnitComplex<Coords<R64, 1>>, Coords<R64, 1>,
);
impl BuildNodes<S1Cover> for S1Cover {
fn build_nodes() -> Vec<Self> {
(0..6)
.map(|i| {
let angle: R64 = R64(i.into()) * R64(std::f64::consts::TAU) / R64(6.0);
S1Cover(UnitComplex(Sphere::new(angle.cos(), [angle.sin()].into())))
})
.collect()
}
}
impl
NerveComplexParameters<
UnitComplex<Coords<R64, 1>>,
Coords<R64, 1>,
UnitComplex<Coords<R64, 1>>,
S1Cover,
> for S1Cover
{
}
#[derive(PartialEq, Debug, Clone)]
pub struct So3Cover(So3<Coords<R64, 3>>);
impl Chart<So3<Coords<R64, 3>>, Coords<R64, 3>> for So3Cover {
fn to_local(&self, point: &So3<Coords<R64, 3>>) -> Option<Coords<R64, 3>> {
self.0.to_local(point)
}
fn to_global(&self, coord: Coords<R64, 3>) -> So3<Coords<R64, 3>> {
self.0.to_global(coord)
}
fn chart_at(p: &So3<Coords<R64, 3>>) -> Self {
Self(So3::chart_at(p))
}
}
impl ExpMap<So3<Coords<R64, 3>>, Coords<R64, 3>> for So3Cover {}
impl TangentBundle<So3<Coords<R64, 3>>, Coords<R64, 3>> for So3Cover {}
impl Bounded<So3<Coords<R64, 3>>, So3<Coords<R64, 3>>, Coords<R64, 3>> for So3Cover {
fn sdf(&self, v: &Coords<R64, 3>) -> R64 {
v.norm() - R64(0.42)
}
}
impl From<So3<Coords<R64, 3>>> for So3Cover {
fn from(value: So3<Coords<R64, 3>>) -> Self {
Self(value)
}
}
impl AsRef<So3<Coords<R64, 3>>> for So3Cover {
fn as_ref(&self) -> &So3<Coords<R64, 3>> {
&self.0
}
}
impl BuildNodes<Self> for So3Cover {
fn build_nodes() -> Vec<Self> {
let phi = (1.0 + 5f64.sqrt()) / 2.0;
let mut quats: Vec<[f64; 4]> = Vec::new();
for i in 0..4 {
for s in [-1.0, 1.0] {
let mut q = [0.0; 4];
q[i] = s;
quats.push(q);
}
}
for a in [-0.5, 0.5] {
for b in [-0.5, 0.5] {
for c in [-0.5, 0.5] {
for d in [-0.5, 0.5] {
quats.push([a, b, c, d]);
}
}
}
}
let even_perms: [[usize; 4]; 12] = [
[0, 1, 2, 3],
[0, 2, 3, 1],
[0, 3, 1, 2],
[1, 0, 3, 2],
[1, 2, 0, 3],
[1, 3, 2, 0],
[2, 0, 1, 3],
[2, 1, 3, 0],
[2, 3, 0, 1],
[3, 0, 2, 1],
[3, 1, 0, 2],
[3, 2, 1, 0],
];
let base = [phi / 2.0, 0.5, 1.0 / (2.0 * phi), 0.0];
for p in even_perms {
for s0 in [-1.0, 1.0] {
for s1 in [-1.0, 1.0] {
for s2 in [-1.0, 1.0] {
let vals = [s0 * base[0], s1 * base[1], s2 * base[2], base[3]];
let mut q = [0.0; 4];
for i in 0..4 {
q[p[i]] = vals[i];
}
quats.push(q);
}
}
}
}
debug_assert_eq!(quats.len(), 120);
let mut seen = std::collections::HashSet::new();
let mut nodes = Vec::new();
for mut q in quats {
if let Some(c) = q.iter().find(|c| c.abs() > 1e-9)
&& *c < 0.0
{
q = q.map(|x| -x);
}
if seen.insert(q.map(|c| (c * 1e6).round() as i64)) {
let [w, x, y, z] = q.map(R64);
nodes.push(So3Cover(So3::new(S3(Sphere::new(w, [x, y, z].into())))));
}
}
debug_assert_eq!(nodes.len(), 60);
nodes
}
}
impl NerveComplexParameters<So3<Coords<R64, 3>>, Coords<R64, 3>, So3<Coords<R64, 3>>, So3Cover>
for So3Cover
{
}