use super::{FixedMatrix, FixedPoint, Interval};
use crate::fixed_point::core_types::errors::OverflowDetected;
use crate::fixed_point::universal::fasc::stack_evaluator::BinaryStorage;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Sign {
Negative,
Zero,
Positive,
}
impl Sign {
#[inline]
pub fn flip(self) -> Sign {
match self {
Sign::Negative => Sign::Positive,
Sign::Zero => Sign::Zero,
Sign::Positive => Sign::Negative,
}
}
}
use super::wide_acc::{acc, Wide};
trait WideSign: Wide {
#[inline]
fn sign(self) -> Sign {
if self.is_negative() {
Sign::Negative
} else if self == Self::zero() {
Sign::Zero
} else {
Sign::Positive
}
}
}
impl<W: Wide> WideSign for W {}
#[inline]
fn p2<W: Wide>(p: [FixedPoint; 2], widen: fn(BinaryStorage) -> W) -> [W; 2] {
[widen(p[0].raw()), widen(p[1].raw())]
}
#[inline]
fn p3<W: Wide>(p: [FixedPoint; 3], widen: fn(BinaryStorage) -> W) -> [W; 3] {
[widen(p[0].raw()), widen(p[1].raw()), widen(p[2].raw())]
}
#[inline]
fn sub2<W: Wide>(a: [W; 2], b: [W; 2]) -> [W; 2] {
[a[0] - b[0], a[1] - b[1]]
}
#[inline]
fn sub3<W: Wide>(a: [W; 3], b: [W; 3]) -> [W; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
#[inline]
fn det3<W: Wide>(r0: [W; 3], r1: [W; 3], r2: [W; 3]) -> W {
r0[0].mul_exact(r1[1].mul_exact(r2[2]) - r1[2].mul_exact(r2[1]))
- r0[1].mul_exact(r1[0].mul_exact(r2[2]) - r1[2].mul_exact(r2[0]))
+ r0[2].mul_exact(r1[0].mul_exact(r2[1]) - r1[1].mul_exact(r2[0]))
}
#[inline]
fn det2<W: Wide>(r0: [W; 2], r1: [W; 2]) -> W {
r0[0].mul_exact(r1[1]) - r0[1].mul_exact(r1[0])
}
pub fn orient2d(a: [FixedPoint; 2], b: [FixedPoint; 2], c: [FixedPoint; 2]) -> Sign {
let (a, b, c) = (p2(a, acc::orient), p2(b, acc::orient), p2(c, acc::orient));
det2(sub2(a, c), sub2(b, c)).sign()
}
pub fn orient3d(a: [FixedPoint; 3], b: [FixedPoint; 3], c: [FixedPoint; 3], d: [FixedPoint; 3]) -> Sign {
let (a, b, c, d) = (p3(a, acc::orient), p3(b, acc::orient), p3(c, acc::orient), p3(d, acc::orient));
det3(sub3(a, d), sub3(b, d), sub3(c, d)).sign()
}
#[cfg(not(table_format = "q256_256"))]
pub fn incircle(a: [FixedPoint; 2], b: [FixedPoint; 2], c: [FixedPoint; 2], d: [FixedPoint; 2]) -> Sign {
let (a, b, c, d) = (p2(a, acc::circle), p2(b, acc::circle), p2(c, acc::circle), p2(d, acc::circle));
let (ad, bd, cd) = (sub2(a, d), sub2(b, d), sub2(c, d));
let lift = |v: [acc::Circle; 2]| v[0].mul_exact(v[0]) + v[1].mul_exact(v[1]);
(lift(ad).mul_exact(det2(bd, cd)) - lift(bd).mul_exact(det2(ad, cd)) + lift(cd).mul_exact(det2(ad, bd))).sign()
}
#[cfg(not(table_format = "q256_256"))]
pub fn insphere(
a: [FixedPoint; 3],
b: [FixedPoint; 3],
c: [FixedPoint; 3],
d: [FixedPoint; 3],
e: [FixedPoint; 3],
) -> Sign {
let (a, b, c, d, e) = (
p3(a, acc::circle),
p3(b, acc::circle),
p3(c, acc::circle),
p3(d, acc::circle),
p3(e, acc::circle),
);
let (ae, be, ce, de) = (sub3(a, e), sub3(b, e), sub3(c, e), sub3(d, e));
let lift = |v: [acc::Circle; 3]| v[0].mul_exact(v[0]) + v[1].mul_exact(v[1]) + v[2].mul_exact(v[2]);
(det3(be, ce, de).mul_exact(-lift(ae)) + det3(ae, ce, de).mul_exact(lift(be))
- det3(ae, be, de).mul_exact(lift(ce))
+ det3(ae, be, ce).mul_exact(lift(de)))
.sign()
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PdVerdict {
PositiveDefinite,
NotPositiveDefinite { pivot: usize },
Inconclusive { pivot: usize, straddle: Interval },
}
impl PdVerdict {
#[inline]
pub fn is_proven_positive_definite(self) -> bool {
matches!(self, PdVerdict::PositiveDefinite)
}
}
pub fn pd_verdict(a: &FixedMatrix) -> Result<PdVerdict, OverflowDetected> {
assert!(a.is_square(), "pd_verdict: matrix must be square");
let n = a.rows();
let zero = Interval::point(FixedPoint::ZERO);
let mut l: Vec<Vec<Interval>> = vec![vec![zero; n]; n];
for i in 0..n {
let pivot = Interval::point(a.get(i, i)).try_sub(Interval::try_dot_intervals(&l[i][..i], &l[i][..i])?)?;
if pivot.is_certainly_positive() {
let l_ii = pivot.try_sqrt()?;
l[i][i] = l_ii;
for j in (i + 1)..n {
let numerator = Interval::point(a.get(j, i)).try_sub(Interval::try_dot_intervals(&l[j][..i], &l[i][..i])?)?;
l[j][i] = numerator.try_div(l_ii)?;
}
} else if pivot.hi() <= FixedPoint::ZERO {
return Ok(PdVerdict::NotPositiveDefinite { pivot: i });
} else {
return Ok(PdVerdict::Inconclusive { pivot: i, straddle: pivot });
}
}
Ok(PdVerdict::PositiveDefinite)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "accumulator width budget violated")]
fn mul_exact_refuses_a_product_beyond_the_accumulator() {
let big: i128 = 1 << 100;
let _ = big.mul_exact(big);
}
#[test]
fn mul_exact_is_signed_and_exact_on_magnitudes() {
assert_eq!(6i128.mul_exact(7), 42);
assert_eq!((-6i128).mul_exact(7), -42);
assert_eq!(6i128.mul_exact(-7), -42);
assert_eq!((-6i128).mul_exact(-7), 42);
assert_eq!(0i128.mul_exact(-7), 0);
let near = (1i128 << 63) - 1;
assert_eq!((-near).mul_exact(near), -(near * near));
assert_eq!((-42i128).sign(), Sign::Negative);
assert_eq!(0i128.sign(), Sign::Zero);
assert_eq!(42i128.sign(), Sign::Positive);
assert_eq!(Sign::Negative.flip(), Sign::Positive);
assert_eq!(Sign::Zero.flip(), Sign::Zero);
}
}