use std::collections::{BTreeMap, HashSet};
use logicaffeine_base::numeric::BigInt;
use crate::reify::{extract_binary_app, extract_slit, extract_sname, extract_svar, VarInterner};
use crate::term::Term;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntExpr {
pub constant: BigInt,
pub coeffs: BTreeMap<i64, BigInt>,
}
impl IntExpr {
pub fn constant(c: impl Into<BigInt>) -> Self {
IntExpr {
constant: c.into(),
coeffs: BTreeMap::new(),
}
}
pub fn var(idx: i64) -> Self {
let mut coeffs = BTreeMap::new();
coeffs.insert(idx, BigInt::from_i64(1));
IntExpr {
constant: BigInt::zero(),
coeffs,
}
}
pub fn add(&self, other: &Self) -> Self {
let mut result = self.clone();
result.constant = result.constant.add(&other.constant);
for (&v, c) in &other.coeffs {
let entry = result.coeffs.entry(v).or_insert_with(BigInt::zero);
*entry = entry.add(c);
if entry.is_zero() {
result.coeffs.remove(&v);
}
}
result
}
pub fn neg(&self) -> Self {
IntExpr {
constant: self.constant.negated(),
coeffs: self.coeffs.iter().map(|(&v, c)| (v, c.negated())).collect(),
}
}
pub fn sub(&self, other: &Self) -> Self {
self.add(&other.neg())
}
pub fn scale(&self, k: impl Into<BigInt>) -> Self {
let k = k.into();
if k.is_zero() {
return IntExpr::constant(0);
}
IntExpr {
constant: self.constant.mul(&k),
coeffs: self
.coeffs
.iter()
.map(|(&v, c)| (v, c.mul(&k)))
.filter(|(_, c)| !c.is_zero())
.collect(),
}
}
pub fn is_constant(&self) -> bool {
self.coeffs.is_empty()
}
pub fn get_coeff(&self, var: i64) -> BigInt {
self.coeffs.get(&var).cloned().unwrap_or_else(BigInt::zero)
}
}
#[derive(Debug, Clone)]
pub struct IntConstraint {
pub expr: IntExpr,
pub strict: bool,
}
impl IntConstraint {
pub fn is_satisfied_constant(&self) -> bool {
if !self.expr.is_constant() {
return true; }
let c = &self.expr.constant;
if self.strict {
c.is_negative() } else {
!c.is_positive() }
}
pub fn normalize(&mut self) {
let g = self
.expr
.coeffs
.values()
.chain(std::iter::once(&self.expr.constant))
.filter(|x| !x.is_zero())
.fold(BigInt::zero(), |a, b| gcd(&a, &b.abs()));
if g > BigInt::from_i64(1) {
self.expr.constant = exact_div(&self.expr.constant, &g);
for v in self.expr.coeffs.values_mut() {
*v = exact_div(v, &g);
}
}
}
}
fn gcd(a: &BigInt, b: &BigInt) -> BigInt {
if b.is_zero() {
a.clone()
} else {
let (_, r) = a.div_rem(b).expect("gcd divisor is nonzero");
gcd(b, &r)
}
}
fn exact_div(a: &BigInt, g: &BigInt) -> BigInt {
a.div_rem(g).expect("gcd is nonzero").0
}
pub fn reify_int_linear(term: &Term, vars: &mut VarInterner) -> Option<IntExpr> {
if let Some(n) = extract_slit(term) {
return Some(IntExpr::constant(n));
}
if let Some(i) = extract_svar(term) {
return Some(IntExpr::var(i));
}
if let Some(name) = extract_sname(term) {
return Some(IntExpr::var(vars.intern(&name)));
}
if let Some((op, a, b)) = extract_binary_app(term) {
match op.as_str() {
"add" => {
let la = reify_int_linear(&a, vars)?;
let lb = reify_int_linear(&b, vars)?;
return Some(la.add(&lb));
}
"sub" => {
let la = reify_int_linear(&a, vars)?;
let lb = reify_int_linear(&b, vars)?;
return Some(la.sub(&lb));
}
"mul" => {
let la = reify_int_linear(&a, vars)?;
let lb = reify_int_linear(&b, vars)?;
if la.is_constant() {
return Some(lb.scale(la.constant));
}
if lb.is_constant() {
return Some(la.scale(lb.constant));
}
return None; }
_ => return None,
}
}
None
}
pub fn extract_comparison(term: &Term) -> Option<(String, Term, Term)> {
if let Some((rel, lhs, rhs)) = extract_binary_app(term) {
match rel.as_str() {
"Lt" | "Le" | "Gt" | "Ge" | "lt" | "le" | "gt" | "ge" => {
return Some((rel, lhs, rhs));
}
_ => {}
}
}
None
}
pub fn goal_to_negated_constraint(rel: &str, lhs: &IntExpr, rhs: &IntExpr) -> Option<IntConstraint> {
let diff = lhs.sub(rhs);
let one = IntExpr::constant(1);
match rel {
"Lt" | "lt" => Some(IntConstraint {
expr: rhs.sub(lhs),
strict: false,
}),
"Le" | "le" => Some(IntConstraint {
expr: rhs.sub(lhs).add(&one),
strict: false,
}),
"Gt" | "gt" => Some(IntConstraint {
expr: diff,
strict: false,
}),
"Ge" | "ge" => Some(IntConstraint {
expr: diff.add(&one),
strict: false,
}),
_ => None,
}
}
pub fn omega_unsat(constraints: &[IntConstraint]) -> bool {
if constraints.is_empty() {
return false;
}
let mut current: Vec<IntConstraint> = constraints.to_vec();
for c in &mut current {
c.normalize();
}
for c in ¤t {
if c.expr.is_constant() && !c.is_satisfied_constant() {
return true;
}
}
let vars: Vec<i64> = current
.iter()
.flat_map(|c| c.expr.coeffs.keys().copied())
.collect::<HashSet<_>>()
.into_iter()
.collect();
for var in vars {
current = eliminate_variable_int(¤t, var);
for c in ¤t {
if c.expr.is_constant() && !c.is_satisfied_constant() {
return true;
}
}
}
current
.iter()
.any(|c| c.expr.is_constant() && !c.is_satisfied_constant())
}
fn eliminate_variable_int(constraints: &[IntConstraint], var: i64) -> Vec<IntConstraint> {
let mut lower: Vec<(IntExpr, BigInt)> = vec![]; let mut upper: Vec<(IntExpr, BigInt)> = vec![]; let mut independent: Vec<IntConstraint> = vec![];
for c in constraints {
let coeff = c.expr.get_coeff(var);
if coeff.is_zero() {
independent.push(c.clone());
} else {
let mut rest = c.expr.clone();
rest.coeffs.remove(&var);
if coeff.is_positive() {
upper.push((rest, coeff));
} else {
lower.push((rest, coeff.negated()));
}
}
}
for (lo_rest, lo_coeff) in &lower {
for (hi_rest, hi_coeff) in &upper {
let new_expr = lo_rest
.scale(hi_coeff.clone())
.add(&hi_rest.scale(lo_coeff.clone()));
let mut new_constraint = IntConstraint {
expr: new_expr,
strict: false,
};
new_constraint.normalize();
independent.push(new_constraint);
}
}
independent
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_int_expr_add() {
let x = IntExpr::var(0);
let y = IntExpr::var(1);
let sum = x.add(&y);
assert!(!sum.is_constant());
assert_eq!(sum.get_coeff(0), BigInt::from_i64(1));
assert_eq!(sum.get_coeff(1), BigInt::from_i64(1));
}
#[test]
fn test_int_expr_cancel() {
let x = IntExpr::var(0);
let neg_x = x.neg();
let zero = x.add(&neg_x);
assert!(zero.is_constant());
assert!(zero.constant.is_zero());
}
#[test]
fn test_constraint_satisfied() {
let c1 = IntConstraint {
expr: IntExpr::constant(-1),
strict: false,
};
assert!(c1.is_satisfied_constant());
let c2 = IntConstraint {
expr: IntExpr::constant(1),
strict: false,
};
assert!(!c2.is_satisfied_constant());
let c3 = IntConstraint {
expr: IntExpr::constant(0),
strict: false,
};
assert!(c3.is_satisfied_constant());
}
#[test]
fn test_omega_constant() {
let constraints = vec![IntConstraint {
expr: IntExpr::constant(1),
strict: false,
}];
assert!(omega_unsat(&constraints));
let constraints2 = vec![IntConstraint {
expr: IntExpr::constant(-1),
strict: false,
}];
assert!(!omega_unsat(&constraints2));
}
#[test]
fn test_x_lt_x_plus_1() {
let constraint = IntConstraint {
expr: IntExpr::constant(1),
strict: false,
};
assert!(omega_unsat(&[constraint]));
}
}