use crate::traits::{Bilinear, DivRing, Euclidean, Field, Form, Interval, Real, Vector};
use super::Point;
use itertools::Itertools;
use num_traits::{One, Zero};
pub trait Chart<P: Point, V: Vector>: Sized {
fn to_local(&self, point: &P) -> Option<V>;
fn to_global(&self, coord: V) -> P;
fn chart_at(p: &P) -> Self;
fn local_distance(&self, other: &P) -> Option<<V::F as Field>::Fixed>
where
V: Euclidean,
{
self.to_local(other).map(|v| v.norm())
}
#[cfg(feature = "testing")]
fn check_local_inverse(p: &P) -> bool
where
P: PartialEq,
{
let chart = Self::chart_at(p);
match chart.to_local(p) {
Some(local) => p == &chart.to_global(local),
None => false,
}
}
}
pub trait ExpMap<P: Point, V: Vector>: Chart<P, V> {
fn base_point(&self) -> P {
self.to_global(V::zero())
}
#[cfg(feature = "testing")]
fn check_base_point_is_origin(&self) -> bool
where
V: Form,
{
self.to_local(&self.base_point())
.map_or(false, |c| c.self_dot() == V::F::zero())
}
#[cfg(feature = "testing")]
fn check_preservation_of_origin(&self) -> bool
where
V: Form,
{
let zero = V::zero();
let exp_zero = self.to_global(zero);
self.to_local(&exp_zero)
.map_or(false, |c| c.self_dot() == V::F::zero())
}
#[cfg(feature = "testing")]
fn check_chart_at_base_point(&self) -> bool
where
V: Form,
{
Self::chart_at(&self.base_point()).check_preservation_of_origin()
}
#[cfg(feature = "testing")]
fn check_geodesic_symmetry(&self, v: V) -> bool
where
V: Form + PartialEq,
{
let fwd = match self.to_local(&self.to_global(v)) {
Some(x) => x,
None => return true,
};
let bwd = match self.to_local(&self.to_global(-v)) {
Some(x) => x,
None => return true,
};
if fwd.self_dot() != v.self_dot() || bwd.self_dot() != (-v).self_dot() {
return true;
}
fwd == -bwd
}
#[cfg(feature = "testing")]
fn check_geodesic_scaling(&self, v: V, t: <V::F as Field>::Fixed) -> bool
where
V: Form,
{
let t_as_f = V::F::from_fixed(t);
let v_local = match self.to_local(&self.to_global(v)) {
Some(x) => x,
None => return true,
};
let tv_local = match self.to_local(&self.to_global(v * t_as_f)) {
Some(x) => x,
None => return true,
};
if v_local.self_dot() != v.dot(&v) || tv_local.self_dot() != (v * t_as_f).self_dot() {
return true;
}
let dot = tv_local.dot(&v_local);
dot * dot == tv_local.self_dot() * v_local.self_dot()
}
}
pub trait PseudoRiemannian<V: Bilinear<F: Real>>: ExpMap<Self, V> + Interval<R = V::F> {
#[cfg(feature = "testing")]
fn check_isometry(&self, v: V) -> bool {
let global = self.to_global(v);
let local = match self.to_local(&global) {
Some(u) => u,
None => return true, };
let s = self.base_point().interval(&global);
s * s == local.norm_squared().into() }
}
impl<V: Bilinear<F: Real>, E: ExpMap<Self, V> + Interval<R = V::F>> PseudoRiemannian<V> for E {}
pub trait TangentBundle<P: Point, V: Vector>: ExpMap<P, V> {
fn sectional_curvature(&self, v: V, w: V, epsilon: V::F) -> Option<V::F>
where
V: Form,
{
let qv = v.self_dot();
let qw = w.self_dot();
let vw = v.dot(&w);
let gram = qv * qw - vw * vw;
if gram == V::F::zero() {
return None;
}
let perturbed = self.to_global(v + w * epsilon);
let delta = self.to_local(&perturbed)? - v;
let q_delta = delta.self_dot();
let eps2 = epsilon * epsilon;
let three = V::F::one() + V::F::one() + V::F::one();
let numerator = three * (eps2 * qw - q_delta).div(eps2 * eps2);
Some(numerator.div(gram))
}
fn max_sectional_curvature(&self, epsilon: V::F) -> Option<V::F>
where
V: Euclidean,
{
(0..V::N)
.array_combinations::<2>()
.filter_map(|[i, j]| {
let v = V::from_fn(|k| if k == i { V::F::one() } else { V::F::zero() });
let w = V::from_fn(|k| if k == j { V::F::one() } else { V::F::zero() });
self.sectional_curvature(v, w, epsilon)
})
.reduce(|max, k| if k > max { k } else { max })
}
#[cfg(feature = "testing")]
fn check_universal_centring(p: P) -> bool
where
V: Form,
{
let chart = Self::chart_at(&p);
chart.check_preservation_of_origin() && chart.check_base_point_is_origin()
}
}
pub trait Smooth<V: Vector>: Point {
fn exp(&self, v: V) -> Self;
fn log(&self, other: &Self) -> Option<V>;
}
impl<V: Vector, S: Smooth<V>> Chart<Self, V> for S {
fn to_local(&self, point: &Self) -> Option<V> {
self.log(point)
}
fn to_global(&self, coord: V) -> Self {
self.exp(coord)
}
fn chart_at(p: &Self) -> Self {
p.clone()
}
}
impl<V: Vector, L: Smooth<V>> ExpMap<Self, V> for L {
fn base_point(&self) -> Self {
self.clone()
}
}
impl<V: Vector, L: Smooth<V>> TangentBundle<Self, V> for L {}