use std::collections::{BTreeMap, HashSet};
use crate::term::{Literal, Term};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntExpr {
pub constant: i64,
pub coeffs: BTreeMap<i64, i64>,
}
impl IntExpr {
pub fn constant(c: i64) -> Self {
IntExpr {
constant: c,
coeffs: BTreeMap::new(),
}
}
pub fn var(idx: i64) -> Self {
let mut coeffs = BTreeMap::new();
coeffs.insert(idx, 1);
IntExpr {
constant: 0,
coeffs,
}
}
pub fn add(&self, other: &Self) -> Self {
let mut result = self.clone();
result.constant += other.constant;
for (&v, &c) in &other.coeffs {
let entry = result.coeffs.entry(v).or_insert(0);
*entry += c;
if *entry == 0 {
result.coeffs.remove(&v);
}
}
result
}
pub fn neg(&self) -> Self {
IntExpr {
constant: -self.constant,
coeffs: self.coeffs.iter().map(|(&v, &c)| (v, -c)).collect(),
}
}
pub fn sub(&self, other: &Self) -> Self {
self.add(&other.neg())
}
pub fn scale(&self, k: i64) -> Self {
if k == 0 {
return IntExpr::constant(0);
}
IntExpr {
constant: self.constant * k,
coeffs: self
.coeffs
.iter()
.map(|(&v, &c)| (v, c * k))
.filter(|(_, c)| *c != 0)
.collect(),
}
}
pub fn is_constant(&self) -> bool {
self.coeffs.is_empty()
}
pub fn get_coeff(&self, var: i64) -> i64 {
self.coeffs.get(&var).copied().unwrap_or(0)
}
}
#[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 < 0 } else {
c <= 0 }
}
pub fn normalize(&mut self) {
let g = self
.expr
.coeffs
.values()
.chain(std::iter::once(&self.expr.constant))
.filter(|&&x| x != 0)
.fold(0i64, |a, &b| gcd(a.abs(), b.abs()));
if g > 1 {
self.expr.constant /= g;
for v in self.expr.coeffs.values_mut() {
*v /= g;
}
}
}
}
fn gcd(a: i64, b: i64) -> i64 {
if b == 0 {
a.max(1)
} else {
gcd(b, a % b)
}
}
pub fn reify_int_linear(term: &Term) -> 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) {
let hash = name_to_var_index(&name);
return Some(IntExpr::var(hash));
}
if let Some((op, a, b)) = extract_binary_app(term) {
match op.as_str() {
"add" => {
let la = reify_int_linear(&a)?;
let lb = reify_int_linear(&b)?;
return Some(la.add(&lb));
}
"sub" => {
let la = reify_int_linear(&a)?;
let lb = reify_int_linear(&b)?;
return Some(la.sub(&lb));
}
"mul" => {
let la = reify_int_linear(&a)?;
let lb = reify_int_linear(&b)?;
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);
match rel {
"Lt" | "lt" => Some(IntConstraint {
expr: rhs.sub(lhs),
strict: false,
}),
"Le" | "le" => {
let mut expr = rhs.sub(lhs);
expr.constant += 1; Some(IntConstraint {
expr,
strict: false,
})
}
"Gt" | "gt" => Some(IntConstraint {
expr: diff,
strict: false,
}),
"Ge" | "ge" => {
let mut expr = diff;
expr.constant += 1; Some(IntConstraint {
expr,
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, i64)> = vec![]; let mut upper: Vec<(IntExpr, i64)> = vec![]; let mut independent: Vec<IntConstraint> = vec![];
for c in constraints {
let coeff = c.expr.get_coeff(var);
if coeff == 0 {
independent.push(c.clone());
} else {
let mut rest = c.expr.clone();
rest.coeffs.remove(&var);
if coeff > 0 {
upper.push((rest, coeff));
} else {
lower.push((rest, -coeff));
}
}
}
for (lo_rest, lo_coeff) in &lower {
for (hi_rest, hi_coeff) in &upper {
let new_expr = lo_rest.scale(*hi_coeff).add(&hi_rest.scale(*lo_coeff));
let mut new_constraint = IntConstraint {
expr: new_expr,
strict: false,
};
new_constraint.normalize();
independent.push(new_constraint);
}
}
independent
}
fn extract_slit(term: &Term) -> Option<i64> {
if let Term::App(ctor, arg) = term {
if let Term::Global(name) = ctor.as_ref() {
if name == "SLit" {
if let Term::Lit(Literal::Int(n)) = arg.as_ref() {
return Some(*n);
}
}
}
}
None
}
fn extract_svar(term: &Term) -> Option<i64> {
if let Term::App(ctor, arg) = term {
if let Term::Global(name) = ctor.as_ref() {
if name == "SVar" {
if let Term::Lit(Literal::Int(i)) = arg.as_ref() {
return Some(*i);
}
}
}
}
None
}
fn extract_sname(term: &Term) -> Option<String> {
if let Term::App(ctor, arg) = term {
if let Term::Global(name) = ctor.as_ref() {
if name == "SName" {
if let Term::Lit(Literal::Text(s)) = arg.as_ref() {
return Some(s.clone());
}
}
}
}
None
}
fn extract_binary_app(term: &Term) -> Option<(String, Term, Term)> {
if let Term::App(outer, b) = term {
if let Term::App(sapp_outer, inner) = outer.as_ref() {
if let Term::Global(ctor) = sapp_outer.as_ref() {
if ctor == "SApp" {
if let Term::App(partial, a) = inner.as_ref() {
if let Term::App(sapp_inner, op_term) = partial.as_ref() {
if let Term::Global(ctor2) = sapp_inner.as_ref() {
if ctor2 == "SApp" {
if let Some(op) = extract_sname(op_term) {
return Some((
op,
a.as_ref().clone(),
b.as_ref().clone(),
));
}
}
}
}
}
}
}
}
}
None
}
fn name_to_var_index(name: &str) -> i64 {
let hash: i64 = name
.bytes()
.fold(0i64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as i64));
-(hash.abs() + 1_000_000)
}
#[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), 1);
assert_eq!(sum.get_coeff(1), 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_eq!(zero.constant, 0);
}
#[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]));
}
}