use alloc::vec::Vec;
use puremp::Rational;
use crate::math::interval::Interval;
use crate::math::polynomial::{Polynomial, Var};
use crate::math::resultant::resultant;
use crate::math::upoly::{self, UPoly};
use crate::nlsat::elim::subst_var;
use crate::nlsat::icp::eval_interval;
fn two() -> Rational {
Rational::from_integer(2.into())
}
pub(crate) fn upoly_to_poly(u: &UPoly, var: Var) -> Polynomial {
use crate::math::polynomial::Monomial;
let terms = u
.coeffs()
.iter()
.enumerate()
.filter(|(_, c)| !c.is_zero())
.map(|(k, c)| (c.clone(), Monomial::from_powers(&[(var, k as u32)])))
.collect();
Polynomial::from_terms(terms)
}
pub(crate) fn poly_to_upoly(p: &Polynomial, var: Var) -> UPoly {
let deg = p.degree_of(var) as usize;
let mut coeffs = alloc::vec![Rational::from_integer(0.into()); deg + 1];
for (k, slot) in coeffs.iter_mut().enumerate() {
*slot = p
.coeff_of_var(var, k as u32)
.as_constant()
.unwrap_or_else(|| Rational::from_integer(0.into()));
}
UPoly::from_coeffs(coeffs)
}
fn eliminate_to_univariate(p: &Polynomial, point: &[Alg]) -> Option<UPoly> {
let n = point.len();
let z = n as Var; let mut f = Polynomial::var(z).sub(p);
for (i, coord) in point.iter().enumerate() {
match coord {
Alg::Rational(r) => {
f = subst_var(&f, i as Var, &Polynomial::constant(r.clone()));
}
Alg::Irrational { poly, .. } => {
let qi = upoly_to_poly(poly, i as Var);
f = resultant(&f, &qi, i as Var)?; }
}
}
Some(poly_to_upoly(&f, z))
}
fn nonzero_root_lower_bound(r: &UPoly) -> Rational {
let coeffs = r.coeffs();
let m = coeffs.iter().position(|c| !c.is_zero());
let Some(m) = m else {
return Rational::from_integer(1.into()); };
let am = coeffs[m].abs();
let mut maxabs = Rational::from_integer(0.into());
for c in coeffs {
let a = c.abs();
if a > maxabs {
maxabs = a;
}
}
am.div(&am.add(&maxabs))
}
fn strict_interval_sign(iv: &Interval) -> Option<i32> {
let (lo, hi) = iv.bounds()?;
use crate::math::interval::Bound;
if let Bound::Finite { value, .. } = lo
&& value.is_positive()
{
return Some(1);
}
if let Bound::Finite { value, .. } = hi
&& value.is_negative()
{
return Some(-1);
}
None
}
fn interval_within(iv: &Interval, l: &Rational) -> bool {
use crate::math::interval::Bound;
let Some((lo, hi)) = iv.bounds() else {
return true; };
let lo_ok = matches!(lo, Bound::Finite { value, .. } if value > &l.neg());
let hi_ok = matches!(hi, Bound::Finite { value, .. } if value < l);
lo_ok && hi_ok
}
fn boxes_of(point: &[Alg]) -> Vec<Interval> {
point
.iter()
.map(|a| {
let (lo, hi) = a.interval();
if lo == hi {
Interval::point(lo)
} else {
Interval::closed(lo, hi)
}
})
.collect()
}
pub fn sign_at_point(p: &Polynomial, point: &[Alg]) -> Option<i32> {
let refinable: Vec<usize> = (0..point.len())
.filter(|&i| matches!(point[i], Alg::Irrational { .. }))
.collect();
let mut pt = point.to_vec();
for _ in 0..64 {
let boxes = boxes_of(&pt);
let val = eval_interval(p, &boxes);
if let Some(s) = strict_interval_sign(&val) {
return Some(s);
}
if refinable.is_empty() {
return Some(exact_rational_sign(p, &pt));
}
for &i in &refinable {
pt[i].refine();
}
}
let r = eliminate_to_univariate(p, &pt)?;
if r.is_zero() {
return Some(0); }
let l = nonzero_root_lower_bound(&r);
for _ in 0..400 {
let boxes = boxes_of(&pt);
let val = eval_interval(p, &boxes);
if let Some(s) = strict_interval_sign(&val) {
return Some(s);
}
if interval_within(&val, &l) {
return Some(0); }
for &i in &refinable {
pt[i].refine();
}
}
Some(0)
}
fn exact_rational_sign(p: &Polynomial, point: &[Alg]) -> i32 {
let assign = |v: Var| match point.get(v as usize) {
Some(Alg::Rational(r)) => r.clone(),
_ => Rational::from_integer(0.into()),
};
p.eval(&assign).signum()
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Alg {
Rational(Rational),
Irrational {
poly: UPoly,
lo: Rational,
hi: Rational,
},
}
impl Alg {
pub fn rational(r: Rational) -> Alg {
Alg::Rational(r)
}
pub fn roots_of(p: &UPoly) -> Vec<Alg> {
let sf = p.squarefree();
let intervals = upoly::isolate_roots(&sf);
let mut out = Vec::new();
for (lo, hi) in intervals {
out.push(Alg::from_isolation(sf.clone(), lo, hi));
}
out
}
fn from_isolation(poly: UPoly, lo: Rational, hi: Rational) -> Alg {
if poly.degree() == 1 {
let a = poly.coeffs()[1].clone();
let b = poly.coeffs()[0].clone();
return Alg::Rational(b.neg().div(&a));
}
Alg::Irrational { poly, lo, hi }
}
pub fn approx(&self) -> Rational {
match self {
Alg::Rational(r) => r.clone(),
Alg::Irrational { lo, hi, .. } => lo.add(hi).div(&two()),
}
}
pub fn refine(&mut self) {
if let Alg::Irrational { poly, lo, hi } = self {
let mid = lo.add(hi).div(&two());
let sm = poly.sign_at(&mid);
if sm == 0 {
*self = Alg::Rational(mid);
return;
}
if poly.sign_at(lo) == sm {
*lo = mid;
} else {
*hi = mid;
}
}
}
pub fn sign_of(&self, q: &UPoly) -> i32 {
match self {
Alg::Rational(r) => q.sign_at(r),
Alg::Irrational { poly, lo, hi } => {
if q.is_zero() {
return 0;
}
let g = poly.gcd(q);
if g.degree() >= 1 {
let sl = g.sign_at(lo);
let sh = g.sign_at(hi);
if sl != 0 && sh != 0 && sl != sh {
return 0;
}
}
let mut a = lo.clone();
let mut b = hi.clone();
let ps = poly.clone();
for _ in 0..256 {
let qa = q.sign_at(&a);
let qb = q.sign_at(&b);
if qa != 0 && qa == qb {
return qa; }
let mid = a.add(&b).div(&two());
let sm = ps.sign_at(&mid);
if sm == 0 {
return q.sign_at(&mid);
}
if ps.sign_at(&a) == sm {
a = mid;
} else {
b = mid;
}
}
q.sign_at(&a.add(&b).div(&two()))
}
}
}
pub fn locate(&self, r: &Rational) -> core::cmp::Ordering {
use core::cmp::Ordering;
match self {
Alg::Rational(v) => r.cmp(v),
Alg::Irrational { poly, lo, hi } => {
if r <= lo {
return Ordering::Less;
}
if r >= hi {
return Ordering::Greater;
}
let sr = poly.sign_at(r);
if sr == 0 {
return Ordering::Equal;
}
if sr == poly.sign_at(lo) {
Ordering::Less
} else {
Ordering::Greater
}
}
}
}
pub fn interval(&self) -> (Rational, Rational) {
match self {
Alg::Rational(r) => (r.clone(), r.clone()),
Alg::Irrational { lo, hi, .. } => (lo.clone(), hi.clone()),
}
}
pub fn compare(&self, other: &Alg) -> core::cmp::Ordering {
use core::cmp::Ordering;
match (self, other) {
(Alg::Rational(a), Alg::Rational(b)) => return a.cmp(b),
(Alg::Rational(a), _) => return other.locate(a),
(_, Alg::Rational(b)) => return self.locate(b).reverse(),
_ => {}
}
let mut a = self.clone();
let mut b = other.clone();
for _ in 0..2000 {
let (alo, ahi) = a.interval();
let (blo, bhi) = b.interval();
if ahi <= blo {
return Ordering::Less;
}
if bhi <= alo {
return Ordering::Greater;
}
if let Alg::Irrational { poly: pa, .. } = &a
&& blo >= alo
&& bhi <= ahi
&& b.sign_of(pa) == 0
{
return Ordering::Equal;
}
if let Alg::Irrational { poly: pb, .. } = &b
&& alo >= blo
&& ahi <= bhi
&& a.sign_of(pb) == 0
{
return Ordering::Equal;
}
a.refine();
b.refine();
}
a.approx().cmp(&b.approx())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i64) -> Rational {
Rational::from_integer(n.into())
}
fn p(cs: &[i64]) -> UPoly {
UPoly::from_coeffs(cs.iter().map(|&c| r(c)).collect())
}
#[test]
fn isolates_sqrt2() {
let roots = Alg::roots_of(&p(&[-2, 0, 1]));
assert_eq!(roots.len(), 2);
assert!(roots[0].approx() < r(0));
assert!(roots[1].approx() > r(0));
assert!(matches!(roots[0], Alg::Irrational { .. }));
}
#[test]
fn rational_root_is_rational() {
let roots = Alg::roots_of(&p(&[-6, 2]));
assert_eq!(roots, vec![Alg::Rational(r(3))]);
}
#[test]
fn sign_at_sqrt2() {
let sqrt2 = Alg::roots_of(&p(&[-2, 0, 1]))
.into_iter()
.find(|a| a.approx() > r(0))
.unwrap();
assert_eq!(sqrt2.sign_of(&p(&[-2, 0, 1])), 0); assert_eq!(sqrt2.sign_of(&p(&[-1, 1])), 1); assert_eq!(sqrt2.sign_of(&p(&[-2, 1])), -1); assert_eq!(sqrt2.sign_of(&p(&[-3, 0, 1])), -1);
}
#[test]
fn locate_rationals() {
use core::cmp::Ordering;
let sqrt2 = Alg::roots_of(&p(&[-2, 0, 1]))
.into_iter()
.find(|a| a.approx() > r(0))
.unwrap();
assert_eq!(sqrt2.locate(&r(1)), Ordering::Less); assert_eq!(sqrt2.locate(&r(2)), Ordering::Greater); assert_eq!(
sqrt2.locate(&Rational::new(3.into(), 2.into())),
Ordering::Greater
); assert_eq!(
sqrt2.locate(&Rational::new(7.into(), 5.into())),
Ordering::Less
); }
#[test]
fn sign_at_algebraic_point() {
use crate::math::polynomial::Monomial;
let sqrt2 = Alg::roots_of(&p(&[-2, 0, 1]))
.into_iter()
.find(|a| a.approx() > r(0))
.unwrap();
let g = Polynomial::from_terms(alloc::vec![
(r(1), Monomial::from_powers(&[(0, 2)])),
(r(1), Monomial::from_powers(&[(1, 2)])),
(r(-3), Monomial::one()),
]);
assert_eq!(
sign_at_point(&g, &[Alg::Rational(r(1)), sqrt2.clone()]).unwrap(),
0
);
let g2 = Polynomial::from_terms(alloc::vec![
(r(1), Monomial::from_powers(&[(0, 2)])),
(r(1), Monomial::from_powers(&[(1, 2)])),
(r(-2), Monomial::one()),
]);
assert_eq!(
sign_at_point(&g2, &[Alg::Rational(r(1)), sqrt2.clone()]).unwrap(),
1
);
let g3 = Polynomial::from_terms(alloc::vec![
(r(1), Monomial::from_powers(&[(0, 2)])),
(r(1), Monomial::from_powers(&[(1, 2)])),
(r(-4), Monomial::one()),
]);
assert_eq!(
sign_at_point(&g3, &[Alg::Rational(r(1)), sqrt2.clone()]).unwrap(),
-1
);
let g4 = Polynomial::from_terms(alloc::vec![
(r(1), Monomial::from_powers(&[(0, 1), (1, 1)])),
(r(-2), Monomial::one()),
]);
assert_eq!(
sign_at_point(&g4, &[sqrt2.clone(), sqrt2.clone()]).unwrap(),
0
);
let g5 = Polynomial::from_terms(alloc::vec![
(r(1), Monomial::from_powers(&[(0, 1), (1, 1)])),
(r(-1), Monomial::one()),
]);
assert_eq!(sign_at_point(&g5, &[sqrt2.clone(), sqrt2]).unwrap(), 1);
}
}