use alloc::vec::Vec;
use puremp::Rational;
use crate::ast::arith::ArithOp;
use crate::ast::manager::AstManager;
use crate::ast::{AstId, DeclKind};
use crate::util::symbol::Symbol;
pub(crate) fn try_fold(m: &mut AstManager, decl: AstId, args: &[AstId]) -> Option<AstId> {
let afid = m.get_family_id(Symbol::new("arith"))?;
let d = m.func_decl(decl).expect("app decl");
if d.info.family_id != afid {
return None;
}
let kind = d.info.decl_kind;
if args.is_empty() {
return None;
}
let is_int = m.is_int_sort(m.get_sort(args[0]));
if kind == ArithOp::Add as DeclKind && args.iter().any(|&a| m.as_numeral(a).is_none()) {
return collect_sum(m, args, is_int);
}
let mut nums: Vec<Rational> = Vec::with_capacity(args.len());
for &a in args {
nums.push(m.as_numeral(a)?);
}
if kind == ArithOp::Add as DeclKind {
let mut acc = nums[0].clone();
for n in &nums[1..] {
acc = &acc + n;
}
Some(m.mk_numeral(acc, is_int))
} else if kind == ArithOp::Mul as DeclKind {
let mut acc = nums[0].clone();
for n in &nums[1..] {
acc = &acc * n;
}
Some(m.mk_numeral(acc, is_int))
} else if kind == ArithOp::Sub as DeclKind {
let mut acc = nums[0].clone();
for n in &nums[1..] {
acc = &acc - n;
}
Some(m.mk_numeral(acc, is_int))
} else if kind == ArithOp::Uminus as DeclKind {
Some(m.mk_numeral(-&nums[0], is_int))
} else if kind == ArithOp::Le as DeclKind {
Some(bool_of(m, nums[0] <= nums[1]))
} else if kind == ArithOp::Lt as DeclKind {
Some(bool_of(m, nums[0] < nums[1]))
} else if kind == ArithOp::Ge as DeclKind {
Some(bool_of(m, nums[0] >= nums[1]))
} else if kind == ArithOp::Gt as DeclKind {
Some(bool_of(m, nums[0] > nums[1]))
} else {
None
}
}
fn bool_of(m: &mut AstManager, b: bool) -> AstId {
if b { m.mk_true() } else { m.mk_false() }
}
fn addend(m: &AstManager, a: AstId) -> (Option<AstId>, Rational) {
if let Some(n) = m.as_numeral(a) {
return (None, n);
}
if let Some(app) = m.app(a) {
let kind = m
.func_decl(app.decl)
.map(|d| (d.info.family_id, d.info.decl_kind));
let afid = m.get_family_id(Symbol::new("arith"));
if let (Some(fid), Some(afid)) = (kind, afid)
&& fid.0 == afid
{
if fid.1 == ArithOp::Uminus as DeclKind && app.args.len() == 1 {
let (b, c) = addend(m, app.args[0]);
return (b, -&c);
}
if fid.1 == ArithOp::Mul as DeclKind && app.args.len() == 2 {
if let Some(c) = m.as_numeral(app.args[0]) {
return (Some(app.args[1]), c);
}
if let Some(c) = m.as_numeral(app.args[1]) {
return (Some(app.args[0]), c);
}
}
}
}
(Some(a), Rational::from_integer(puremp::Int::from(1)))
}
fn collect_sum(m: &mut AstManager, args: &[AstId], is_int: bool) -> Option<AstId> {
let mut coeffs: Vec<(AstId, Rational)> = Vec::new();
let mut constant = Rational::from_integer(puremp::Int::from(0));
for &a in args {
let (base, c) = addend(m, a);
match base {
None => constant = &constant + &c,
Some(t) => {
if let Some(e) = coeffs.iter_mut().find(|(u, _)| *u == t) {
e.1 = &e.1 + &c;
} else {
coeffs.push((t, c));
}
}
}
}
let zero = Rational::from_integer(puremp::Int::from(0));
let one = Rational::from_integer(puremp::Int::from(1));
let mut parts: Vec<AstId> = Vec::new();
for (t, c) in &coeffs {
if *c == zero {
continue; } else if *c == one {
parts.push(*t);
} else {
let k = m.mk_numeral(c.clone(), is_int);
parts.push(m.mk_mul(&[k, *t]));
}
}
if constant != zero || parts.is_empty() {
parts.push(m.mk_numeral(constant, is_int));
}
Some(if parts.len() == 1 {
parts[0]
} else {
m.mk_add(&parts)
})
}
#[cfg(test)]
mod tests {
use crate::ast::manager::AstManager;
use crate::rewriter::simplify;
#[test]
fn folds_arithmetic_constants() {
let mut m = AstManager::new();
let two = m.mk_int(2);
let three = m.mk_int(3);
let one = m.mk_int(1);
let prod = m.mk_mul(&[two, three]);
let sum = m.mk_add(&[prod, one]);
let seven = m.mk_int(7);
assert_eq!(simplify(&mut m, sum), seven);
}
#[test]
fn folds_comparisons_to_booleans() {
let mut m = AstManager::new();
let two = m.mk_int(2);
let three = m.mk_int(3);
let le = m.mk_le(two, three);
let gt = m.mk_gt(two, three);
assert_eq!(simplify(&mut m, le), m.mk_true());
assert_eq!(simplify(&mut m, gt), m.mk_false());
}
#[test]
fn collects_like_terms_in_sums() {
let mut m = AstManager::new();
let x = m.mk_int_const("x");
let three = m.mk_int(3);
let neg_x = m.mk_uminus(x);
let sum = m.mk_add(&[x, neg_x, three]);
assert_eq!(simplify(&mut m, sum), three);
let two = m.mk_int(2);
let three2 = m.mk_int(3);
let t1 = m.mk_mul(&[two, x]);
let t2 = m.mk_mul(&[three2, x]);
let sum2 = m.mk_add(&[t1, t2]);
let five = m.mk_int(5);
let expect = m.mk_mul(&[five, x]);
assert_eq!(simplify(&mut m, sum2), expect);
}
#[test]
fn leaves_symbolic_arithmetic_alone() {
let mut m = AstManager::new();
let x = m.mk_int_const("x");
let one = m.mk_int(1);
let sum = m.mk_add(&[x, one]);
assert_eq!(simplify(&mut m, sum), sum);
}
#[test]
fn folds_inside_a_larger_term() {
let mut m = AstManager::new();
let x = m.mk_int_const("x");
let two = m.mk_int(2);
let three = m.mk_int(3);
let five = m.mk_int(5);
let sum = m.mk_add(&[two, three]);
let le = m.mk_le(x, sum);
let expected = m.mk_le(x, five);
assert_eq!(simplify(&mut m, le), expected);
}
}