use core::{marker::PhantomData, ops::Mul};
use crate::{
complex::Complex,
impl_group_via_mul, impl_lie_group_via_quotient,
quaternion::Quaternion,
traits::{Chart, Euclidean, Interval, LieGroup, Metric, Quotient, Real, RootOfUnity, Smooth},
};
use num_traits::{Inv, NumCast, One, Zero, real::Real as _};
#[derive(Debug, PartialEq, Clone)]
pub struct Sphere<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<V: Euclidean> Chart<Sphere<V>, V> for Stereographic<V> {
type Global = Sphere<V>;
fn to_local(&self, point: &Sphere<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.clone() * recip)
}
fn to_global(&self, coord: V) -> Sphere<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<V>) -> Self {
if p.real > V::F::zero() {
Self::south_pole()
} else {
Self::north_pole()
}
}
}
impl<V: Euclidean> Sphere<V> {
pub fn real(&self) -> V::F {
self.real
}
pub fn imag(&self) -> V {
self.imag.clone()
}
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_one() && 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.clone() - self.imag.clone() * cos_d;
let sin_d = (w_real * w_real + w_imag.norm_squared()).sqrt();
V::F::atan2(sin_d, cos_d) }
}
impl<V: Euclidean> Smooth<V> for Sphere<V> {
type Global = Self;
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.clone());
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<V: Euclidean> Sphere<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.clone();
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>(Sphere<V>);
impl_group_via_mul!(S0<V>, V: Euclidean);
#[derive(Debug, Clone, PartialEq)]
pub struct UnitComplex<V: Euclidean>(Sphere<V>);
impl_group_via_mul!(UnitComplex<V>, V: Euclidean);
#[derive(Debug, Clone, PartialEq)]
pub struct S3<V: Euclidean>(Sphere<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> S0<V> {
pub fn new(s: Sphere<V>) -> Self {
const { assert!(V::N == 0) }
Self(s)
}
pub fn to_inner(self) -> Sphere<V> {
self.0
}
pub fn inner(&self) -> &Sphere<V> {
&self.0
}
}
impl<V: Euclidean> UnitComplex<V> {
pub fn new(s: Sphere<V>) -> Self {
const { assert!(V::N == 1) }
Self(s)
}
pub fn to_inner(self) -> Sphere<V> {
self.0
}
pub fn inner(&self) -> &Sphere<V> {
&self.0
}
}
impl<V: Euclidean> S3<V> {
pub fn new(s: Sphere<V>) -> Self {
const { assert!(V::N == 3) }
Self(s)
}
pub fn to_inner(self) -> Sphere<V> {
self.0
}
pub fn inner(&self) -> &Sphere<V> {
&self.0
}
pub fn to_quaternion(&self) -> Quaternion<V::F> {
Quaternion::new(self.0.real, self.0.imag[0], self.0.imag[1], self.0.imag[2])
}
pub fn from_quaternion(quaternion: Quaternion<V::F>) -> Self {
let [real, i, j, k] = quaternion.into();
Self::new(Sphere::new(real, V::from_iter([i, j, k])))
}
}
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::new(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[0]);
let (a2, b2) = (rhs.0.real, rhs.0.imag[0]);
Self(Sphere::new(
a1 * a2 - b1 * b2,
V::from_iter([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];
Self::new(Sphere::new(alpha.cos(), V::from_iter([alpha.sin()])))
}
fn identity_log(p: &Self) -> Option<V> {
Some(V::from_iter([V::F::atan2(p.0.imag[0], p.0.real)]))
}
}
impl<V: Euclidean> One for S3<V> {
fn one() -> Self {
Self::new(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, a2) = (self.0.real, rhs.0.real);
let im1 = self.0.imag;
let im2 = rhs.0.imag;
let (b1, c1, d1, b2, c2, d2) = (im1[0], im1[1], im1[2], im2[0], im2[1], im2[2]);
Self(Sphere::new(
a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2,
V::from_iter([
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 = self.0.real();
let im = self.0.imag;
let (b, c, d) = (im[0], im[1], im[2]);
Self(Sphere::new(a, V::from_iter([-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::new(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.clone() * sinc_recip)
}
}
impl<V: Euclidean> Interval for Sphere<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<V: Euclidean> Metric for Sphere<V> {}
#[derive(Clone, Debug, PartialEq)]
pub struct So3<V: Euclidean>(S3<V>);
impl<V: Euclidean> Quotient<S3<V>, RootOfUnity<V::F, 2>, 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())
{
core::cmp::Ordering::Less => So3(S3(Sphere::new(-g.0.real(), -g.0.imag()))),
core::cmp::Ordering::Equal | core::cmp::Ordering::Greater => So3(g),
}
}
fn lift(&self) -> S3<V> {
self.0.clone()
}
fn embed(h: RootOfUnity<V::F, 2>) -> S3<V> {
S3(Sphere::new(h.inner(), V::zero()))
}
}
impl_lie_group_via_quotient!(So3<V>, S3<V>, RootOfUnity<V::F, 2>, V, V: Euclidean);
#[cfg(feature = "simplicial")]
mod simplicial {
use super::*;
use crate::epsilon_metric::R64;
use crate::{
coords::Coords,
impl_tangent_bundle_via_bounded,
traits::{
ExpMap, InnerProduct, TangentBundle,
simplicial::{Bounded, BuildNodes, NerveComplexParameters},
},
};
use std::vec::Vec;
#[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 {
type Global = So3<Coords<R64, 3>>;
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
{
}
}
#[cfg(feature = "simplicial")]
pub use simplicial::*;