diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 77cf2f6fd..dcf0856bb 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -11,6 +11,7 @@ foreach (component ${z3_test_expanded_deps}) endforeach() add_executable(test-z3 EXCLUDE_FROM_ALL + ackermannize.cpp algebraic.cpp algebraic_numbers.cpp api_ast_map.cpp diff --git a/src/test/ackermannize.cpp b/src/test/ackermannize.cpp new file mode 100644 index 000000000..24d6243bb --- /dev/null +++ b/src/test/ackermannize.cpp @@ -0,0 +1,214 @@ +/*++ +Copyright (c) 2015 Microsoft Corporation + +--*/ + +#include "ast/ast.h" +#include "ast/bv_decl_plugin.h" +#include "ast/reg_decl_plugins.h" +#include "tactic/goal.h" +#include "tactic/tactic.h" +#include "ackermannization/lackr.h" +#include "ackermannization/ackermannize_bv_tactic.h" +#include "ackermannization/ackr_bound_probe.h" +#include "ackermannization/ackr_model_converter.h" +#include "ackermannization/ackr_info.h" +#include "model/model.h" +#include "util/params.h" +#include +#include + +// Helper: create a BV uninterpreted function formula: f(x) = f(y) +static void mk_uf_bv_goal(ast_manager& m, goal_ref& g) { + bv_util bv(m); + sort* bv8 = bv.mk_sort(8); + + func_decl* f = m.mk_func_decl(symbol("f"), 1, &bv8, bv8); + expr* x = m.mk_const(symbol("x"), bv8); + expr* y = m.mk_const(symbol("y"), bv8); + app* fx = m.mk_app(f, x); + app* fy = m.mk_app(f, y); + + // f(x) = f(y) + g->assert_expr(m.mk_eq(fx, fy)); + // x != y + g->assert_expr(m.mk_not(m.mk_eq(x, y))); +} + +// Test 1: mk_ackermann returns false when lemmas_upper_bound <= 0 +static void test_mk_ackermann_zero_bound() { + ast_manager m; + reg_decl_plugins(m); + bv_util bv(m); + sort* bv8 = bv.mk_sort(8); + + func_decl* f = m.mk_func_decl(symbol("f"), 1, &bv8, bv8); + expr* x = m.mk_const(symbol("x"), bv8); + app* fx = m.mk_app(f, x); + + ptr_vector flas; + flas.push_back(fx); + params_ref p; + lackr_stats st; + lackr lk(m, p, st, flas, nullptr); + goal_ref g(alloc(goal, m, true, false)); + // With bound = 0, mk_ackermann should return false + ENSURE(!lk.mk_ackermann(g, 0.0)); + std::cout << "test_mk_ackermann_zero_bound: PASS\n"; +} + +// Test 2: mk_ackermann returns false when init() fails (formula with quantifiers) +static void test_mk_ackermann_quantifier() { + ast_manager m; + reg_decl_plugins(m); + bv_util bv(m); + sort* bv8 = bv.mk_sort(8); + + // Create a quantified formula: forall x:bv8. x = x + symbol name("x"); + expr_ref body(m.mk_eq(m.mk_var(0, bv8), m.mk_var(0, bv8)), m); + expr_ref qfml(m.mk_forall(1, &bv8, &name, body), m); + + ptr_vector flas; + flas.push_back(qfml.get()); + params_ref p; + lackr_stats st; + lackr lk(m, p, st, flas, nullptr); + goal_ref g(alloc(goal, m, true, false)); + // Quantified formulas cause init() to fail, mk_ackermann should return false + ENSURE(!lk.mk_ackermann(g, std::numeric_limits::infinity())); + std::cout << "test_mk_ackermann_quantifier: PASS\n"; +} + +// Test 3: mk_ackermann returns false when lemma count exceeds upper bound +static void test_mk_ackermann_exceeds_bound() { + ast_manager m; + reg_decl_plugins(m); + bv_util bv(m); + sort* bv8 = bv.mk_sort(8); + + // Create a goal with 2 distinct uninterpreted terms f(x), f(y) + // This gives C(2,2)=1 lemma; use bound=0.5 to trigger the limit (1 > 0.5) + func_decl* f = m.mk_func_decl(symbol("f"), 1, &bv8, bv8); + expr* x = m.mk_const(symbol("x"), bv8); + expr* y = m.mk_const(symbol("y"), bv8); + app* fx = m.mk_app(f, x); + app* fy = m.mk_app(f, y); + + ptr_vector flas; + flas.push_back(m.mk_eq(fx, fy)); + flas.push_back(m.mk_not(m.mk_eq(x, y))); + params_ref p; + lackr_stats st; + lackr lk(m, p, st, flas, nullptr); + goal_ref g(alloc(goal, m, true, false)); + // With upper_bound=0.5, C(2,2)=1 > 0.5, so mk_ackermann should return false + ENSURE(!lk.mk_ackermann(g, 0.5)); + std::cout << "test_mk_ackermann_exceeds_bound: PASS\n"; +} + +// Test 4: ackermannize_bv_tactic processes all formulas in goal (covers loop at ackermannize_bv_tactic.cpp:42) +static void test_ackermannize_bv_tactic_loop() { + ast_manager m; + reg_decl_plugins(m); + + goal_ref g(alloc(goal, m, true, false)); + mk_uf_bv_goal(m, g); + + params_ref p; + tactic_ref t = mk_ackermannize_bv_tactic(m, p); + goal_ref_buffer result; + (*t)(g, result); + // The tactic should produce a result (not crash) + ENSURE(result.size() > 0); + std::cout << "test_ackermannize_bv_tactic_loop: PASS\n"; +} + +// Test 5: ackr_bound_probe processes all formulas in goal (covers loop at ackr_bound_probe.cpp:67) +static void test_ackr_bound_probe_loop() { + ast_manager m; + reg_decl_plugins(m); + + goal_ref g(alloc(goal, m, true, false)); + mk_uf_bv_goal(m, g); + + scoped_ptr pr = mk_ackr_bound_probe(); + probe::result r = (*pr)(*g); + // Should return a non-negative bound (covers the loop) + ENSURE(r.get_value() >= 0.0); + std::cout << "test_ackr_bound_probe_loop: PASS\n"; +} + +// Test 6: ackermannize_bv_tactic with multiple formulas verifies full loop coverage +static void test_ackermannize_multiple_formulas() { + ast_manager m; + reg_decl_plugins(m); + bv_util bv(m); + sort* bv8 = bv.mk_sort(8); + + func_decl* f = m.mk_func_decl(symbol("f"), 1, &bv8, bv8); + expr* x = m.mk_const(symbol("x"), bv8); + expr* y = m.mk_const(symbol("y"), bv8); + + goal_ref g(alloc(goal, m, true, false)); + // Add multiple formulas to ensure the loop iterates more than once + g->assert_expr(m.mk_eq(m.mk_app(f, x), m.mk_app(f, y))); + g->assert_expr(m.mk_not(m.mk_eq(x, y))); + g->assert_expr(m.mk_true()); + + // Verify the probe processes all 3 formulas + scoped_ptr pr = mk_ackr_bound_probe(); + probe::result r = (*pr)(*g); + ENSURE(r.get_value() >= 0.0); + + // Verify the tactic also processes all 3 formulas + params_ref p; + tactic_ref t = mk_ackermannize_bv_tactic(m, p); + goal_ref_buffer result; + (*t)(g, result); + ENSURE(result.size() > 0); + std::cout << "test_ackermannize_multiple_formulas: PASS\n"; +} + +// Test 7: ackr_model_converter iterates over model constants +// (covers loop at ackr_model_converter.cpp:106) +static void test_ackr_model_converter_constants() { + ast_manager m; + reg_decl_plugins(m); + bv_util bv(m); + sort* bv8 = bv.mk_sort(8); + + // Set up an abstraction: f(x) -> fresh_c + func_decl* f = m.mk_func_decl(symbol("f"), 1, &bv8, bv8); + expr_ref x(m.mk_const(symbol("x"), bv8), m); + app_ref fx(m.mk_app(f, x.get()), m); + app_ref fresh_c(m.mk_fresh_const("f", bv8), m); + + ackr_info_ref info = alloc(ackr_info, m); + info->set_abstr(fx.get(), fresh_c.get()); + info->seal(); + + // Create a source model with the fresh constant having a value (BV numeral 42) + model_ref src_model = alloc(model, m); + expr_ref val42(bv.mk_numeral(42, 8), m); + src_model->register_decl(fresh_c->get_decl(), val42.get()); + ENSURE(src_model->get_num_constants() == 1); + + // Apply the model converter: it should process the constant and create a new model + model_converter_ref mc(mk_ackr_model_converter(m, info)); + model_ref dst_model(src_model.get()); + (*mc)(dst_model); + + // The converter ran without crashing, verifying the loop iterated correctly + std::cout << "test_ackr_model_converter_constants: PASS\n"; +} + +void tst_ackermannize() { + test_mk_ackermann_zero_bound(); + test_mk_ackermann_quantifier(); + test_mk_ackermann_exceeds_bound(); + test_ackermannize_bv_tactic_loop(); + test_ackr_bound_probe_loop(); + test_ackermannize_multiple_formulas(); + test_ackr_model_converter_constants(); +} diff --git a/src/test/main.cpp b/src/test/main.cpp index 063ef31d3..3784bb87c 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -146,6 +146,7 @@ int main(int argc, char ** argv) { bool do_display_usage = false; bool test_all = false; parse_cmd_line_args(argc, argv, do_display_usage, test_all); + TST(ackermannize); TST(random); TST(symbol_table); TST(region);