use alloc::vec;
use alloc::vec::Vec;
use puremp::{Int, Rational};
use super::arith::LinExpr;
use crate::ast::AstId;
fn int(n: i64) -> Int {
Int::from(n)
}
fn rat_int(i: &Int) -> Rational {
Rational::from_integer(i.clone())
}
#[derive(Clone)]
pub enum Atom {
Lt(LinExpr),
Le(LinExpr),
Eq(LinExpr),
Ne(LinExpr),
Div(Int, LinExpr),
Ndiv(Int, LinExpr),
}
pub type Dnf = Vec<Vec<Atom>>;
fn expr_of(a: &Atom) -> &LinExpr {
match a {
Atom::Lt(e) | Atom::Le(e) | Atom::Eq(e) | Atom::Ne(e) => e,
Atom::Div(_, e) | Atom::Ndiv(_, e) => e,
}
}
fn icoeff(e: &LinExpr, x: AstId) -> Option<Int> {
e.coeff_of(x).to_integer()
}
fn has_var(a: &Atom, x: AstId) -> bool {
!expr_of(a).coeff_of(x).is_zero()
}
fn scale_atom(a: &Atom, f: &Int) -> Atom {
let fr = rat_int(f);
match a {
Atom::Lt(e) => Atom::Lt(e.scale(&fr)),
Atom::Le(e) => Atom::Le(e.scale(&fr)),
Atom::Eq(e) => Atom::Eq(e.scale(&fr)),
Atom::Ne(e) => Atom::Ne(e.scale(&fr)),
Atom::Div(m, e) => Atom::Div(m * f, e.scale(&fr)),
Atom::Ndiv(m, e) => Atom::Ndiv(m * f, e.scale(&fr)),
}
}
fn unitize_x(a: &Atom, x: AstId) -> Atom {
let e = expr_of(a);
let c = e.coeff_of(x); let sign = if c.is_negative() {
Rational::from_integer(int(-1))
} else {
Rational::from_integer(int(1))
};
let e2 = e
.sub(&LinExpr::var(x).scale(&c))
.add(&LinExpr::var(x).scale(&sign));
match a {
Atom::Lt(_) => Atom::Lt(e2),
Atom::Le(_) => Atom::Le(e2),
Atom::Eq(_) => Atom::Eq(e2),
Atom::Ne(_) => Atom::Ne(e2),
Atom::Div(m, _) => Atom::Div(m.clone(), e2),
Atom::Ndiv(m, _) => Atom::Ndiv(m.clone(), e2),
}
}
fn subst(a: &Atom, x: AstId, val: &LinExpr) -> Atom {
let e = expr_of(a);
let c = e.coeff_of(x);
let e2 = e.sub(&LinExpr::var(x).scale(&c)).add(&val.scale(&c));
match a {
Atom::Lt(_) => Atom::Lt(e2),
Atom::Le(_) => Atom::Le(e2),
Atom::Eq(_) => Atom::Eq(e2),
Atom::Ne(_) => Atom::Ne(e2),
Atom::Div(m, _) => Atom::Div(m.clone(), e2),
Atom::Ndiv(m, _) => Atom::Ndiv(m.clone(), e2),
}
}
fn eval_ground(a: &Atom) -> Option<bool> {
let e = expr_of(a);
if !e.is_constant() {
return None;
}
let k = e.const_term();
Some(match a {
Atom::Lt(_) => k.is_negative(),
Atom::Le(_) => k.is_negative() || k.is_zero(),
Atom::Eq(_) => k.is_zero(),
Atom::Ne(_) => !k.is_zero(),
Atom::Div(m, _) => k.to_integer().is_some_and(|i| i.rem_euclid(m).is_zero()),
Atom::Ndiv(m, _) => k.to_integer().is_some_and(|i| !i.rem_euclid(m).is_zero()),
})
}
fn neg_atom(a: &Atom) -> Atom {
match a {
Atom::Lt(e) => Atom::Le(e.neg()),
Atom::Le(e) => Atom::Lt(e.neg()),
Atom::Eq(e) => Atom::Ne(e.clone()),
Atom::Ne(e) => Atom::Eq(e.clone()),
Atom::Div(m, e) => Atom::Ndiv(m.clone(), e.clone()),
Atom::Ndiv(m, e) => Atom::Div(m.clone(), e.clone()),
}
}
fn minus_inf(a: &Atom, x: AstId) -> Option<bool> {
let e = expr_of(a);
let c = e.coeff_of(x); let neg = c.is_negative();
match a {
Atom::Lt(_) | Atom::Le(_) => Some(!neg),
Atom::Eq(_) => Some(false),
Atom::Ne(_) => Some(true),
Atom::Div(_, _) | Atom::Ndiv(_, _) => None,
}
}
fn cube_exists(cube: &[Atom], x: AstId, budget: &mut u64) -> Option<Dnf> {
let mut with_x = Vec::new();
let mut without: Vec<Atom> = Vec::new();
for a in cube {
if has_var(a, x) {
with_x.push(a.clone());
} else {
without.push(a.clone());
}
}
if with_x.is_empty() {
return Some(vec![without]);
}
let mut delta = int(1);
for a in &with_x {
delta = delta.lcm(&icoeff(expr_of(a), x)?.abs());
}
let mut norm: Vec<Atom> = Vec::new();
for a in &with_x {
let c = icoeff(expr_of(a), x)?;
let factor = &delta / &c.abs();
norm.push(unitize_x(&scale_atom(a, &factor), x));
}
if delta != int(1) {
norm.push(Atom::Div(delta.clone(), LinExpr::var(x)));
}
let mut dd = int(1);
for a in &norm {
if let Atom::Div(m, _) | Atom::Ndiv(m, _) = a {
dd = dd.lcm(m);
}
}
let d_span = dd.to_i64().filter(|&n| (1..=5000).contains(&n))?;
let mut bset: Vec<LinExpr> = Vec::new();
let mut minf: Vec<Atom> = Vec::new();
let mut minf_dead = false;
for a in &norm {
let e = expr_of(a);
let c = e.coeff_of(x);
let neg = c.is_negative();
let t = e.sub(&LinExpr::var(x).scale(&c));
match a {
Atom::Lt(_) if neg => bset.push(t),
Atom::Le(_) if neg => bset.push(t.sub(&LinExpr::constant(rat_int(&int(1))))),
Atom::Eq(_) => {
let point = t.scale(&c).neg(); bset.push(point.sub(&LinExpr::constant(rat_int(&int(1)))));
}
_ => {}
}
match minus_inf(a, x) {
Some(true) => {} Some(false) => minf_dead = true, None => minf.push(a.clone()), }
}
let mut out: Dnf = Vec::new();
if !minf_dead {
for j in 1..=d_span {
take(budget)?;
let jv = LinExpr::constant(rat_int(&int(j)));
let mut c2 = without.clone();
if push_subst(&mut c2, &minf, x, &jv) {
out.push(c2);
}
}
}
for b in &bset {
for j in 1..=d_span {
take(budget)?;
let val = b.add(&LinExpr::constant(rat_int(&int(j))));
let mut c2 = without.clone();
if push_subst(&mut c2, &norm, x, &val) {
out.push(c2);
}
}
}
Some(out)
}
fn take(budget: &mut u64) -> Option<()> {
if *budget == 0 {
return None;
}
*budget -= 1;
Some(())
}
fn push_subst(cube: &mut Vec<Atom>, atoms: &[Atom], x: AstId, val: &LinExpr) -> bool {
for a in atoms {
let s = subst(a, x, val);
match eval_ground(&s) {
Some(true) => {}
Some(false) => return false,
None => cube.push(s),
}
}
true
}
pub fn exists(dnf: &Dnf, x: AstId, budget: &mut u64) -> Option<Dnf> {
let mut out = Vec::new();
for cube in dnf {
out.extend(cube_exists(cube, x, budget)?);
}
Some(out)
}
fn negate(dnf: &Dnf, budget: &mut u64) -> Option<Dnf> {
let mut acc: Dnf = vec![Vec::new()];
for cube in dnf {
let mut next: Dnf = Vec::new();
for a in cube {
let na = neg_atom(a);
for c in &acc {
take(budget)?;
let mut c2 = c.clone();
c2.push(na.clone());
next.push(c2);
if next.len() > 4000 {
return None;
}
}
}
acc = next;
if acc.is_empty() {
return Some(Vec::new()); }
}
Some(acc)
}
pub fn forall(dnf: &Dnf, x: AstId, budget: &mut u64) -> Option<Dnf> {
let n = negate(dnf, budget)?;
let e = exists(&n, x, budget)?;
negate(&e, budget)
}
pub fn ground_sat(dnf: &Dnf) -> Option<bool> {
let mut any = false;
for cube in dnf {
let mut all = true;
for a in cube {
match eval_ground(a) {
Some(true) => {}
Some(false) => {
all = false;
break;
}
None => return None, }
}
if all {
any = true;
}
}
Some(any)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::AstId;
fn c(n: i64) -> LinExpr {
LinExpr::constant(Rational::from_integer(int(n)))
}
fn lin(coeff: i64, x: AstId, k: i64) -> LinExpr {
LinExpr::var(x)
.scale(&Rational::from_integer(int(coeff)))
.add(&c(k))
}
#[test]
fn forall_exists_gt() {
let (x, y) = (AstId(1000), AstId(1001));
let phi: Dnf = vec![vec![Atom::Lt(lin(1, x, 0).sub(&LinExpr::var(y)))]];
let mut b = 1_000_000u64;
let ey = exists(&phi, y, &mut b).unwrap();
let all = forall(&ey, x, &mut b).unwrap();
assert_eq!(ground_sat(&all), Some(true));
}
#[test]
fn forall_exists_empty() {
let (x, y) = (AstId(1000), AstId(1001));
let phi: Dnf = vec![vec![
Atom::Lt(lin(1, x, 0).sub(&LinExpr::var(y))), Atom::Lt(LinExpr::var(y).sub(&lin(1, x, 0))), ]];
let mut b = 1_000_000u64;
let ey = exists(&phi, y, &mut b).unwrap();
let all = forall(&ey, x, &mut b).unwrap();
assert_eq!(ground_sat(&all), Some(false));
}
#[test]
fn forall_exists_even() {
let (x, y) = (AstId(1000), AstId(1001));
let e = LinExpr::var(y)
.scale(&Rational::from_integer(int(2)))
.sub(&LinExpr::var(x));
let phi: Dnf = vec![vec![Atom::Eq(e)]];
let mut b = 1_000_000u64;
let ey = exists(&phi, y, &mut b).unwrap();
let all = forall(&ey, x, &mut b).unwrap();
assert_eq!(ground_sat(&all), Some(false));
}
#[test]
fn forall_exists_double() {
let (x, y) = (AstId(1000), AstId(1001));
let e = LinExpr::var(y)
.scale(&Rational::from_integer(int(2)))
.sub(&LinExpr::var(x).scale(&Rational::from_integer(int(2))));
let phi: Dnf = vec![vec![Atom::Eq(e)]];
let mut b = 1_000_000u64;
let ey = exists(&phi, y, &mut b).unwrap();
let all = forall(&ey, x, &mut b).unwrap();
assert_eq!(ground_sat(&all), Some(true));
}
}