use alloc::vec::Vec;
use puremp::Rational;
use crate::math::polynomial::{Monomial, Polynomial, Var};
use crate::nlsat::icp::Rel;
pub fn subst_var(poly: &Polynomial, v: Var, repl: &Polynomial) -> Polynomial {
let mut result = Polynomial::zero();
for (coeff, mono) in poly.terms() {
let k = mono.degree_of(v);
let rest_powers: Vec<(Var, u32)> = mono
.vars()
.filter(|&x| x != v)
.map(|x| (x, mono.degree_of(x)))
.collect();
let rest = Polynomial::from_terms(alloc::vec![(
coeff.clone(),
Monomial::from_powers(&rest_powers)
)]);
let term = if k == 0 { rest } else { rest.mul(&repl.pow(k)) };
result = result.add(&term);
}
result
}
fn solve_linear_for(p: &Polynomial, v: Var) -> Option<(Polynomial, Rational)> {
if p.degree_of(v) != 1 {
return None;
}
let mut coeff_v = Rational::from_integer(0.into());
let mut rest = Polynomial::zero();
for (coeff, mono) in p.terms() {
match mono.degree_of(v) {
0 => {
rest = rest.add(&Polynomial::from_terms(alloc::vec![(
coeff.clone(),
mono.clone()
)]));
}
1 if mono.total_degree() == 1 => {
coeff_v = coeff_v.add(coeff);
}
_ => return None,
}
}
if coeff_v.is_zero() {
return None;
}
Some((rest.neg().scale(&coeff_v.recip()), coeff_v))
}
pub fn eliminate_linear(
mut constraints: Vec<(Polynomial, Rel)>,
can_eliminate: impl Fn(Var, &Rational) -> bool,
) -> Vec<(Polynomial, Rel)> {
let mut guard = 0;
loop {
guard += 1;
if guard > 64 {
break;
}
let mut chosen: Option<(usize, Var, Polynomial)> = None;
'outer: for (i, (p, rel)) in constraints.iter().enumerate() {
if *rel != Rel::Eq {
continue;
}
for v in p.vars() {
if let Some((repl, c)) = solve_linear_for(p, v) {
if !can_eliminate(v, &c) {
continue;
}
chosen = Some((i, v, repl));
break 'outer;
}
}
}
let Some((i, v, repl)) = chosen else { break };
constraints.remove(i);
for (p, _) in constraints.iter_mut() {
if p.degree_of(v) > 0 {
*p = subst_var(p, v, &repl);
}
}
}
constraints
}
pub fn remap_vars(constraints: &[(Polynomial, Rel)]) -> (Vec<(Polynomial, Rel)>, Vec<Var>) {
let mut used: Vec<Var> = constraints.iter().flat_map(|(p, _)| p.vars()).collect();
used.sort_unstable();
used.dedup();
let index_of = |v: Var| used.iter().position(|&u| u == v).unwrap() as Var;
let rewritten = constraints
.iter()
.map(|(p, rel)| {
let terms = p
.terms()
.iter()
.map(|(c, m)| {
let powers: Vec<(Var, u32)> =
m.vars().map(|v| (index_of(v), m.degree_of(v))).collect();
(c.clone(), Monomial::from_powers(&powers))
})
.collect();
(Polynomial::from_terms(terms), *rel)
})
.collect();
(rewritten, used)
}
pub fn sat_by_fixing(constraints: &[(Polynomial, Rel)], is_int: &[bool]) -> bool {
let k = is_int.len();
if !(2..=4).contains(&k) {
return false;
}
let free = (k - 1) as Var;
let fixed: Vec<Var> = (0..free).collect();
let candidates = |int: bool| -> Vec<Rational> {
let mut v: Vec<Rational> = (-8..=8).map(|n| Rational::from_integer(n.into())).collect();
if !int {
for h in [-5i64, -3, -1, 1, 3, 5] {
v.push(Rational::new(h.into(), 2.into()));
}
}
v
};
let cands: Vec<Vec<Rational>> = fixed
.iter()
.map(|&fv| candidates(is_int[fv as usize]))
.collect();
let total: u128 = cands.iter().map(|c| c.len() as u128).product();
if total > 30_000 {
return false;
}
let mut idx = alloc::vec![0usize; fixed.len()];
loop {
let mut cons = constraints.to_vec();
for (fi, &fv) in fixed.iter().enumerate() {
let val = Polynomial::constant(cands[fi][idx[fi]].clone());
for (p, _) in cons.iter_mut() {
if p.degree_of(fv) > 0 {
*p = subst_var(p, fv, &val);
}
}
}
let (reduced, _vars) = remap_vars(&cons);
let dec = if is_int[free as usize] {
crate::nlsat::univariate::decide_int(&reduced, 0)
} else {
crate::nlsat::univariate::decide(&reduced, 0)
};
if dec == Some(true) {
return true;
}
let mut i = 0;
loop {
if i == fixed.len() {
return false;
}
idx[i] += 1;
if idx[i] < cands[i].len() {
break;
}
idx[i] = 0;
i += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i64) -> Rational {
Rational::from_integer(n.into())
}
fn mono(pairs: &[(Var, u32)]) -> Monomial {
Monomial::from_powers(pairs)
}
#[test]
fn subst_composes_polynomials() {
let xy = Polynomial::from_terms(alloc::vec![(r(1), mono(&[(0, 1), (1, 1)]))]);
let repl = Polynomial::from_terms(alloc::vec![
(r(5), Monomial::one()),
(r(-1), mono(&[(0, 1)])),
]);
let out = subst_var(&xy, 1, &repl);
let expect = Polynomial::from_terms(alloc::vec![
(r(5), mono(&[(0, 1)])),
(r(-1), mono(&[(0, 2)])),
]);
assert_eq!(out, expect);
}
#[test]
fn eliminate_reduces_to_univariate() {
let c = alloc::vec![
(
Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(0, 1), (1, 1)])),
(r(-6), Monomial::one()),
]),
Rel::Eq,
),
(
Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(0, 1)])),
(r(1), mono(&[(1, 1)])),
(r(-5), Monomial::one()),
]),
Rel::Eq,
),
];
let out = eliminate_linear(c, |_, _| true);
assert_eq!(out.len(), 1);
let (reduced, vars) = remap_vars(&out);
assert_eq!(vars.len(), 1); assert_eq!(crate::nlsat::univariate::decide(&reduced, 0), Some(true));
}
#[test]
fn nonlinear_coefficient_not_eliminated() {
let p = Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(0, 1), (1, 1)])),
(r(1), Monomial::one()),
]);
assert!(solve_linear_for(&p, 1).is_none());
}
#[test]
fn eliminate_into_inequality() {
let c = alloc::vec![
(
Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(0, 2)])),
(r(1), mono(&[(1, 1)])),
(r(-5), Monomial::one()),
]),
Rel::Eq,
),
(
Polynomial::from_terms(alloc::vec![(r(1), mono(&[(1, 1)]))]),
Rel::Gt
),
];
let out = eliminate_linear(c, |_, _| true);
let (reduced, vars) = remap_vars(&out);
assert_eq!(vars.len(), 1);
assert_eq!(crate::nlsat::univariate::decide(&reduced, 0), Some(true));
}
}