use alloc::vec::Vec;
use puremp::Rational;
use crate::math::polynomial::{Polynomial, Var};
use crate::math::resultant::{principal_subresultant_coeffs, resultant};
use crate::math::upoly::UPoly;
use crate::nlsat::icp::Rel;
use crate::nlsat::realclosure::{Alg, poly_to_upoly, sign_at_point, upoly_to_poly};
const MAX_VARS: usize = 4;
const MAX_DEG: u32 = 12;
const MAX_PROJ: usize = 500;
const MAX_CELLS: usize = 40_000;
fn rel_holds(sigma: i32, rel: Rel) -> bool {
match rel {
Rel::Lt => sigma < 0,
Rel::Le => sigma <= 0,
Rel::Gt => sigma > 0,
Rel::Ge => sigma >= 0,
Rel::Eq => sigma == 0,
Rel::Ne => sigma != 0,
}
}
pub fn cad_sat(constraints: &[(Polynomial, Rel)], num_vars: usize) -> Option<bool> {
if constraints.is_empty() {
return Some(true);
}
if num_vars == 0 {
return Some(constraints.iter().all(|(p, rel)| {
let s = p.as_constant().map(|c| c.signum()).unwrap_or(0);
rel_holds(s, *rel)
}));
}
if num_vars > MAX_VARS {
return None;
}
for (p, _) in constraints {
if p.total_degree() > MAX_DEG {
return None;
}
}
let top: Vec<Polynomial> = clean(
constraints
.iter()
.map(|(p, _)| p.clone())
.filter(|p| !p.is_zero() && p.as_constant().is_none())
.collect(),
);
let mut levels: Vec<Vec<Polynomial>> = alloc::vec![Vec::new(); num_vars];
levels[num_vars - 1] = top;
for main in (1..num_vars).rev() {
let proj = project(&levels[main], main as Var)?;
if proj.len() > MAX_PROJ {
return None;
}
levels[main - 1] = proj;
}
let base = base_samples(&levels[0]);
let mut samples: Vec<Vec<Alg>> = base.into_iter().map(|a| alloc::vec![a]).collect();
#[allow(clippy::needless_range_loop)]
for k in 1..num_vars {
let level_k = &levels[k];
let mut next: Vec<Vec<Alg>> = Vec::new();
for s in &samples {
let children = lift(s, level_k, k as Var)?;
next.extend(children);
if next.len() > MAX_CELLS {
return None;
}
}
samples = next;
}
for s in &samples {
let mut all = true;
for (p, rel) in constraints {
if !rel_holds(sign_at_point(p, s)?, *rel) {
all = false;
break;
}
}
if all {
return Some(true);
}
}
Some(false)
}
fn project(polys: &[Polynomial], var: Var) -> Option<Vec<Polynomial>> {
let mut proj: Vec<Polynomial> = Vec::new();
let conditioned: Vec<Polynomial> = polys.iter().map(|p| squarefree_main(p, var)).collect();
for p in conditioned.iter().filter(|p| p.degree_of(var) == 0) {
proj.push(p.clone());
}
let reducta_lists: Vec<Vec<Polynomial>> = conditioned
.iter()
.filter(|p| p.degree_of(var) >= 1)
.map(|f| reducta(f, var))
.collect();
for rl in &reducta_lists {
for g in rl {
let d = g.degree_of(var);
debug_assert!(d >= 1);
proj.push(g.coeff_of_var(var, d)); let gd = g.deriv_var(var);
if gd.degree_of(var) >= 1 {
proj.extend(principal_subresultant_coeffs(g, &gd, var)?);
}
}
}
for i in 0..reducta_lists.len() {
for j in (i + 1)..reducta_lists.len() {
for g in &reducta_lists[i] {
for h in &reducta_lists[j] {
proj.extend(principal_subresultant_coeffs(g, h, var)?);
}
}
}
}
Some(clean(proj))
}
fn reducta(f: &Polynomial, var: Var) -> Vec<Polynomial> {
let mut out = Vec::new();
let mut g = f.clone();
loop {
let d = g.degree_of(var);
if d < 1 {
break;
}
let lc = g.coeff_of_var(var, d);
out.push(g.clone());
if lc.as_constant().is_some() {
break; }
let lead = lc.mul(&Polynomial::var(var).pow(d));
g = g.sub(&lead);
if g.is_zero() {
break;
}
}
out
}
fn squarefree_main(f: &Polynomial, var: Var) -> Polynomial {
let d = f.degree_of(var) as usize;
if d == 0 {
return f.clone();
}
let mut coeffs = Vec::with_capacity(d + 1);
for j in 0..=d {
match f.coeff_of_var(var, j as u32).as_constant() {
Some(c) => coeffs.push(c),
None => return f.clone(), }
}
let sf = UPoly::from_coeffs(coeffs).squarefree();
upoly_to_poly(&sf, var)
}
fn clean(polys: Vec<Polynomial>) -> Vec<Polynomial> {
let mut out: Vec<Polynomial> = Vec::new();
for p in polys {
if p.is_zero() || p.as_constant().is_some() {
continue;
}
if !out.contains(&p) {
out.push(p);
}
}
out
}
fn base_samples(polys: &[Polynomial]) -> Vec<Alg> {
let mut roots: Vec<Alg> = Vec::new();
for f in polys {
let u = poly_to_upoly(f, 0);
if u.degree() >= 1 {
for beta in Alg::roots_of(&u) {
if !roots
.iter()
.any(|e| e.compare(&beta) == core::cmp::Ordering::Equal)
{
roots.push(beta);
}
}
}
}
roots.sort_by(|a, b| a.compare(b));
samples_around(&roots)
}
fn samples_around(roots: &[Alg]) -> Vec<Alg> {
let one = Rational::from_integer(1.into());
if roots.is_empty() {
return alloc::vec![Alg::Rational(Rational::from_integer(0.into()))];
}
let mut rs = roots.to_vec();
let mut out: Vec<Alg> = Vec::new();
let first_lo = rs[0].interval().0;
out.push(Alg::Rational(
Rational::from_integer(first_lo.floor()).sub(&one),
));
for i in 0..rs.len() {
out.push(rs[i].clone());
if i + 1 < rs.len() {
let mid = rational_between(&mut rs, i);
out.push(Alg::Rational(mid));
}
}
let last_hi = rs.last().unwrap().interval().1;
out.push(Alg::Rational(
Rational::from_integer(last_hi.ceil()).add(&one),
));
out
}
fn rational_between(rs: &mut [Alg], i: usize) -> Rational {
let two = Rational::from_integer(2.into());
for _ in 0..4000 {
let a_hi = rs[i].interval().1;
let b_lo = rs[i + 1].interval().0;
if a_hi < b_lo {
return a_hi.add(&b_lo).div(&two);
}
rs[i].refine();
rs[i + 1].refine();
}
rs[i].approx().add(&rs[i + 1].approx()).div(&two)
}
fn lift(sample: &[Alg], polys: &[Polynomial], var: Var) -> Option<Vec<Vec<Alg>>> {
let mut roots: Vec<Alg> = Vec::new();
for f in polys {
for r in roots_at(f, sample, var)? {
if !roots
.iter()
.any(|e| e.compare(&r) == core::cmp::Ordering::Equal)
{
roots.push(r);
}
}
}
roots.sort_by(|a, b| a.compare(b));
let coords = samples_around(&roots);
Some(
coords
.into_iter()
.map(|c| {
let mut ext = sample.to_vec();
ext.push(c);
ext
})
.collect(),
)
}
fn roots_at(f: &Polynomial, sample: &[Alg], var: Var) -> Option<Vec<Alg>> {
if f.degree_of(var) == 0 {
return Some(Vec::new()); }
let mut g = f.clone();
for (i, coord) in sample.iter().enumerate() {
match coord {
Alg::Rational(r) => {
g = crate::nlsat::elim::subst_var(&g, i as Var, &Polynomial::constant(r.clone()));
}
Alg::Irrational { poly, .. } => {
g = resultant(&g, &upoly_to_poly(poly, i as Var), i as Var)?;
}
}
}
let u = poly_to_upoly(&g, var);
if u.is_zero() {
let d = f.degree_of(var);
let mut nullified = true;
for k in 0..=d {
if sign_at_point(&f.coeff_of_var(var, k), sample)? != 0 {
nullified = false;
break;
}
}
if nullified {
return Some(Vec::new());
}
return None;
}
let candidates = Alg::roots_of(&u);
let mut out = Vec::new();
for beta in candidates {
let mut point = sample.to_vec();
point.push(beta.clone());
if sign_at_point(f, &point)? == 0 {
out.push(beta);
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::polynomial::Monomial;
fn r(n: i64) -> Rational {
Rational::from_integer(n.into())
}
fn mono(p: &[(Var, u32)]) -> Monomial {
Monomial::from_powers(p)
}
fn poly(terms: &[(i64, &[(Var, u32)])]) -> Polynomial {
Polynomial::from_terms(terms.iter().map(|&(c, m)| (r(c), mono(m))).collect())
}
#[test]
fn circle_vs_hyperbola_unsat() {
let c = alloc::vec![
(poly(&[(1, &[(0, 2)]), (1, &[(1, 2)]), (-1, &[])]), Rel::Lt),
(poly(&[(1, &[(0, 1), (1, 1)]), (-1, &[])]), Rel::Gt),
];
assert_eq!(cad_sat(&c, 2), Some(false));
}
#[test]
fn product_and_sum_sat() {
let c = alloc::vec![
(poly(&[(1, &[(0, 1), (1, 1)]), (-5, &[])]), Rel::Gt),
(poly(&[(1, &[(0, 1)]), (1, &[(1, 1)]), (-3, &[])]), Rel::Lt),
];
assert_eq!(cad_sat(&c, 2), Some(true));
}
#[test]
fn circle_vs_hyperbola_sat() {
let c = alloc::vec![
(poly(&[(1, &[(0, 2)]), (1, &[(1, 2)]), (-4, &[])]), Rel::Lt),
(poly(&[(1, &[(0, 1), (1, 1)]), (-1, &[])]), Rel::Gt),
];
assert_eq!(cad_sat(&c, 2), Some(true));
}
#[test]
fn circle_equality_sat() {
let c = alloc::vec![(poly(&[(1, &[(0, 2)]), (1, &[(1, 2)]), (-1, &[])]), Rel::Eq,)];
assert_eq!(cad_sat(&c, 2), Some(true));
}
#[test]
fn two_equalities_and_inequality_sat() {
let c = alloc::vec![
(poly(&[(1, &[(0, 2)]), (-2, &[])]), Rel::Eq),
(poly(&[(1, &[(1, 2)]), (-3, &[])]), Rel::Eq),
(poly(&[(1, &[(0, 1)]), (1, &[(1, 1)])]), Rel::Lt),
];
assert_eq!(cad_sat(&c, 2), Some(true));
}
#[test]
fn inequalities_only_sat() {
let c = alloc::vec![
(poly(&[(1, &[(0, 2)]), (-1, &[(1, 2)])]), Rel::Gt),
(poly(&[(1, &[(1, 1)]), (-10, &[])]), Rel::Gt),
(poly(&[(1, &[(0, 1)]), (-1, &[])]), Rel::Lt),
];
assert_eq!(cad_sat(&c, 2), Some(true));
}
#[test]
fn eq_and_strict_square_ineq_sat() {
let c = alloc::vec![
(poly(&[(1, &[(0, 1), (1, 1)]), (-2, &[])]), Rel::Eq),
(poly(&[(1, &[(0, 2)]), (-1, &[(1, 2)])]), Rel::Lt),
];
assert_eq!(cad_sat(&c, 2), Some(true));
}
#[test]
fn sphere_vs_plane_unsat() {
let c = alloc::vec![
(
poly(&[(1, &[(0, 2)]), (1, &[(1, 2)]), (1, &[(2, 2)]), (-1, &[])]),
Rel::Eq
),
(
poly(&[(1, &[(0, 1)]), (1, &[(1, 1)]), (1, &[(2, 1)]), (-2, &[])]),
Rel::Gt
),
];
assert_eq!(cad_sat(&c, 3), Some(false));
}
#[test]
fn coupled_products_unsat() {
let c = alloc::vec![
(poly(&[(1, &[(0, 1), (1, 1)]), (-1, &[(2, 1)])]), Rel::Eq),
(poly(&[(1, &[(1, 1), (2, 1)]), (-1, &[(0, 1)])]), Rel::Eq),
(poly(&[(1, &[(2, 1), (0, 1)]), (-1, &[(1, 1)])]), Rel::Eq),
(poly(&[(1, &[(0, 1)])]), Rel::Gt),
(poly(&[(1, &[(1, 1)])]), Rel::Gt),
(poly(&[(1, &[(2, 1)])]), Rel::Gt),
(poly(&[(1, &[(0, 1)]), (-1, &[])]), Rel::Ne),
];
assert_eq!(cad_sat(&c, 3), Some(false));
}
#[test]
fn empty_variety_unsat() {
let c = alloc::vec![(poly(&[(1, &[(0, 2)]), (1, &[(1, 2)]), (1, &[])]), Rel::Eq,)];
assert_eq!(cad_sat(&c, 2), Some(false));
}
}