use super::FixedVector;
use super::FixedMatrix;
use super::compute_matrix::{ComputeMatrix, compute_lu_decompose};
use super::matrix_functions::{matrix_exp_compute, matrix_log_compute, matrix_sqrt_compute};
use super::decompose::{svd_decompose, qr_decompose, cholesky_decompose};
use super::kernels;
use super::Scalar;
use super::matrix_functions::MatrixFunctions;
use crate::fixed_point::fixed_num::{Compute, Fixed, FixedNum};
use crate::fixed_point::width::{Storage, WideInt};
use crate::fixed_point::core_types::errors::OverflowDetected;
type C<S, const F: u32> = Compute<Fixed<S, F>>;
#[inline]
fn raws<S: Storage, const F: u32>(v: &FixedVector<Fixed<S, F>>) -> Vec<S>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
(0..v.len()).map(|i| v[i].raw()).collect()
}
#[inline]
fn up_vec<S: Storage, const F: u32>(v: &FixedVector<Fixed<S, F>>) -> Vec<C<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
(0..v.len()).map(|i| Fixed::<S, F>::upscale_to_compute(v[i].raw())).collect()
}
#[inline]
fn down<S: Storage, const F: u32>(x: C<S, F>) -> Result<Fixed<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
Ok(Fixed::from_raw(Fixed::<S, F>::downscale_to_storage(x)?))
}
fn down_vec<S: Storage, const F: u32>(v: &[C<S, F>]) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let mut out = FixedVector::<Fixed<S, F>>::new(v.len());
for (i, x) in v.iter().enumerate() { out[i] = down(*x)?; }
Ok(out)
}
fn down_matrix<S: Storage, const F: u32>(m: &ComputeMatrix<Fixed<S, F>>) -> Result<FixedMatrix<Fixed<S, F>>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let mut out = FixedMatrix::<Fixed<S, F>>::new(m.rows(), m.cols());
for r in 0..m.rows() {
for c in 0..m.cols() { out.set(r, c, down(m.get(r, c))?); }
}
Ok(out)
}
#[inline]
fn c_add<S: Storage, const F: u32>(a: C<S, F>, b: C<S, F>) -> Result<C<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{ Fixed::<S, F>::compute_checked_add(a, b) }
#[inline]
fn c_sub<S: Storage, const F: u32>(a: C<S, F>, b: C<S, F>) -> Result<C<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{ Fixed::<S, F>::compute_checked_add(a, Fixed::<S, F>::compute_negate(b)) }
fn dot_c<S: Storage, const F: u32>(a: &[C<S, F>], b: &[C<S, F>]) -> Result<C<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
kernels::narrow_product_to_compute::<Fixed<S, F>>(kernels::exact_dot_compute::<Fixed<S, F>>(a, b)?)
}
fn mdot_c<S: Storage, const F: u32>(a: &[C<S, F>], b: &[C<S, F>]) -> Result<C<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
kernels::narrow_product_to_compute::<Fixed<S, F>>(kernels::exact_dot_compute::<Fixed<S, F>>(&a[1..], &b[1..])?.add_exact(-S::widen_product(a[0], b[0]))?)
}
#[inline]
fn mul_div_c<S: Storage, const F: u32>(x: C<S, F>, y: C<S, F>, z: C<S, F>) -> Result<C<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
kernels::divide_to_compute_nearest::<Fixed<S, F>>(S::widen_product(x, y), z)
}
#[inline]
fn sqrt_c<S: Storage, const F: u32>(x: C<S, F>) -> C<S, F>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
if Fixed::<S, F>::compute_is_negative(&x) { Fixed::<S, F>::make_compute_int(0) } else { Fixed::<S, F>::sqrt_at_compute_tier(x) }
}
fn sqrt_det_c<S: Storage, const F: u32>(a: C<S, F>, b: C<S, F>, c: C<S, F>, d: C<S, F>) -> Result<C<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
Ok(sqrt_c(kernels::narrow_product_to_compute::<Fixed<S, F>>(S::widen_product(a, b).add_exact(-S::widen_product(c, d))?)?))
}
fn atan2_c<S: Storage, const F: u32>(y: C<S, F>, x: C<S, F>) -> C<S, F>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
Fixed::<S, F>::atan2_at_compute_tier(y, x)
}
fn sinhcosh_c<S: Storage, const F: u32>(x: C<S, F>) -> Result<(C<S, F>, C<S, F>), OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let ep = Fixed::<S, F>::exp_at_compute_tier(x);
let en = Fixed::<S, F>::exp_at_compute_tier(Fixed::<S, F>::compute_negate(x));
if Fixed::<S, F>::exp_sentinel_reached(&ep) || Fixed::<S, F>::exp_sentinel_reached(&en) {
return Err(OverflowDetected::TierOverflow);
}
Ok((Fixed::<S, F>::compute_halve(Fixed::<S, F>::compute_subtract(ep, en)), Fixed::<S, F>::compute_halve(Fixed::<S, F>::compute_checked_add(ep, en)?)))
}
fn frobenius_sq_c<S: Storage, const F: u32>(m: &ComputeMatrix<Fixed<S, F>>) -> Result<C<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let mut v = Vec::with_capacity(m.rows() * m.cols());
for r in 0..m.rows() {
for c in 0..m.cols() { v.push(m.get(r, c)); }
}
dot_c(&v, &v)
}
fn euclidean_norm<S: Storage, const F: u32>(v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let r = raws(v);
Fixed::from_raw(kernels::sqrt_dot::<Fixed<S, F>>(&r, &r).expect("manifold norm exceeds the storage range"))
}
pub trait Manifold<T: Scalar> {
fn dimension(&self) -> usize;
fn inner_product(
&self,
base: &FixedVector<T>,
u: &FixedVector<T>,
v: &FixedVector<T>,
) -> T;
fn norm(&self, base: &FixedVector<T>, v: &FixedVector<T>) -> T {
T::sqrt_scalar(self.inner_product(base, v, v))
}
fn exp_map(
&self,
base: &FixedVector<T>,
tangent: &FixedVector<T>,
) -> Result<FixedVector<T>, OverflowDetected>;
fn log_map(
&self,
base: &FixedVector<T>,
target: &FixedVector<T>,
) -> Result<FixedVector<T>, OverflowDetected>;
fn distance(
&self,
p: &FixedVector<T>,
q: &FixedVector<T>,
) -> Result<T, OverflowDetected>;
fn parallel_transport(
&self,
base: &FixedVector<T>,
target: &FixedVector<T>,
tangent: &FixedVector<T>,
) -> Result<FixedVector<T>, OverflowDetected>;
}
pub struct EuclideanSpace {
pub dim: usize,
}
impl<S: Storage, const F: u32> Manifold<Fixed<S, F>> for EuclideanSpace
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
fn dimension(&self) -> usize { self.dim }
fn inner_product(&self, _base: &FixedVector<Fixed<S, F>>, u: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
u.dot_precise(v)
}
fn norm(&self, _base: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
euclidean_norm(v)
}
fn exp_map(&self, base: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
Ok(base + tangent)
}
fn log_map(&self, base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
Ok(target - base)
}
fn distance(&self, p: &FixedVector<Fixed<S, F>>, q: &FixedVector<Fixed<S, F>>) -> Result<Fixed<S, F>, OverflowDetected> {
Ok(p.metric_distance_safe(q))
}
fn parallel_transport(&self, _base: &FixedVector<Fixed<S, F>>, _target: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
Ok(tangent.clone()) }
}
pub struct Sphere {
pub dim: usize, }
impl Sphere {
fn geodesic<S: Storage, const F: u32>(p: &FixedVector<Fixed<S, F>>, q: &FixedVector<Fixed<S, F>>) -> Result<(C<S, F>, Vec<C<S, F>>, C<S, F>), OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
assert_eq!(p.len(), q.len(), "Sphere: dimension mismatch");
let (pr, qr) = (raws(p), raws(q));
let pp = kernels::exact_dot::<Fixed<S, F>>(&pr, &pr)?;
let qq = kernels::exact_dot::<Fixed<S, F>>(&qr, &qr)?;
if Fixed::<S, F>::compute_is_zero(&pp) || Fixed::<S, F>::compute_is_zero(&qq) {
return Err(OverflowDetected::DomainError);
}
let c = kernels::exact_dot::<Fixed<S, F>>(&pr, &qr)?;
let theta = atan2_c(sqrt_det_c(pp, qq, c, c)?, c);
let mut w = Vec::with_capacity(p.len());
for i in 0..p.len() {
let along = mul_div_c(c, Fixed::<S, F>::upscale_to_compute(pr[i]), pp)?;
w.push(c_sub(Fixed::<S, F>::upscale_to_compute(qr[i]), along)?);
}
let w_len = sqrt_c(dot_c(&w, &w)?);
Ok((theta, w, w_len))
}
}
impl<S: Storage, const F: u32> Manifold<Fixed<S, F>> for Sphere
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
fn dimension(&self) -> usize { self.dim }
fn inner_product(&self, _base: &FixedVector<Fixed<S, F>>, u: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
u.dot_precise(v)
}
fn norm(&self, _base: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
euclidean_norm(v)
}
fn exp_map(&self, base: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let vr = raws(tangent);
if Fixed::<S, F>::compute_is_zero(&kernels::exact_dot::<Fixed<S, F>>(&vr, &vr)?) {
return Ok(base.clone());
}
let br = raws(base);
exact_forms::settled(&exact_forms::SphereExp { p: &br, v: &vr }, || Self::exp_map_compute(base, tangent))
}
fn log_map(&self, base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let (pr, qr) = (raws(base), raws(target));
let pp = kernels::exact_dot::<Fixed<S, F>>(&pr, &pr)?;
let qq = kernels::exact_dot::<Fixed<S, F>>(&qr, &qr)?;
if Fixed::<S, F>::compute_is_zero(&pp) || Fixed::<S, F>::compute_is_zero(&qq) { return Err(OverflowDetected::DomainError); }
let c = kernels::exact_dot::<Fixed<S, F>>(&pr, &qr)?;
if S::widen_product(pp, qq) == S::widen_product(c, c) { return Ok(FixedVector::<Fixed<S, F>>::new(base.len())); }
exact_forms::settled(&exact_forms::SphereLog { p: &pr, q: &qr }, || Self::log_map_compute(base, target))
}
fn distance(&self, p: &FixedVector<Fixed<S, F>>, q: &FixedVector<Fixed<S, F>>) -> Result<Fixed<S, F>, OverflowDetected> {
let (pr, qr) = (raws(p), raws(q));
assert_eq!(pr.len(), qr.len(), "Sphere: dimension mismatch");
if Fixed::<S, F>::compute_is_zero(&kernels::exact_dot::<Fixed<S, F>>(&pr, &pr)?) || Fixed::<S, F>::compute_is_zero(&kernels::exact_dot::<Fixed<S, F>>(&qr, &qr)?) {
return Err(OverflowDetected::DomainError);
}
Ok(exact_forms::settled(&exact_forms::SphereDistance { p: &pr, q: &qr }, || Ok(FixedVector::from_slice(&[Self::distance_compute(p, q)?])))?[0])
}
fn parallel_transport(&self, base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let (pr, qr, vr) = (raws(base), raws(target), raws(tangent));
let denom = c_add(Fixed::<S, F>::make_compute_int(1), kernels::exact_dot::<Fixed<S, F>>(&pr, &qr)?)?;
if Fixed::<S, F>::compute_is_zero(&denom) { return Err(OverflowDetected::DomainError); }
exact_forms::settled(&exact_forms::SphereTransport { p: &pr, q: &qr, v: &vr }, || Self::transport_compute(base, target, tangent))
}
}
impl Sphere {
fn exp_map_compute<S: Storage, const F: u32>(base: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let vr = raws(tangent);
let theta = sqrt_c(kernels::exact_dot::<Fixed<S, F>>(&vr, &vr)?);
if Fixed::<S, F>::compute_is_zero(&theta) {
return Ok(base.clone());
}
let (sin_t, cos_t) = Fixed::<S, F>::sincos_at_compute_tier(theta);
let mut out = Vec::with_capacity(base.len());
for i in 0..base.len() {
let along = kernels::compute_product::<Fixed<S, F>>(cos_t, Fixed::<S, F>::upscale_to_compute(base[i].raw()))?;
let across = mul_div_c(sin_t, Fixed::<S, F>::upscale_to_compute(vr[i]), theta)?;
out.push(c_add(along, across)?);
}
down_vec(&out)
}
fn log_map_compute<S: Storage, const F: u32>(base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let (theta, w, w_len) = Self::geodesic(base, target)?;
if Fixed::<S, F>::compute_is_zero(&theta) || Fixed::<S, F>::compute_is_zero(&w_len) {
return Ok(FixedVector::<Fixed<S, F>>::new(base.len()));
}
let mut out = Vec::with_capacity(w.len());
for x in &w { out.push(mul_div_c(*x, theta, w_len)?); }
down_vec(&out)
}
fn distance_compute<S: Storage, const F: u32>(p: &FixedVector<Fixed<S, F>>, q: &FixedVector<Fixed<S, F>>) -> Result<Fixed<S, F>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
down(Self::geodesic(p, q)?.0)
}
fn transport_compute<S: Storage, const F: u32>(base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let (pr, qr, vr) = (raws(base), raws(target), raws(tangent));
let denom = c_add(Fixed::<S, F>::make_compute_int(1), kernels::exact_dot::<Fixed<S, F>>(&pr, &qr)?)?;
if Fixed::<S, F>::compute_is_zero(&denom) {
return Err(OverflowDetected::DomainError);
}
let num = c_add(kernels::exact_dot::<Fixed<S, F>>(&vr, &pr)?, kernels::exact_dot::<Fixed<S, F>>(&vr, &qr)?)?;
let mut out = Vec::with_capacity(vr.len());
for i in 0..vr.len() {
let sum_i = c_add(Fixed::<S, F>::upscale_to_compute(pr[i]), Fixed::<S, F>::upscale_to_compute(qr[i]))?;
out.push(c_sub(Fixed::<S, F>::upscale_to_compute(vr[i]), mul_div_c(num, sum_i, denom)?)?);
}
down_vec(&out)
}
}
pub struct HyperbolicSpace {
pub dim: usize, }
impl HyperbolicSpace {
fn minkowski_dot<S: Storage, const F: u32>(u: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
Fixed::from_raw(kernels::round_to_storage::<Fixed<S, F>>(Self::minkowski_dot_compute(u, v)))
}
fn minkowski_dot_compute<S: Storage, const F: u32>(u: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Compute<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
assert_eq!(u.len(), v.len());
let n = u.len();
if n == 0 { return Fixed::<S, F>::upscale_to_compute(Fixed::ZERO.raw()); }
let u_raw: Vec<S> = (1..n).map(|i| u[i].raw()).collect();
let v_raw: Vec<S> = (1..n).map(|i| v[i].raw()).collect();
let spatial = kernels::dot_acc::<Fixed<S, F>>(&u_raw, &v_raw);
let temporal = kernels::dot_acc::<Fixed<S, F>>(&[u[0].raw()], &[v[0].raw()]);
Fixed::<S, F>::compute_checked_add(spatial, Fixed::<S, F>::compute_negate(temporal)).expect("Minkowski product exceeds the compute tier")
}
fn minkowski_norm<S: Storage, const F: u32>(v: &FixedVector<Fixed<S, F>>) -> Result<Fixed<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let dot = Self::minkowski_dot_compute(v, v);
let dot = if Fixed::<S, F>::compute_is_negative(&dot) { Fixed::<S, F>::compute_negate(dot) } else { dot };
Ok(Fixed::from_raw(Fixed::<S, F>::downscale_to_storage(Fixed::<S, F>::sqrt_at_compute_tier(dot))?))
}
fn geodesic<S: Storage, const F: u32>(p: &FixedVector<Fixed<S, F>>, q: &FixedVector<Fixed<S, F>>) -> Result<(C<S, F>, Vec<C<S, F>>, C<S, F>), OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
assert_eq!(p.len(), q.len(), "HyperbolicSpace: dimension mismatch");
let alpha = Fixed::<S, F>::compute_negate(Self::minkowski_dot_compute(p, q));
let pp = Fixed::<S, F>::compute_negate(Self::minkowski_dot_compute(p, p));
let qq = Fixed::<S, F>::compute_negate(Self::minkowski_dot_compute(q, q));
let zero = Fixed::<S, F>::make_compute_int(0);
let positive = |x: &C<S, F>| !Fixed::<S, F>::compute_is_negative(x) && !Fixed::<S, F>::compute_is_zero(x);
if !(positive(&pp) && positive(&qq) && positive(&alpha)) {
return Err(OverflowDetected::DomainError);
}
let sinh_part = sqrt_det_c(alpha, alpha, pp, qq)?;
let scale = sqrt_c(kernels::narrow_product_to_compute::<Fixed<S, F>>(S::widen_product(pp, qq))?);
let ratio = kernels::divide_to_compute_nearest::<Fixed<S, F>>(S::widen_product(c_add(alpha, sinh_part)?, Fixed::<S, F>::make_compute_int(1)), scale)?;
let d = Fixed::<S, F>::ln_at_compute_tier(ratio);
let d = if Fixed::<S, F>::compute_is_negative(&d) { zero } else { d };
let (pc, qc) = (up_vec(p), up_vec(q));
let mut w = Vec::with_capacity(p.len());
for i in 0..p.len() {
w.push(c_sub(qc[i], mul_div_c(alpha, pc[i], pp)?)?);
}
let w_len = sqrt_c(mdot_c(&w, &w)?);
Ok((d, w, w_len))
}
}
impl<S: Storage, const F: u32> Manifold<Fixed<S, F>> for HyperbolicSpace
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
fn dimension(&self) -> usize { self.dim }
fn inner_product(&self, _base: &FixedVector<Fixed<S, F>>, u: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
Self::minkowski_dot(u, v)
}
fn norm(&self, _base: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
Self::minkowski_norm(v).unwrap_or(Fixed::ZERO)
}
fn exp_map(&self, base: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let theta_sq = Self::minkowski_dot_compute(tangent, tangent);
if Fixed::<S, F>::compute_is_zero(&theta_sq) { return Ok(base.clone()); }
let (br, vr) = (raws(base), raws(tangent));
let timelike = Fixed::<S, F>::compute_is_negative(&theta_sq);
exact_forms::settled(&exact_forms::HypExp { p: &br, v: &vr, timelike }, || Self::exp_map_compute(base, tangent))
}
fn log_map(&self, base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
match Self::degenerate(base, target)? {
true => Ok(FixedVector::<Fixed<S, F>>::new(base.len())),
false => exact_forms::settled(&exact_forms::HypLog { p: &raws(base), q: &raws(target) }, || Self::log_map_compute(base, target)),
}
}
fn distance(&self, p: &FixedVector<Fixed<S, F>>, q: &FixedVector<Fixed<S, F>>) -> Result<Fixed<S, F>, OverflowDetected> {
if Self::degenerate(p, q)? { return Ok(Fixed::ZERO); }
Ok(exact_forms::settled(&exact_forms::HypDistance { p: &raws(p), q: &raws(q) }, || Ok(FixedVector::from_slice(&[Self::distance_compute(p, q)?])))?[0])
}
fn parallel_transport(&self, base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
if Self::degenerate(base, target)? { return Ok(tangent.clone()); }
exact_forms::settled(&exact_forms::HypTransport { p: &raws(base), q: &raws(target), v: &raws(tangent) }, || Self::transport_compute(base, target, tangent))
}
}
impl HyperbolicSpace {
fn degenerate<S: Storage, const F: u32>(p: &FixedVector<Fixed<S, F>>, q: &FixedVector<Fixed<S, F>>) -> Result<bool, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
assert_eq!(p.len(), q.len(), "HyperbolicSpace: dimension mismatch");
let alpha = Fixed::<S, F>::compute_negate(Self::minkowski_dot_compute(p, q));
let pp = Fixed::<S, F>::compute_negate(Self::minkowski_dot_compute(p, p));
let qq = Fixed::<S, F>::compute_negate(Self::minkowski_dot_compute(q, q));
let positive = |x: &C<S, F>| !Fixed::<S, F>::compute_is_negative(x) && !Fixed::<S, F>::compute_is_zero(x);
if !(positive(&pp) && positive(&qq) && positive(&alpha)) { return Err(OverflowDetected::DomainError); }
Ok(S::widen_product(alpha, alpha) == S::widen_product(pp, qq))
}
fn exp_map_compute<S: Storage, const F: u32>(base: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let tc = up_vec(tangent);
let theta_sq = Self::minkowski_dot_compute(tangent, tangent);
let theta = sqrt_c(if Fixed::<S, F>::compute_is_negative(&theta_sq) { Fixed::<S, F>::compute_negate(theta_sq) } else { theta_sq });
if Fixed::<S, F>::compute_is_zero(&theta) {
return Ok(base.clone());
}
let (sinh_t, cosh_t) = sinhcosh_c(theta)?;
let mut out = Vec::with_capacity(base.len());
for i in 0..base.len() {
let along = kernels::compute_product::<Fixed<S, F>>(cosh_t, Fixed::<S, F>::upscale_to_compute(base[i].raw()))?;
out.push(c_add(along, mul_div_c(sinh_t, tc[i], theta)?)?);
}
down_vec(&out)
}
fn log_map_compute<S: Storage, const F: u32>(base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let (d, w, w_len) = Self::geodesic(base, target)?;
if Fixed::<S, F>::compute_is_zero(&d) || Fixed::<S, F>::compute_is_zero(&w_len) {
return Ok(FixedVector::<Fixed<S, F>>::new(base.len()));
}
let mut out = Vec::with_capacity(w.len());
for x in &w { out.push(mul_div_c(*x, d, w_len)?); }
down_vec(&out)
}
fn distance_compute<S: Storage, const F: u32>(p: &FixedVector<Fixed<S, F>>, q: &FixedVector<Fixed<S, F>>) -> Result<Fixed<S, F>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
down(Self::geodesic(p, q)?.0)
}
fn transport_compute<S: Storage, const F: u32>(base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>, tangent: &FixedVector<Fixed<S, F>>) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let (d, w, w_len) = Self::geodesic(base, target)?;
if Fixed::<S, F>::compute_is_zero(&d) || Fixed::<S, F>::compute_is_zero(&w_len) {
return Ok(tangent.clone());
}
let one = Fixed::<S, F>::make_compute_int(1);
let vc = up_vec(tangent);
let vw = kernels::exact_dot_compute::<Fixed<S, F>>(&vc[1..], &w[1..])?.add_exact(-S::widen_product(vc[0], w[0]))?;
let coeff = kernels::divide_to_compute_nearest::<Fixed<S, F>>(vw, w_len)?;
let (sinh_d, cosh_d) = sinhcosh_c(d)?;
let cosh_m1 = c_sub(cosh_d, one)?;
let mut out = Vec::with_capacity(vc.len());
for i in 0..vc.len() {
let u_i = mul_div_c(w[i], one, w_len)?;
let dir = c_add(kernels::compute_product::<Fixed<S, F>>(sinh_d, Fixed::<S, F>::upscale_to_compute(base[i].raw()))?, kernels::compute_product::<Fixed<S, F>>(cosh_m1, u_i)?)?;
out.push(c_add(vc[i], kernels::compute_product::<Fixed<S, F>>(coeff, dir)?)?);
}
down_vec(&out)
}
}
pub struct SPDManifold {
pub n: usize,
}
fn sym_to_vec<S: Storage, const F: u32>(m: &FixedMatrix<Fixed<S, F>>) -> FixedVector<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let n = m.rows();
let dim = n * (n + 1) / 2;
let mut v = FixedVector::<Fixed<S, F>>::new(dim);
let mut k = 0;
for i in 0..n {
for j in i..n {
v[k] = m.get(i, j);
k += 1;
}
}
v
}
fn vec_to_sym<S: Storage, const F: u32>(v: &FixedVector<Fixed<S, F>>, n: usize) -> FixedMatrix<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let mut m = FixedMatrix::<Fixed<S, F>>::new(n, n);
let mut k = 0;
for i in 0..n {
for j in i..n {
m.set(i, j, v[k]);
m.set(j, i, v[k]); k += 1;
}
}
m
}
impl SPDManifold {
fn sqrt_and_inv_sqrt<S: Storage, const F: u32>(p: &FixedMatrix<Fixed<S, F>>) -> Result<(ComputeMatrix<Fixed<S, F>>, ComputeMatrix<Fixed<S, F>>), OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
cholesky_decompose(p)?;
let sqrt_p = matrix_sqrt_compute(&ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(p))?;
let inv_sqrt_p = compute_lu_decompose(&sqrt_p)?.inverse()?;
Ok((sqrt_p, inv_sqrt_p))
}
fn inverse_compute<S: Storage, const F: u32>(p: &FixedMatrix<Fixed<S, F>>) -> Result<ComputeMatrix<Fixed<S, F>>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
compute_lu_decompose(&ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(p))?.inverse()
}
fn trace_of_product<S: Storage, const F: u32>(a: &ComputeMatrix<Fixed<S, F>>, b: &ComputeMatrix<Fixed<S, F>>) -> Result<C<S, F>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let n = a.rows();
let mut x = Vec::with_capacity(n * n);
let mut y = Vec::with_capacity(n * n);
for i in 0..n {
for j in 0..n {
x.push(a.get(i, j));
y.push(b.get(j, i));
}
}
dot_c(&x, &y)
}
}
impl<S: Storage, const F: u32> Manifold<Fixed<S, F>> for SPDManifold
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
fn dimension(&self) -> usize {
self.n * (self.n + 1) / 2
}
fn inner_product(
&self,
base: &FixedVector<Fixed<S, F>>,
u: &FixedVector<Fixed<S, F>>,
v: &FixedVector<Fixed<S, F>>,
) -> Fixed<S, F> {
let p_inv = Self::inverse_compute(&vec_to_sym(base, self.n))
.expect("SPDManifold::inner_product: base point is singular");
let a = p_inv.mat_mul(&ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&vec_to_sym(u, self.n)));
let b = p_inv.mat_mul(&ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&vec_to_sym(v, self.n)));
down(Self::trace_of_product(&a, &b).expect("SPDManifold::inner_product exceeds the compute tier"))
.expect("SPDManifold::inner_product exceeds the storage range")
}
fn norm(&self, base: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
let p_inv = Self::inverse_compute(&vec_to_sym(base, self.n))
.expect("SPDManifold::norm: base point is singular");
let a = p_inv.mat_mul(&ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&vec_to_sym(v, self.n)));
down(sqrt_c(Self::trace_of_product(&a, &a).expect("SPDManifold::norm exceeds the compute tier")))
.expect("SPDManifold::norm exceeds the storage range")
}
fn exp_map(
&self,
base: &FixedVector<Fixed<S, F>>,
tangent: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let (sqrt_p, inv_sqrt_p) = Self::sqrt_and_inv_sqrt(&vec_to_sym(base, self.n))?;
let v = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&vec_to_sym(tangent, self.n));
let inner = inv_sqrt_p.mat_mul(&v).mat_mul(&inv_sqrt_p);
let result = sqrt_p.mat_mul(&matrix_exp_compute(&inner)?).mat_mul(&sqrt_p);
Ok(sym_to_vec(&down_matrix(&result)?))
}
fn log_map(
&self,
base: &FixedVector<Fixed<S, F>>,
target: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let (sqrt_p, inv_sqrt_p) = Self::sqrt_and_inv_sqrt(&vec_to_sym(base, self.n))?;
let q = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&vec_to_sym(target, self.n));
let inner = inv_sqrt_p.mat_mul(&q).mat_mul(&inv_sqrt_p);
let result = sqrt_p.mat_mul(&matrix_log_compute(&inner)?).mat_mul(&sqrt_p);
Ok(sym_to_vec(&down_matrix(&result)?))
}
fn distance(
&self,
p: &FixedVector<Fixed<S, F>>,
q: &FixedVector<Fixed<S, F>>,
) -> Result<Fixed<S, F>, OverflowDetected> {
let (_, inv_sqrt_p) = Self::sqrt_and_inv_sqrt(&vec_to_sym(p, self.n))?;
let q = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&vec_to_sym(q, self.n));
let log_inner = matrix_log_compute(&inv_sqrt_p.mat_mul(&q).mat_mul(&inv_sqrt_p))?;
down(sqrt_c(frobenius_sq_c(&log_inner)?))
}
fn parallel_transport(
&self,
base: &FixedVector<Fixed<S, F>>,
target: &FixedVector<Fixed<S, F>>,
tangent: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let p = vec_to_sym(base, self.n);
cholesky_decompose(&p)?;
let q = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&vec_to_sym(target, self.n));
let v = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&vec_to_sym(tangent, self.n));
let e = matrix_sqrt_compute(&q.mat_mul(&Self::inverse_compute(&p)?))?;
let result = e.mat_mul(&v).mat_mul(&e.transpose());
Ok(sym_to_vec(&down_matrix(&result)?))
}
}
pub struct Grassmannian {
pub k: usize, pub n: usize, }
impl Grassmannian {
fn mat_to_vec<S: Storage, const F: u32>(m: &FixedMatrix<Fixed<S, F>>) -> FixedVector<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let len = m.rows() * m.cols();
let mut v = FixedVector::<Fixed<S, F>>::new(len);
let mut idx = 0;
for c in 0..m.cols() {
for r in 0..m.rows() {
v[idx] = m.get(r, c);
idx += 1;
}
}
v
}
fn vec_to_mat<S: Storage, const F: u32>(v: &FixedVector<Fixed<S, F>>, n: usize, k: usize) -> FixedMatrix<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let mut m = FixedMatrix::<Fixed<S, F>>::new(n, k);
let mut idx = 0;
for c in 0..k {
for r in 0..n {
m.set(r, c, v[idx]);
idx += 1;
}
}
m
}
#[allow(dead_code)]
fn project_tangent<S: Storage, const F: u32>(q: &FixedMatrix<Fixed<S, F>>, delta: &FixedMatrix<Fixed<S, F>>) -> FixedMatrix<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let q_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(q);
let delta_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(delta);
let qt_delta_c = q_c.transpose().mat_mul(&delta_c);
delta_c.sub(&q_c.mat_mul(&qt_delta_c)).to_fixed_matrix()
}
}
struct GrassmannLog<T: FixedNum> {
u: ComputeMatrix<T>,
theta: Vec<Compute<T>>,
a: ComputeMatrix<T>,
}
impl Grassmannian {
fn column_norms<S: Storage, const F: u32>(m: &ComputeMatrix<Fixed<S, F>>) -> Result<Vec<C<S, F>>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
(0..m.cols()).map(|c| { let col = m.col_vec(c); Ok(sqrt_c(dot_c(&col, &col)?)) }).collect()
}
fn log_parts<S: Storage, const F: u32>(&self, q1: &FixedMatrix<Fixed<S, F>>, q2: &FixedMatrix<Fixed<S, F>>) -> Result<GrassmannLog<Fixed<S, F>>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let q1_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(q1);
let q2_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(q2);
let cross = q1_c.transpose().mat_mul(&q2_c); let svd = svd_decompose(&down_matrix(&cross)?)?; let b = Self::jacobi_refine(&cross.transpose().mat_mul(&cross),
ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&svd.vt.transpose()))?;
let y = q2_c.mat_mul(&b);
let c = q1_c.transpose().mat_mul(&y);
let perp = y.sub(&q1_c.mat_mul(&c));
let (sin_norms, cos_norms) = (Self::column_norms(&perp)?, Self::column_norms(&c)?);
let k = c.cols();
let one = Fixed::<S, F>::make_compute_int(1);
let mut u = ComputeMatrix::<Fixed<S, F>>::new(perp.rows(), k);
let mut a = ComputeMatrix::<Fixed<S, F>>::new(k, k);
let mut theta = Vec::with_capacity(k);
for i in 0..k {
theta.push(atan2_c(sin_norms[i], cos_norms[i]));
if !Fixed::<S, F>::compute_is_zero(&sin_norms[i]) {
for r in 0..perp.rows() { u.set(r, i, mul_div_c(perp.get(r, i), one, sin_norms[i])?); }
}
if Fixed::<S, F>::compute_is_zero(&cos_norms[i]) {
for r in 0..k { a.set(r, i, Fixed::<S, F>::upscale_to_compute(svd.u.get(r, i).raw())); }
} else {
for r in 0..k { a.set(r, i, mul_div_c(c.get(r, i), one, cos_norms[i])?); }
}
}
Ok(GrassmannLog::<Fixed<S, F>> { u, theta, a })
}
fn jacobi_refine<S: Storage, const F: u32>(nm: &ComputeMatrix<Fixed<S, F>>, mut b: ComputeMatrix<Fixed<S, F>>) -> Result<ComputeMatrix<Fixed<S, F>>, OverflowDetected>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let k = b.cols();
let one = Fixed::<S, F>::make_compute_int(1);
for _sweep in 0..6 {
let mut rotated = false;
for i in 0..k {
for j in (i + 1)..k {
let bi = b.col_vec(i);
let bj = b.col_vec(j);
let (ni, nj) = (nm.mul_vector_compute(&bi), nm.mul_vector_compute(&bj));
let gij = dot_c(&bi, &nj)?;
if Fixed::<S, F>::compute_is_zero(&gij) { continue; }
rotated = true;
let d = Fixed::<S, F>::compute_halve(Fixed::<S, F>::compute_subtract(dot_c(&bj, &nj)?, dot_c(&bi, &ni)?));
let root = sqrt_det_c(d, d, Fixed::<S, F>::compute_negate(gij), gij)?;
let den = if Fixed::<S, F>::compute_is_negative(&d) { Fixed::<S, F>::compute_subtract(d, root) } else { c_add(d, root)? };
let t = mul_div_c(gij, one, den)?;
let c = mul_div_c(one, one, sqrt_c(c_add(one, kernels::compute_product::<Fixed<S, F>>(t, t)?)?))?;
let s = kernels::compute_product::<Fixed<S, F>>(t, c)?;
for r in 0..b.rows() {
let (x, y) = (bi[r], bj[r]);
b.set(r, i, Fixed::<S, F>::compute_subtract(kernels::compute_product::<Fixed<S, F>>(c, x)?, kernels::compute_product::<Fixed<S, F>>(s, y)?));
b.set(r, j, c_add(kernels::compute_product::<Fixed<S, F>>(s, x)?, kernels::compute_product::<Fixed<S, F>>(c, y)?)?);
}
}
}
if !rotated { break; }
}
Ok(b)
}
fn diag<S: Storage, const F: u32>(d: &[C<S, F>]) -> ComputeMatrix<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let zero = Fixed::<S, F>::make_compute_int(0);
ComputeMatrix::<Fixed<S, F>>::from_fn(d.len(), d.len(), |r, c| if r == c { d[r] } else { zero })
}
}
impl<S: Storage, const F: u32> Manifold<Fixed<S, F>> for Grassmannian
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
fn dimension(&self) -> usize {
self.k * (self.n - self.k)
}
fn inner_product(
&self,
_base: &FixedVector<Fixed<S, F>>,
u: &FixedVector<Fixed<S, F>>,
v: &FixedVector<Fixed<S, F>>,
) -> Fixed<S, F> {
let u_mat = Self::vec_to_mat(u, self.n, self.k);
let v_mat = Self::vec_to_mat(v, self.n, self.k);
let u_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&u_mat);
let v_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&v_mat);
u_c.transpose().mat_mul(&v_c).trace_compute()
}
fn norm(&self, _base: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
euclidean_norm(v)
}
fn exp_map(
&self,
base: &FixedVector<Fixed<S, F>>,
tangent: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let q = Self::vec_to_mat(base, self.n, self.k);
let delta = Self::vec_to_mat(tangent, self.n, self.k);
let svd = svd_decompose(&delta)?;
let delta_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&delta);
let v = Self::jacobi_refine(&delta_c.transpose().mat_mul(&delta_c),
ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&svd.vt.transpose()))?;
let dv = delta_c.mat_mul(&v); let sigma = Self::column_norms(&dv)?;
let one = Fixed::<S, F>::make_compute_int(1);
let (mut cos_d, mut sinc_d) = (Vec::with_capacity(self.k), Vec::with_capacity(self.k));
for s in &sigma {
let (sin_s, cos_s) = Fixed::<S, F>::sincos_at_compute_tier(*s);
cos_d.push(cos_s);
sinc_d.push(if Fixed::<S, F>::compute_is_zero(s) { one } else { mul_div_c(sin_s, one, *s)? });
}
let vt = v.transpose();
let q_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&q);
let term1 = q_c.mat_mul(&v).mat_mul(&Self::diag(&cos_d)).mat_mul(&vt);
let term2 = dv.mat_mul(&Self::diag(&sinc_d)).mat_mul(&vt);
Ok(Self::mat_to_vec(&down_matrix(&term1.add(&term2))?))
}
fn log_map(
&self,
base: &FixedVector<Fixed<S, F>>,
target: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let q1 = Self::vec_to_mat(base, self.n, self.k);
let q2 = Self::vec_to_mat(target, self.n, self.k);
let log = self.log_parts(&q1, &q2)?;
let result = log.u.mat_mul(&Self::diag(&log.theta)).mat_mul(&log.a.transpose());
Ok(Self::mat_to_vec(&down_matrix(&result)?))
}
fn distance(
&self,
p: &FixedVector<Fixed<S, F>>,
q: &FixedVector<Fixed<S, F>>,
) -> Result<Fixed<S, F>, OverflowDetected> {
let q1 = Self::vec_to_mat(p, self.n, self.k);
let q2 = Self::vec_to_mat(q, self.n, self.k);
let theta = self.log_parts(&q1, &q2)?.theta;
down(sqrt_c(dot_c(&theta, &theta)?))
}
fn parallel_transport(
&self,
base: &FixedVector<Fixed<S, F>>,
target: &FixedVector<Fixed<S, F>>,
tangent: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let q1 = Self::vec_to_mat(base, self.n, self.k);
let q2 = Self::vec_to_mat(target, self.n, self.k);
let log = self.log_parts(&q1, &q2)?;
let one = Fixed::<S, F>::make_compute_int(1);
let (mut sin_d, mut cos_m1) = (Vec::with_capacity(self.k), Vec::with_capacity(self.k));
for t in &log.theta {
let (s, c) = Fixed::<S, F>::sincos_at_compute_tier(*t);
sin_d.push(s);
cos_m1.push(Fixed::<S, F>::compute_subtract(c, one));
}
let q1_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&q1);
let delta_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&Self::vec_to_mat(tangent, self.n, self.k));
let ut_delta = log.u.transpose().mat_mul(&delta_c);
let along = q1_c.mat_mul(&log.a).mat_mul(&Self::diag(&sin_d)).mat_mul(&ut_delta);
let within = log.u.mat_mul(&Self::diag(&cos_m1)).mat_mul(&ut_delta);
let result = delta_c.sub(&along).add(&within);
Ok(Self::mat_to_vec(&down_matrix(&result)?))
}
}
pub struct StiefelManifold {
pub k: usize, pub n: usize, }
fn stiefel_mat_to_vec<S: Storage, const F: u32>(m: &FixedMatrix<Fixed<S, F>>) -> FixedVector<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let len = m.rows() * m.cols();
let mut v = FixedVector::<Fixed<S, F>>::new(len);
let mut idx = 0;
for c in 0..m.cols() {
for r in 0..m.rows() {
v[idx] = m.get(r, c);
idx += 1;
}
}
v
}
fn stiefel_vec_to_mat<S: Storage, const F: u32>(v: &FixedVector<Fixed<S, F>>, n: usize, k: usize) -> FixedMatrix<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let mut m = FixedMatrix::<Fixed<S, F>>::new(n, k);
let mut idx = 0;
for c in 0..k {
for r in 0..n {
m.set(r, c, v[idx]);
idx += 1;
}
}
m
}
impl StiefelManifold {
fn project_tangent<S: Storage, const F: u32>(q: &FixedMatrix<Fixed<S, F>>, delta: &FixedMatrix<Fixed<S, F>>) -> FixedMatrix<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
Self::project_tangent_compute(&ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(q), &ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(delta))
.to_fixed_matrix()
}
fn project_tangent_compute<S: Storage, const F: u32>(q_c: &ComputeMatrix<Fixed<S, F>>, delta_c: &ComputeMatrix<Fixed<S, F>>) -> ComputeMatrix<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let qt_delta_c = q_c.transpose().mat_mul(delta_c); let sym_c = qt_delta_c.add(&qt_delta_c.transpose()).halve();
delta_c.sub(&q_c.mat_mul(&sym_c))
}
fn log_compute<S: Storage, const F: u32>(&self, base: &FixedVector<Fixed<S, F>>, target: &FixedVector<Fixed<S, F>>) -> ComputeMatrix<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
let q_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&stiefel_vec_to_mat(base, self.n, self.k));
let target_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&stiefel_vec_to_mat(target, self.n, self.k));
Self::project_tangent_compute(&q_c, &target_c.sub(&q_c))
}
}
impl<S: Storage, const F: u32> Manifold<Fixed<S, F>> for StiefelManifold
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
fn dimension(&self) -> usize {
self.n * self.k - self.k * (self.k + 1) / 2
}
fn inner_product(
&self,
_base: &FixedVector<Fixed<S, F>>,
u: &FixedVector<Fixed<S, F>>,
v: &FixedVector<Fixed<S, F>>,
) -> Fixed<S, F> {
let u_mat = stiefel_vec_to_mat(u, self.n, self.k);
let v_mat = stiefel_vec_to_mat(v, self.n, self.k);
let u_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&u_mat);
let v_c = ComputeMatrix::<Fixed<S, F>>::from_fixed_matrix(&v_mat);
u_c.transpose().mat_mul(&v_c).trace_compute()
}
fn norm(&self, _base: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
euclidean_norm(v)
}
fn exp_map(
&self,
base: &FixedVector<Fixed<S, F>>,
tangent: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let q = stiefel_vec_to_mat(base, self.n, self.k);
let delta = stiefel_vec_to_mat(tangent, self.n, self.k);
let q_plus_delta = &q + δ
let qr = qr_decompose(&q_plus_delta)?;
let q_new = FixedMatrix::<Fixed<S, F>>::from_fn(self.n, self.k, |r, c| {
let sign = if qr.r.get(c, c).is_negative() {
Fixed::from_int(-1)
} else {
Fixed::one()
};
qr.q.get(r, c) * sign
});
Ok(stiefel_mat_to_vec(&q_new))
}
fn log_map(
&self,
base: &FixedVector<Fixed<S, F>>,
target: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
Ok(stiefel_mat_to_vec(&down_matrix(&self.log_compute(base, target))?))
}
fn distance(
&self,
p: &FixedVector<Fixed<S, F>>,
q: &FixedVector<Fixed<S, F>>,
) -> Result<Fixed<S, F>, OverflowDetected> {
down(sqrt_c(frobenius_sq_c(&self.log_compute(p, q))?))
}
fn parallel_transport(
&self,
_base: &FixedVector<Fixed<S, F>>,
target: &FixedVector<Fixed<S, F>>,
tangent: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let q_target = stiefel_vec_to_mat(target, self.n, self.k);
let delta = stiefel_vec_to_mat(tangent, self.n, self.k);
let transported = Self::project_tangent(&q_target, &delta);
Ok(stiefel_mat_to_vec(&transported))
}
}
pub struct ProductManifold<T> {
m1: Box<dyn Manifold<T>>,
m2: Box<dyn Manifold<T>>,
dim1_embed: usize,
dim2_embed: usize,
}
impl<S: Storage, const F: u32> ProductManifold<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
pub fn new(
m1: Box<dyn Manifold<Fixed<S, F>>>,
dim1_embed: usize,
m2: Box<dyn Manifold<Fixed<S, F>>>,
dim2_embed: usize,
) -> Self {
Self { m1, m2, dim1_embed, dim2_embed }
}
fn split(&self, v: &FixedVector<Fixed<S, F>>) -> (FixedVector<Fixed<S, F>>, FixedVector<Fixed<S, F>>) {
let mut v1 = FixedVector::<Fixed<S, F>>::new(self.dim1_embed);
let mut v2 = FixedVector::<Fixed<S, F>>::new(self.dim2_embed);
for i in 0..self.dim1_embed { v1[i] = v[i]; }
for i in 0..self.dim2_embed { v2[i] = v[self.dim1_embed + i]; }
(v1, v2)
}
fn join(v1: &FixedVector<Fixed<S, F>>, v2: &FixedVector<Fixed<S, F>>) -> FixedVector<Fixed<S, F>> {
let mut v = FixedVector::<Fixed<S, F>>::new(v1.len() + v2.len());
for i in 0..v1.len() { v[i] = v1[i]; }
for i in 0..v2.len() { v[v1.len() + i] = v2[i]; }
v
}
}
impl<S: Storage, const F: u32> Manifold<Fixed<S, F>> for ProductManifold<Fixed<S, F>>
where
Fixed<S, F>: FixedNum<Storage = S> + MatrixFunctions,
{
fn dimension(&self) -> usize {
self.m1.dimension() + self.m2.dimension()
}
fn inner_product(
&self,
base: &FixedVector<Fixed<S, F>>,
u: &FixedVector<Fixed<S, F>>,
v: &FixedVector<Fixed<S, F>>,
) -> Fixed<S, F> {
let (b1, b2) = self.split(base);
let (u1, u2) = self.split(u);
let (v1, v2) = self.split(v);
let first = Fixed::<S, F>::upscale_to_compute(self.m1.inner_product(&b1, &u1, &v1).raw());
let second = Fixed::<S, F>::upscale_to_compute(self.m2.inner_product(&b2, &u2, &v2).raw());
down(c_add(first, second).expect("ProductManifold::inner_product exceeds the compute tier"))
.expect("ProductManifold::inner_product exceeds the storage range")
}
fn norm(&self, base: &FixedVector<Fixed<S, F>>, v: &FixedVector<Fixed<S, F>>) -> Fixed<S, F> {
let (b1, b2) = self.split(base);
let (v1, v2) = self.split(v);
let parts = [self.m1.norm(&b1, &v1).raw(), self.m2.norm(&b2, &v2).raw()];
Fixed::from_raw(kernels::sqrt_dot::<Fixed<S, F>>(&parts, &parts).expect("ProductManifold::norm exceeds the storage range"))
}
fn exp_map(
&self,
base: &FixedVector<Fixed<S, F>>,
tangent: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let (b1, b2) = self.split(base);
let (t1, t2) = self.split(tangent);
let r1 = self.m1.exp_map(&b1, &t1)?;
let r2 = self.m2.exp_map(&b2, &t2)?;
Ok(Self::join(&r1, &r2))
}
fn log_map(
&self,
base: &FixedVector<Fixed<S, F>>,
target: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let (b1, b2) = self.split(base);
let (t1, t2) = self.split(target);
let l1 = self.m1.log_map(&b1, &t1)?;
let l2 = self.m2.log_map(&b2, &t2)?;
Ok(Self::join(&l1, &l2))
}
fn distance(
&self,
p: &FixedVector<Fixed<S, F>>,
q: &FixedVector<Fixed<S, F>>,
) -> Result<Fixed<S, F>, OverflowDetected> {
let (p1, p2) = self.split(p);
let (q1, q2) = self.split(q);
let d1 = self.m1.distance(&p1, &q1)?;
let d2 = self.m2.distance(&p2, &q2)?;
let parts = [d1.raw(), d2.raw()];
Ok(Fixed::from_raw(kernels::sqrt_dot::<Fixed<S, F>>(&parts, &parts)?))
}
fn parallel_transport(
&self,
base: &FixedVector<Fixed<S, F>>,
target: &FixedVector<Fixed<S, F>>,
tangent: &FixedVector<Fixed<S, F>>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected> {
let (b1, b2) = self.split(base);
let (t1, t2) = self.split(target);
let (v1, v2) = self.split(tangent);
let pt1 = self.m1.parallel_transport(&b1, &t1, &v1)?;
let pt2 = self.m2.parallel_transport(&b2, &t2, &v2)?;
Ok(Self::join(&pt1, &pt2))
}
}
mod exact_forms {
use super::*;
use crate::fixed_point::domains::binary_fixed::transcendental::mpf::MpfErr;
use crate::fixed_point::exact::{settle, Formula, Num};
type R<X> = Result<X, MpfErr>;
pub(super) fn words<S: Storage, const F: u32, B: Num<Fixed<S, F>>>(r: &[S]) -> R<Vec<B>>
where Fixed<S, F>: FixedNum<Storage = S> {
r.iter().map(|&x| B::from_word(x, F)).collect()
}
pub(super) fn dot<S: Storage, const F: u32, B: Num<Fixed<S, F>>>(a: &[B], b: &[B]) -> R<B>
where Fixed<S, F>: FixedNum<Storage = S> {
let mut acc = B::int(0)?;
for (x, y) in a.iter().zip(b) { acc = acc.add(x.mul(*y)?)?; }
Ok(acc)
}
pub(super) fn mdot<S: Storage, const F: u32, B: Num<Fixed<S, F>>>(a: &[B], b: &[B]) -> R<B>
where Fixed<S, F>: FixedNum<Storage = S> {
let rest = dot::<S, F, B>(&a[1..], &b[1..])?;
rest.sub(a[0].mul(b[0])?)
}
pub(super) fn settled<S: Storage, const F: u32, G: Formula<Fixed<S, F>>>(
g: &G,
fallback: impl FnOnce() -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>,
) -> Result<FixedVector<Fixed<S, F>>, OverflowDetected>
where Fixed<S, F>: FixedNum<Storage = S> {
match settle::<Fixed<S, F>, G>(g) {
Ok(v) => Ok(FixedVector::from_slice(&v.into_iter().map(Fixed::<S, F>::from_raw).collect::<Vec<_>>())),
Err(MpfErr::Domain) => Err(OverflowDetected::DomainError),
Err(MpfErr::Overflow) => Err(OverflowDetected::TierOverflow),
Err(_) => fallback(),
}
}
fn sphere_angle<S: Storage, const F: u32, B: Num<Fixed<S, F>>>(p: &[B], q: &[B]) -> R<(B, B, B)>
where Fixed<S, F>: FixedNum<Storage = S> {
let pp = dot::<S, F, B>(p, p)?;
let qq = dot::<S, F, B>(q, q)?;
let c = dot::<S, F, B>(p, q)?;
let cross = pp.mul(qq)?.sub(c.mul(c)?)?.sqrt()?;
Ok((cross.atan2(c)?, c, pp))
}
pub(super) struct SphereDistance<'a, S> { pub p: &'a [S], pub q: &'a [S] }
impl<'a, S: Storage, const F: u32> Formula<Fixed<S, F>> for SphereDistance<'a, S> where Fixed<S, F>: FixedNum<Storage = S> {
fn eval<B: Num<Fixed<S, F>>>(&self) -> R<Vec<B>> {
let (p, q) = (words::<S, F, B>(self.p)?, words::<S, F, B>(self.q)?);
Ok(vec![sphere_angle::<S, F, B>(&p, &q)?.0])
}
}
pub(super) struct SphereLog<'a, S> { pub p: &'a [S], pub q: &'a [S] }
impl<'a, S: Storage, const F: u32> Formula<Fixed<S, F>> for SphereLog<'a, S> where Fixed<S, F>: FixedNum<Storage = S> {
fn eval<B: Num<Fixed<S, F>>>(&self) -> R<Vec<B>> {
let (p, q) = (words::<S, F, B>(self.p)?, words::<S, F, B>(self.q)?);
let (theta, c, pp) = sphere_angle::<S, F, B>(&p, &q)?;
let k = c.div(pp)?;
let w: Vec<B> = p.iter().zip(&q).map(|(pi, qi)| qi.sub(k.mul(*pi)?)).collect::<R<_>>()?;
let wl = dot::<S, F, B>(&w, &w)?.sqrt()?;
let s = theta.div(wl)?;
w.iter().map(|wi| wi.mul(s)).collect()
}
}
pub(super) struct SphereExp<'a, S> { pub p: &'a [S], pub v: &'a [S] }
impl<'a, S: Storage, const F: u32> Formula<Fixed<S, F>> for SphereExp<'a, S> where Fixed<S, F>: FixedNum<Storage = S> {
fn eval<B: Num<Fixed<S, F>>>(&self) -> R<Vec<B>> {
let (p, v) = (words::<S, F, B>(self.p)?, words::<S, F, B>(self.v)?);
let theta = dot::<S, F, B>(&v, &v)?.sqrt()?;
let (s, c) = (theta.sin()?, theta.cos()?);
let k = s.div(theta)?;
p.iter().zip(&v).map(|(pi, vi)| c.mul(*pi)?.add(k.mul(*vi)?)).collect()
}
}
pub(super) struct SphereTransport<'a, S> { pub p: &'a [S], pub q: &'a [S], pub v: &'a [S] }
impl<'a, S: Storage, const F: u32> Formula<Fixed<S, F>> for SphereTransport<'a, S> where Fixed<S, F>: FixedNum<Storage = S> {
fn eval<B: Num<Fixed<S, F>>>(&self) -> R<Vec<B>> {
let (p, q, v) = (words::<S, F, B>(self.p)?, words::<S, F, B>(self.q)?, words::<S, F, B>(self.v)?);
let den = B::int(1)?.add(dot::<S, F, B>(&p, &q)?)?;
let k = dot::<S, F, B>(&v, &p)?.add(dot::<S, F, B>(&v, &q)?)?.div(den)?;
(0..v.len()).map(|i| v[i].sub(k.mul(p[i].add(q[i])?)?)).collect()
}
}
fn hyp_distance<S: Storage, const F: u32, B: Num<Fixed<S, F>>>(p: &[B], q: &[B]) -> R<(B, B, B)>
where Fixed<S, F>: FixedNum<Storage = S> {
let a = mdot::<S, F, B>(p, q)?.neg();
let pp = mdot::<S, F, B>(p, p)?.neg();
let qq = mdot::<S, F, B>(q, q)?.neg();
let ppqq = pp.mul(qq)?;
let sh = a.mul(a)?.sub(ppqq)?.sqrt()?;
let d = a.add(sh)?.div(ppqq.sqrt()?)?.ln()?;
Ok((d, a, pp))
}
pub(super) struct HypDistance<'a, S> { pub p: &'a [S], pub q: &'a [S] }
impl<'a, S: Storage, const F: u32> Formula<Fixed<S, F>> for HypDistance<'a, S> where Fixed<S, F>: FixedNum<Storage = S> {
fn eval<B: Num<Fixed<S, F>>>(&self) -> R<Vec<B>> {
let (p, q) = (words::<S, F, B>(self.p)?, words::<S, F, B>(self.q)?);
Ok(vec![hyp_distance::<S, F, B>(&p, &q)?.0])
}
}
fn hyp_direction<S: Storage, const F: u32, B: Num<Fixed<S, F>>>(p: &[B], q: &[B], a: B, pp: B) -> R<(Vec<B>, B)>
where Fixed<S, F>: FixedNum<Storage = S> {
let k = a.div(pp)?;
let w: Vec<B> = p.iter().zip(q).map(|(pi, qi)| qi.sub(k.mul(*pi)?)).collect::<R<_>>()?;
let wl = mdot::<S, F, B>(&w, &w)?.sqrt()?;
Ok((w, wl))
}
pub(super) struct HypLog<'a, S> { pub p: &'a [S], pub q: &'a [S] }
impl<'a, S: Storage, const F: u32> Formula<Fixed<S, F>> for HypLog<'a, S> where Fixed<S, F>: FixedNum<Storage = S> {
fn eval<B: Num<Fixed<S, F>>>(&self) -> R<Vec<B>> {
let (p, q) = (words::<S, F, B>(self.p)?, words::<S, F, B>(self.q)?);
let (d, a, pp) = hyp_distance::<S, F, B>(&p, &q)?;
let (w, wl) = hyp_direction::<S, F, B>(&p, &q, a, pp)?;
let s = d.div(wl)?;
w.iter().map(|wi| wi.mul(s)).collect()
}
}
pub(super) struct HypExp<'a, S> { pub p: &'a [S], pub v: &'a [S], pub timelike: bool }
impl<'a, S: Storage, const F: u32> Formula<Fixed<S, F>> for HypExp<'a, S> where Fixed<S, F>: FixedNum<Storage = S> {
fn eval<B: Num<Fixed<S, F>>>(&self) -> R<Vec<B>> {
let (p, v) = (words::<S, F, B>(self.p)?, words::<S, F, B>(self.v)?);
let t2 = mdot::<S, F, B>(&v, &v)?;
let theta = (if self.timelike { t2.neg() } else { t2 }).sqrt()?;
let (sh, ch) = (theta.sinh()?, theta.cosh()?);
let k = sh.div(theta)?;
p.iter().zip(&v).map(|(pi, vi)| ch.mul(*pi)?.add(k.mul(*vi)?)).collect()
}
}
pub(super) struct HypTransport<'a, S> { pub p: &'a [S], pub q: &'a [S], pub v: &'a [S] }
impl<'a, S: Storage, const F: u32> Formula<Fixed<S, F>> for HypTransport<'a, S> where Fixed<S, F>: FixedNum<Storage = S> {
fn eval<B: Num<Fixed<S, F>>>(&self) -> R<Vec<B>> {
let (p, q, v) = (words::<S, F, B>(self.p)?, words::<S, F, B>(self.q)?, words::<S, F, B>(self.v)?);
let (d, a, pp) = hyp_distance::<S, F, B>(&p, &q)?;
let (w, wl) = hyp_direction::<S, F, B>(&p, &q, a, pp)?;
let u: Vec<B> = w.iter().map(|wi| wi.div(wl)).collect::<R<_>>()?;
let coeff = mdot::<S, F, B>(&v, &u)?;
let (sh, chm1) = (d.sinh()?, d.cosh()?.sub(B::int(1)?)?);
(0..v.len()).map(|i| v[i].add(coeff.mul(sh.mul(p[i])?.add(chm1.mul(u[i])?)?)?)).collect()
}
}
}