mirror of
https://github.com/Z3Prover/z3
synced 2025-11-21 21:26:40 +00:00
Add comprehensive algebraic rewrite rules to finite_set_rewriter (#7975)
* Initial plan * Add comprehensive rewrite rules for finite set operations Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com> * Add comprehensive unit tests for finite set rewrite rules Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com> * Fix order of checks in mk_in to handle singleton same element case first Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com> Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
parent
4225f1a0f1
commit
2e7f80f597
2 changed files with 309 additions and 16 deletions
|
|
@ -31,55 +31,169 @@ br_status finite_set_rewriter::mk_app_core(func_decl * f, unsigned num_args, exp
|
|||
case OP_FINITE_SET_SUBSET:
|
||||
SASSERT(num_args == 2);
|
||||
return mk_subset(args[0], args[1], result);
|
||||
case OP_FINITE_SET_SINGLETON:
|
||||
SASSERT(num_args == 1);
|
||||
return mk_singleton(args[0], result);
|
||||
case OP_FINITE_SET_IN:
|
||||
SASSERT(num_args == 2);
|
||||
return mk_in(args[0], args[1], result);
|
||||
default:
|
||||
return BR_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
br_status finite_set_rewriter::mk_union(unsigned num_args, expr * const * args, expr_ref & result) {
|
||||
// Handle binary case - check if both arguments are the same
|
||||
// set.union(x, x) -> x
|
||||
// Idempotency: set.union(x, x) -> x
|
||||
if (num_args == 2 && args[0] == args[1]) {
|
||||
result = args[0];
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// Additional simplifications can be added here
|
||||
// For example: set.union(x, empty) -> x
|
||||
// But for now, we keep it minimal as per requirements
|
||||
// Identity: set.union(x, empty) -> x or set.union(empty, x) -> x
|
||||
if (num_args == 2) {
|
||||
if (m_util.is_empty(args[0])) {
|
||||
result = args[1];
|
||||
return BR_DONE;
|
||||
}
|
||||
if (m_util.is_empty(args[1])) {
|
||||
result = args[0];
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// Absorption: set.union(x, set.intersect(x, y)) -> x
|
||||
expr* a1, *a2;
|
||||
if (m_util.is_intersect(args[1], a1, a2)) {
|
||||
if (args[0] == a1 || args[0] == a2) {
|
||||
result = args[0];
|
||||
return BR_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Absorption: set.union(set.intersect(x, y), x) -> x
|
||||
if (m_util.is_intersect(args[0], a1, a2)) {
|
||||
if (args[1] == a1 || args[1] == a2) {
|
||||
result = args[1];
|
||||
return BR_DONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BR_FAILED;
|
||||
}
|
||||
|
||||
br_status finite_set_rewriter::mk_intersect(unsigned num_args, expr * const * args, expr_ref & result) {
|
||||
// set.intersect(x, x) -> x
|
||||
// Idempotency: set.intersect(x, x) -> x
|
||||
if (num_args == 2 && args[0] == args[1]) {
|
||||
result = args[0];
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// Annihilation: set.intersect(x, empty) -> empty or set.intersect(empty, x) -> empty
|
||||
if (num_args == 2) {
|
||||
if (m_util.is_empty(args[0])) {
|
||||
result = args[0];
|
||||
return BR_DONE;
|
||||
}
|
||||
if (m_util.is_empty(args[1])) {
|
||||
result = args[1];
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// Absorption: set.intersect(x, set.union(x, y)) -> x
|
||||
expr* a1, *a2;
|
||||
if (m_util.is_union(args[1], a1, a2)) {
|
||||
if (args[0] == a1 || args[0] == a2) {
|
||||
result = args[0];
|
||||
return BR_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Absorption: set.intersect(set.union(x, y), x) -> x
|
||||
if (m_util.is_union(args[0], a1, a2)) {
|
||||
if (args[1] == a1 || args[1] == a2) {
|
||||
result = args[1];
|
||||
return BR_DONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BR_FAILED;
|
||||
}
|
||||
|
||||
br_status finite_set_rewriter::mk_difference(expr * arg1, expr * arg2, expr_ref & result) {
|
||||
// set.difference(x, x) -> set.empty
|
||||
if (arg1 == arg2) {
|
||||
// Get the set sort directly from the argument
|
||||
sort* set_sort = arg1->get_sort();
|
||||
SASSERT(m_util.is_finite_set(set_sort));
|
||||
|
||||
// Call mk_empty with set_sort directly as suggested
|
||||
result = m_util.mk_empty(set_sort);
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// Identity: set.difference(x, empty) -> x
|
||||
if (m_util.is_empty(arg2)) {
|
||||
result = arg1;
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// Annihilation: set.difference(empty, x) -> empty
|
||||
if (m_util.is_empty(arg1)) {
|
||||
result = arg1;
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
return BR_FAILED;
|
||||
}
|
||||
|
||||
br_status finite_set_rewriter::mk_subset(expr * arg1, expr * arg2, expr_ref & result) {
|
||||
// set.subset(x, y) -> set.intersect(x, y) = x
|
||||
// set.subset(x, x) -> true
|
||||
if (arg1 == arg2) {
|
||||
result = m().mk_true();
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// set.subset(empty, x) -> true
|
||||
if (m_util.is_empty(arg1)) {
|
||||
result = m().mk_true();
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// set.subset(x, empty) -> x = empty
|
||||
if (m_util.is_empty(arg2)) {
|
||||
result = m().mk_eq(arg1, arg2);
|
||||
return BR_REWRITE1;
|
||||
}
|
||||
|
||||
// General case: set.subset(x, y) -> set.intersect(x, y) = x
|
||||
expr_ref intersect(m());
|
||||
intersect = m_util.mk_intersect(arg1, arg2);
|
||||
result = m().mk_eq(intersect, arg1);
|
||||
return BR_REWRITE3;
|
||||
}
|
||||
|
||||
br_status finite_set_rewriter::mk_singleton(expr * arg, expr_ref & result) {
|
||||
// Singleton is already in normal form, no simplifications
|
||||
return BR_FAILED;
|
||||
}
|
||||
|
||||
br_status finite_set_rewriter::mk_in(expr * elem, expr * set, expr_ref & result) {
|
||||
// set.in(x, empty) -> false
|
||||
if (m_util.is_empty(set)) {
|
||||
result = m().mk_false();
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
// set.in(x, singleton(y)) checks
|
||||
expr* singleton_elem;
|
||||
if (m_util.is_singleton(set, singleton_elem)) {
|
||||
// set.in(x, singleton(x)) -> true (when x is the same)
|
||||
if (elem == singleton_elem) {
|
||||
result = m().mk_true();
|
||||
return BR_DONE;
|
||||
}
|
||||
// set.in(x, singleton(y)) -> x = y (when x != y)
|
||||
result = m().mk_eq(elem, singleton_elem);
|
||||
return BR_REWRITE1;
|
||||
}
|
||||
|
||||
return BR_FAILED;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ static void test_union_idempotent() {
|
|||
app_ref s1(fsets.mk_range(zero, ten), m);
|
||||
|
||||
// Test set.union(s1, s1) -> s1
|
||||
expr* args[2] = { s1, s1 };
|
||||
app_ref union_app(fsets.mk_union(s1, s1), m);
|
||||
expr_ref result(m);
|
||||
br_status st = rw.mk_union(2, args, result);
|
||||
br_status st = rw.mk_app_core(union_app->get_decl(), union_app->get_num_args(), union_app->get_args(), result);
|
||||
|
||||
ENSURE(st == BR_DONE);
|
||||
ENSURE(result == s1);
|
||||
|
|
@ -59,9 +59,9 @@ static void test_intersect_idempotent() {
|
|||
app_ref s1(fsets.mk_range(zero, ten), m);
|
||||
|
||||
// Test set.intersect(s1, s1) -> s1
|
||||
expr* args[2] = { s1, s1 };
|
||||
app_ref intersect_app(fsets.mk_intersect(s1, s1), m);
|
||||
expr_ref result(m);
|
||||
br_status st = rw.mk_intersect(2, args, result);
|
||||
br_status st = rw.mk_app_core(intersect_app->get_decl(), intersect_app->get_num_args(), intersect_app->get_args(), result);
|
||||
|
||||
ENSURE(st == BR_DONE);
|
||||
ENSURE(result == s1);
|
||||
|
|
@ -82,8 +82,9 @@ static void test_difference_same() {
|
|||
app_ref s1(fsets.mk_range(zero, ten), m);
|
||||
|
||||
// Test set.difference(s1, s1) -> empty
|
||||
app_ref diff_app(fsets.mk_difference(s1, s1), m);
|
||||
expr_ref result(m);
|
||||
br_status st = rw.mk_difference(s1, s1, result);
|
||||
br_status st = rw.mk_app_core(diff_app->get_decl(), diff_app->get_num_args(), diff_app->get_args(), result);
|
||||
|
||||
ENSURE(st == BR_DONE);
|
||||
ENSURE(fsets.is_empty(result));
|
||||
|
|
@ -106,8 +107,9 @@ static void test_subset_rewrite() {
|
|||
app_ref s2(fsets.mk_range(zero, twenty), m);
|
||||
|
||||
// Test set.subset(s1, s2) -> set.intersect(s1, s2) = s1
|
||||
app_ref subset_app(fsets.mk_subset(s1, s2), m);
|
||||
expr_ref result(m);
|
||||
br_status st = rw.mk_subset(s1, s2, result);
|
||||
br_status st = rw.mk_app_core(subset_app->get_decl(), subset_app->get_num_args(), subset_app->get_args(), result);
|
||||
|
||||
ENSURE(st == BR_REWRITE3);
|
||||
ENSURE(m.is_eq(result));
|
||||
|
|
@ -148,10 +150,187 @@ static void test_mk_app_core() {
|
|||
ENSURE(result == s1);
|
||||
}
|
||||
|
||||
static void test_union_with_empty() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
|
||||
finite_set_util fsets(m);
|
||||
finite_set_rewriter rw(m);
|
||||
arith_util arith(m);
|
||||
|
||||
// Create a set and empty set
|
||||
sort_ref int_sort(arith.mk_int(), m);
|
||||
expr_ref zero(arith.mk_int(0), m);
|
||||
expr_ref ten(arith.mk_int(10), m);
|
||||
app_ref s1(fsets.mk_range(zero, ten), m);
|
||||
app_ref empty_set(fsets.mk_empty(s1->get_sort()), m);
|
||||
|
||||
// Test set.union(s1, empty) -> s1
|
||||
app_ref union_app1(fsets.mk_union(s1, empty_set), m);
|
||||
expr_ref result1(m);
|
||||
br_status st1 = rw.mk_app_core(union_app1->get_decl(), union_app1->get_num_args(), union_app1->get_args(), result1);
|
||||
ENSURE(st1 == BR_DONE);
|
||||
ENSURE(result1 == s1);
|
||||
|
||||
// Test set.union(empty, s1) -> s1
|
||||
app_ref union_app2(fsets.mk_union(empty_set, s1), m);
|
||||
expr_ref result2(m);
|
||||
br_status st2 = rw.mk_app_core(union_app2->get_decl(), union_app2->get_num_args(), union_app2->get_args(), result2);
|
||||
ENSURE(st2 == BR_DONE);
|
||||
ENSURE(result2 == s1);
|
||||
}
|
||||
|
||||
static void test_intersect_with_empty() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
|
||||
finite_set_util fsets(m);
|
||||
finite_set_rewriter rw(m);
|
||||
arith_util arith(m);
|
||||
|
||||
// Create a set and empty set
|
||||
sort_ref int_sort(arith.mk_int(), m);
|
||||
expr_ref zero(arith.mk_int(0), m);
|
||||
expr_ref ten(arith.mk_int(10), m);
|
||||
app_ref s1(fsets.mk_range(zero, ten), m);
|
||||
app_ref empty_set(fsets.mk_empty(s1->get_sort()), m);
|
||||
|
||||
// Test set.intersect(s1, empty) -> empty
|
||||
app_ref intersect_app1(fsets.mk_intersect(s1, empty_set), m);
|
||||
expr_ref result1(m);
|
||||
br_status st1 = rw.mk_app_core(intersect_app1->get_decl(), intersect_app1->get_num_args(), intersect_app1->get_args(), result1);
|
||||
ENSURE(st1 == BR_DONE);
|
||||
ENSURE(result1 == empty_set);
|
||||
|
||||
// Test set.intersect(empty, s1) -> empty
|
||||
app_ref intersect_app2(fsets.mk_intersect(empty_set, s1), m);
|
||||
expr_ref result2(m);
|
||||
br_status st2 = rw.mk_app_core(intersect_app2->get_decl(), intersect_app2->get_num_args(), intersect_app2->get_args(), result2);
|
||||
ENSURE(st2 == BR_DONE);
|
||||
ENSURE(result2 == empty_set);
|
||||
}
|
||||
|
||||
static void test_difference_with_empty() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
|
||||
finite_set_util fsets(m);
|
||||
finite_set_rewriter rw(m);
|
||||
arith_util arith(m);
|
||||
|
||||
// Create a set and empty set
|
||||
sort_ref int_sort(arith.mk_int(), m);
|
||||
expr_ref zero(arith.mk_int(0), m);
|
||||
expr_ref ten(arith.mk_int(10), m);
|
||||
app_ref s1(fsets.mk_range(zero, ten), m);
|
||||
app_ref empty_set(fsets.mk_empty(s1->get_sort()), m);
|
||||
|
||||
// Test set.difference(s1, empty) -> s1
|
||||
app_ref diff_app1(fsets.mk_difference(s1, empty_set), m);
|
||||
expr_ref result1(m);
|
||||
br_status st1 = rw.mk_app_core(diff_app1->get_decl(), diff_app1->get_num_args(), diff_app1->get_args(), result1);
|
||||
ENSURE(st1 == BR_DONE);
|
||||
ENSURE(result1 == s1);
|
||||
|
||||
// Test set.difference(empty, s1) -> empty
|
||||
app_ref diff_app2(fsets.mk_difference(empty_set, s1), m);
|
||||
expr_ref result2(m);
|
||||
br_status st2 = rw.mk_app_core(diff_app2->get_decl(), diff_app2->get_num_args(), diff_app2->get_args(), result2);
|
||||
ENSURE(st2 == BR_DONE);
|
||||
ENSURE(result2 == empty_set);
|
||||
}
|
||||
|
||||
static void test_subset_with_empty() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
|
||||
finite_set_util fsets(m);
|
||||
finite_set_rewriter rw(m);
|
||||
arith_util arith(m);
|
||||
|
||||
// Create a set and empty set
|
||||
sort_ref int_sort(arith.mk_int(), m);
|
||||
expr_ref zero(arith.mk_int(0), m);
|
||||
expr_ref ten(arith.mk_int(10), m);
|
||||
app_ref s1(fsets.mk_range(zero, ten), m);
|
||||
app_ref empty_set(fsets.mk_empty(s1->get_sort()), m);
|
||||
|
||||
// Test set.subset(empty, s1) -> true
|
||||
app_ref subset_app1(fsets.mk_subset(empty_set, s1), m);
|
||||
expr_ref result1(m);
|
||||
br_status st1 = rw.mk_app_core(subset_app1->get_decl(), subset_app1->get_num_args(), subset_app1->get_args(), result1);
|
||||
ENSURE(st1 == BR_DONE);
|
||||
ENSURE(m.is_true(result1));
|
||||
|
||||
// Test set.subset(s1, s1) -> true
|
||||
app_ref subset_app2(fsets.mk_subset(s1, s1), m);
|
||||
expr_ref result2(m);
|
||||
br_status st2 = rw.mk_app_core(subset_app2->get_decl(), subset_app2->get_num_args(), subset_app2->get_args(), result2);
|
||||
ENSURE(st2 == BR_DONE);
|
||||
ENSURE(m.is_true(result2));
|
||||
}
|
||||
|
||||
static void test_in_singleton() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
|
||||
finite_set_util fsets(m);
|
||||
finite_set_rewriter rw(m);
|
||||
arith_util arith(m);
|
||||
|
||||
// Create elements and singleton
|
||||
expr_ref five(arith.mk_int(5), m);
|
||||
expr_ref ten(arith.mk_int(10), m);
|
||||
app_ref singleton_five(fsets.mk_singleton(five), m);
|
||||
|
||||
// Test set.in(five, singleton(five)) -> true
|
||||
app_ref in_app1(fsets.mk_in(five, singleton_five), m);
|
||||
expr_ref result1(m);
|
||||
br_status st1 = rw.mk_app_core(in_app1->get_decl(), in_app1->get_num_args(), in_app1->get_args(), result1);
|
||||
ENSURE(st1 == BR_DONE);
|
||||
ENSURE(m.is_true(result1));
|
||||
|
||||
// Test set.in(ten, singleton(five)) -> ten = five
|
||||
app_ref in_app2(fsets.mk_in(ten, singleton_five), m);
|
||||
expr_ref result2(m);
|
||||
br_status st2 = rw.mk_app_core(in_app2->get_decl(), in_app2->get_num_args(), in_app2->get_args(), result2);
|
||||
ENSURE(st2 == BR_REWRITE1);
|
||||
ENSURE(m.is_eq(result2));
|
||||
}
|
||||
|
||||
static void test_in_empty() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
|
||||
finite_set_util fsets(m);
|
||||
finite_set_rewriter rw(m);
|
||||
arith_util arith(m);
|
||||
|
||||
// Create element and empty set
|
||||
sort_ref int_sort(arith.mk_int(), m);
|
||||
expr_ref five(arith.mk_int(5), m);
|
||||
parameter param(int_sort.get());
|
||||
sort_ref set_sort(m.mk_sort(fsets.get_family_id(), FINITE_SET_SORT, 1, ¶m), m);
|
||||
app_ref empty_set(fsets.mk_empty(set_sort), m);
|
||||
|
||||
// Test set.in(five, empty) -> false
|
||||
app_ref in_app(fsets.mk_in(five, empty_set), m);
|
||||
expr_ref result(m);
|
||||
br_status st = rw.mk_app_core(in_app->get_decl(), in_app->get_num_args(), in_app->get_args(), result);
|
||||
ENSURE(st == BR_DONE);
|
||||
ENSURE(m.is_false(result));
|
||||
}
|
||||
|
||||
void tst_finite_set_rewriter() {
|
||||
test_union_idempotent();
|
||||
test_intersect_idempotent();
|
||||
test_difference_same();
|
||||
test_subset_rewrite();
|
||||
test_mk_app_core();
|
||||
test_union_with_empty();
|
||||
test_intersect_with_empty();
|
||||
test_difference_with_empty();
|
||||
test_subset_with_empty();
|
||||
test_in_singleton();
|
||||
test_in_empty();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue