use super::FixedPoint;
use super::FixedVector;
use super::FixedMatrix;
use super::linalg::{exact_dot, round_to_storage};
use super::wide_acc::{acc, divide_to_storage_nearest, narrow_triple_nearest, widen_product, widen_storage, Wide};
use crate::fixed_point::universal::fasc::stack_evaluator::{BinaryStorage, ComputeStorage};
use crate::fixed_point::universal::fasc::stack_evaluator::compute::{
compute_add, compute_checked_add, compute_is_zero, compute_negate, compute_subtract, make_compute_int,
};
use crate::fixed_point::core_types::errors::OverflowDetected;
use crate::fixed_point::frac_config;
#[cfg(table_format = "q32_32")]
use crate::fixed_point::I256;
#[cfg(table_format = "q64_64")]
use crate::fixed_point::I512;
#[cfg(table_format = "q128_128")]
use crate::fixed_point::I1024;
#[cfg(table_format = "q256_256")]
use crate::fixed_point::I2048;
fn exact_signed_sum(
plus: &[(BinaryStorage, BinaryStorage)],
minus: &[(BinaryStorage, BinaryStorage)],
) -> Result<ComputeStorage, OverflowDetected> {
let dot = |t: &[(BinaryStorage, BinaryStorage)]| {
let (a, b): (Vec<BinaryStorage>, Vec<BinaryStorage>) = t.iter().copied().unzip();
exact_dot(&a, &b)
};
compute_checked_add(dot(plus)?, compute_negate(dot(minus)?))
}
#[inline]
fn lift_to_triple(c: ComputeStorage) -> acc::Orient {
widen_product(c, widen_storage(FixedPoint::one().raw()))
}
fn quotient(num: ComputeStorage, den: ComputeStorage) -> Result<FixedPoint, OverflowDetected> {
if compute_is_zero(&den) {
return Err(OverflowDetected::DomainError);
}
Ok(FixedPoint::from_raw(divide_to_storage_nearest(lift_to_triple(num), den)?))
}
#[inline]
fn orient_unit() -> acc::Orient {
#[cfg(table_format = "q16_16")]
{ 1i128 }
#[cfg(table_format = "q32_32")]
{ I256::from_i128(1) }
#[cfg(table_format = "q64_64")]
{ I512::from_i128(1) }
#[cfg(table_format = "q128_128")]
{ I1024::from_i128(1) }
#[cfg(table_format = "q256_256")]
{ I2048::from_i128(1) }
}
#[inline]
fn orient_divmod(n: acc::Orient, d: acc::Orient) -> (acc::Orient, acc::Orient) {
#[cfg(not(table_format = "q256_256"))]
{ (n / d, n % d) }
#[cfg(table_format = "q256_256")]
{ crate::fixed_point::domains::binary_fixed::i2048::i2048_divmod(n, d) }
}
fn orient_mul(x: acc::Orient, y: acc::Orient) -> Result<acc::Orient, OverflowDetected> {
let magnitude = |v: acc::Orient| if Wide::is_negative(v) { -v } else { v };
if Wide::bit_length(magnitude(x)) + Wide::bit_length(magnitude(y)) > <acc::Orient as Wide>::BITS - 1 {
return Err(OverflowDetected::TierOverflow);
}
Ok(x.mul_exact(y))
}
fn ratio_to_storage(n: acc::Orient, d: acc::Orient) -> Result<BinaryStorage, OverflowDetected> {
let zero = <acc::Orient as Wide>::zero();
if d == zero {
return Err(OverflowDetected::DivisionByZero);
}
let negative = Wide::is_negative(n) != Wide::is_negative(d) && n != zero;
let na = if Wide::is_negative(n) { -n } else { n };
let da = if Wide::is_negative(d) { -d } else { d };
let (mut q, mut r) = orient_divmod(na, da);
if Wide::bit_length(q) > <acc::Orient as Wide>::BITS / 4 {
return Err(OverflowDetected::TierOverflow);
}
let unit = orient_unit();
for _ in 0..3 * frac_config::FRAC_BITS {
let rest = da - r;
if r >= rest {
q = q + q + unit;
r = r - rest;
} else {
q = q + q;
r = r + r;
}
}
let floor = if negative { -(if r != zero { q + unit } else { q }) } else { q };
narrow_triple_nearest(floor)
}
pub fn to_homogeneous(v: &FixedVector) -> FixedVector {
let n = v.len();
let mut h = FixedVector::new(n + 1);
for i in 0..n {
h[i] = v[i];
}
h[n] = FixedPoint::one();
h
}
pub fn from_homogeneous(h: &FixedVector) -> Result<FixedVector, OverflowDetected> {
let n = h.len();
if n == 0 { return Err(OverflowDetected::DomainError); }
let w = h[n - 1];
if w.is_zero() {
return Err(OverflowDetected::DomainError);
}
let mut v = FixedVector::new(n - 1);
for i in 0..n - 1 {
v[i] = h[i] / w;
}
Ok(v)
}
pub fn is_at_infinity(h: &FixedVector, tol: FixedPoint) -> bool {
let n = h.len();
if n == 0 { return true; }
h[n - 1].abs() < tol
}
pub fn projective_transform(
h_matrix: &FixedMatrix,
point: &FixedVector,
) -> Result<FixedVector, OverflowDetected> {
let n = point.len();
assert!(h_matrix.rows() == n + 1 && h_matrix.cols() == n + 1, "projective_transform: H must be (n+1)x(n+1)");
let hp: Vec<BinaryStorage> = to_homogeneous(point).iter().map(|x| x.raw()).collect();
let row = |i: usize| {
let h: Vec<BinaryStorage> = (0..=n).map(|j| h_matrix.get(i, j).raw()).collect();
exact_dot(&h, &hp)
};
let w = row(n)?;
if compute_is_zero(&w) {
return Err(OverflowDetected::DomainError);
}
let mut v = FixedVector::new(n);
for i in 0..n {
v[i] = quotient(row(i)?, w)?;
}
Ok(v)
}
pub fn projective_transform_homogeneous(
h_matrix: &FixedMatrix,
point: &FixedVector,
) -> FixedVector {
h_matrix.mul_vector(point)
}
pub fn compose_projective(
h1: &FixedMatrix,
h2: &FixedMatrix,
) -> FixedMatrix {
h1 * h2
}
pub fn cross_ratio_1d(
a: FixedPoint,
b: FixedPoint,
c: FixedPoint,
d: FixedPoint,
) -> Result<FixedPoint, OverflowDetected> {
let (a, b, c, d) = (widen_storage(a.raw()), widen_storage(b.raw()), widen_storage(c.raw()), widen_storage(d.raw()));
let numer = widen_product(compute_subtract(a, c), compute_subtract(b, d));
let denom = widen_product(compute_subtract(a, d), compute_subtract(b, c));
if denom == <acc::Orient as Wide>::zero() {
return Err(OverflowDetected::DomainError);
}
Ok(FixedPoint::from_raw(ratio_to_storage(numer, denom)?))
}
pub fn cross_ratio(
a: &FixedVector,
b: &FixedVector,
c: &FixedVector,
d: &FixedVector,
) -> Result<FixedPoint, OverflowDetected> {
let n = a.len();
assert!(b.len() == n && c.len() == n && d.len() == n, "cross_ratio: dimension mismatch");
let diff = |x: &FixedVector| -> Vec<ComputeStorage> {
(0..n).map(|i| compute_subtract(widen_storage(x[i].raw()), widen_storage(a[i].raw()))).collect()
};
let dir = diff(b);
let project = |x: &FixedVector| super::wide_acc::exact_dot_compute(&diff(x), &dir);
let tb = project(b)?;
if tb == <acc::Orient as Wide>::zero() {
return Err(OverflowDetected::DomainError);
}
let (tc, td) = (project(c)?, project(d)?);
let numer = orient_mul(-tc, tb.add_exact(-td)?)?;
let denom = orient_mul(-td, tb.add_exact(-tc)?)?;
if denom == <acc::Orient as Wide>::zero() {
return Err(OverflowDetected::DomainError);
}
Ok(FixedPoint::from_raw(ratio_to_storage(numer, denom)?))
}
pub fn stereo_project(p: &FixedVector) -> Result<FixedVector, OverflowDetected> {
let n = p.len();
if n < 2 { return Err(OverflowDetected::DomainError); }
let pn = p[n - 1];
let denom = FixedPoint::one() - pn;
if denom.is_zero() {
return Err(OverflowDetected::DomainError); }
let mut x = FixedVector::new(n - 1);
for i in 0..n - 1 {
x[i] = p[i] / denom;
}
Ok(x)
}
pub fn stereo_unproject(x: &FixedVector) -> FixedVector {
const OVERFLOW: &str = "stereo_unproject: |x|^2 exceeds the compute tier";
let n = x.len();
let one = make_compute_int(1);
let x_raw: Vec<BinaryStorage> = (0..n).map(|i| x[i].raw()).collect();
let x_sq = exact_dot(&x_raw, &x_raw).expect(OVERFLOW);
let denom = compute_add(one, x_sq);
let mut p = FixedVector::new(n + 1);
for i in 0..n {
let numer = widen_product(make_compute_int(2), widen_storage(x[i].raw()));
p[i] = FixedPoint::from_raw(divide_to_storage_nearest(numer, denom).expect(OVERFLOW));
}
p[n] = quotient(compute_subtract(x_sq, one), denom).expect(OVERFLOW);
p
}
#[derive(Clone, Copy, Debug)]
pub struct Moebius {
pub a: FixedPoint,
pub b: FixedPoint,
pub c: FixedPoint,
pub d: FixedPoint,
}
impl Moebius {
pub fn new(a: FixedPoint, b: FixedPoint, c: FixedPoint, d: FixedPoint) -> Self {
Self { a, b, c, d }
}
pub fn identity() -> Self {
Self {
a: FixedPoint::one(),
b: FixedPoint::ZERO,
c: FixedPoint::ZERO,
d: FixedPoint::one(),
}
}
pub fn apply(&self, x: FixedPoint) -> Result<FixedPoint, OverflowDetected> {
let one = FixedPoint::one().raw();
let x = x.raw();
let numer = exact_signed_sum(&[(self.a.raw(), x), (self.b.raw(), one)], &[])?;
let denom = exact_signed_sum(&[(self.c.raw(), x), (self.d.raw(), one)], &[])?;
quotient(numer, denom)
}
pub fn compose(&self, other: &Moebius) -> Moebius {
let pair = |p: FixedPoint, q: FixedPoint, r: FixedPoint, s: FixedPoint| {
let sum = exact_signed_sum(&[(p.raw(), q.raw()), (r.raw(), s.raw())], &[])
.expect("Moebius::compose: coefficient exceeds the compute tier");
FixedPoint::from_raw(round_to_storage(sum))
};
Moebius {
a: pair(self.a, other.a, self.b, other.c),
b: pair(self.a, other.b, self.b, other.d),
c: pair(self.c, other.a, self.d, other.c),
d: pair(self.c, other.b, self.d, other.d),
}
}
pub fn inverse(&self) -> Moebius {
Moebius {
a: self.d,
b: -self.b,
c: -self.c,
d: self.a,
}
}
pub fn determinant(&self) -> FixedPoint {
let det = exact_signed_sum(&[(self.a.raw(), self.d.raw())], &[(self.b.raw(), self.c.raw())])
.expect("Moebius::determinant exceeds the compute tier");
FixedPoint::from_raw(round_to_storage(det))
}
pub fn to_matrix(&self) -> FixedMatrix {
FixedMatrix::from_slice(2, 2, &[self.a, self.b, self.c, self.d])
}
}
#[derive(Clone, Copy, Debug)]
pub struct MoebiusComplex {
pub a: (FixedPoint, FixedPoint), pub b: (FixedPoint, FixedPoint),
pub c: (FixedPoint, FixedPoint),
pub d: (FixedPoint, FixedPoint),
}
type Complex = (FixedPoint, FixedPoint);
fn complex_mul_add_exact(p: Complex, q: Complex, r: Complex, s: Complex) -> Result<(ComputeStorage, ComputeStorage), OverflowDetected> {
let re = exact_signed_sum(
&[(p.0.raw(), q.0.raw()), (r.0.raw(), s.0.raw())],
&[(p.1.raw(), q.1.raw()), (r.1.raw(), s.1.raw())],
)?;
let im = exact_signed_sum(
&[(p.0.raw(), q.1.raw()), (p.1.raw(), q.0.raw()), (r.0.raw(), s.1.raw()), (r.1.raw(), s.0.raw())],
&[],
)?;
Ok((re, im))
}
fn complex_div(
a: (ComputeStorage, ComputeStorage),
b: (ComputeStorage, ComputeStorage),
) -> Result<(FixedPoint, FixedPoint), OverflowDetected> {
let denom = widen_product(b.0, b.0).add_exact(widen_product(b.1, b.1))?;
if denom == <acc::Orient as Wide>::zero() {
return Err(OverflowDetected::DomainError);
}
let re = widen_product(a.0, b.0).add_exact(widen_product(a.1, b.1))?;
let im = widen_product(a.1, b.0).add_exact(-widen_product(a.0, b.1))?;
Ok((
FixedPoint::from_raw(ratio_to_storage(re, denom)?),
FixedPoint::from_raw(ratio_to_storage(im, denom)?),
))
}
impl MoebiusComplex {
pub fn new(
a: (FixedPoint, FixedPoint),
b: (FixedPoint, FixedPoint),
c: (FixedPoint, FixedPoint),
d: (FixedPoint, FixedPoint),
) -> Self {
Self { a, b, c, d }
}
pub fn apply(
&self,
z: (FixedPoint, FixedPoint),
) -> Result<(FixedPoint, FixedPoint), OverflowDetected> {
let one = (FixedPoint::one(), FixedPoint::ZERO);
let numer = complex_mul_add_exact(self.a, z, self.b, one)?;
let denom = complex_mul_add_exact(self.c, z, self.d, one)?;
complex_div(numer, denom)
}
pub fn compose(&self, other: &MoebiusComplex) -> MoebiusComplex {
let term = |p: Complex, q: Complex, r: Complex, s: Complex| {
let (re, im) = complex_mul_add_exact(p, q, r, s)
.expect("MoebiusComplex::compose: coefficient exceeds the compute tier");
(FixedPoint::from_raw(round_to_storage(re)), FixedPoint::from_raw(round_to_storage(im)))
};
MoebiusComplex {
a: term(self.a, other.a, self.b, other.c),
b: term(self.a, other.b, self.b, other.d),
c: term(self.c, other.a, self.d, other.c),
d: term(self.c, other.b, self.d, other.d),
}
}
pub fn inverse(&self) -> MoebiusComplex {
MoebiusComplex {
a: self.d,
b: (-self.b.0, -self.b.1),
c: (-self.c.0, -self.c.1),
d: self.a,
}
}
}