mirror of
https://github.com/Z3Prover/z3
synced 2026-07-17 04:25:44 +00:00
Merge remote-tracking branch 'origin/master' into c3
This commit is contained in:
commit
706f62286e
199 changed files with 11004 additions and 4584 deletions
|
|
@ -3,7 +3,7 @@ add_subdirectory(lp)
|
|||
################################################################################
|
||||
# z3-test executable
|
||||
################################################################################
|
||||
set(z3_test_deps api fuzzing simplex smt_seq)
|
||||
set(z3_test_deps api fuzzing simplex)
|
||||
z3_expand_dependencies(z3_test_expanded_deps ${z3_test_deps})
|
||||
set (z3_test_extra_object_files "")
|
||||
foreach (component ${z3_test_expanded_deps})
|
||||
|
|
@ -24,6 +24,7 @@ add_executable(test-z3
|
|||
api_datalog.cpp
|
||||
parametric_datatype.cpp
|
||||
arith_rewriter.cpp
|
||||
seq_rewriter.cpp
|
||||
arith_simplifier_plugin.cpp
|
||||
ast.cpp
|
||||
bdd.cpp
|
||||
|
|
@ -52,8 +53,6 @@ add_executable(test-z3
|
|||
escaped.cpp
|
||||
euf_bv_plugin.cpp
|
||||
euf_arith_plugin.cpp
|
||||
euf_sgraph.cpp
|
||||
euf_seq_plugin.cpp
|
||||
ex.cpp
|
||||
expr_rand.cpp
|
||||
expr_substitution.cpp
|
||||
|
|
@ -81,6 +80,7 @@ add_executable(test-z3
|
|||
"${CMAKE_CURRENT_BINARY_DIR}/install_tactic.cpp"
|
||||
interval.cpp
|
||||
karr.cpp
|
||||
lcube.cpp
|
||||
list.cpp
|
||||
main.cpp
|
||||
map.cpp
|
||||
|
|
@ -115,15 +115,19 @@ add_executable(test-z3
|
|||
polynomial_factorization.cpp
|
||||
polynorm.cpp
|
||||
prime_generator.cpp
|
||||
psmt.cpp
|
||||
seq_regex_bisim.cpp
|
||||
proof_checker.cpp
|
||||
qe_arith.cpp
|
||||
mbp_qel.cpp
|
||||
quant_elim.cpp
|
||||
quant_solve.cpp
|
||||
random.cpp
|
||||
range_predicate.cpp
|
||||
rational.cpp
|
||||
rcf.cpp
|
||||
region.cpp
|
||||
regex_range_collapse.cpp
|
||||
sat_local_search.cpp
|
||||
sat_lookahead.cpp
|
||||
sat_user_scope.cpp
|
||||
|
|
@ -135,11 +139,6 @@ add_executable(test-z3
|
|||
sls_test.cpp
|
||||
sls_seq_plugin.cpp
|
||||
seq_split.cpp
|
||||
seq_nielsen.cpp
|
||||
seq_parikh.cpp
|
||||
nseq_basic.cpp
|
||||
seq_regex.cpp
|
||||
nseq_zipt.cpp
|
||||
small_object_allocator.cpp
|
||||
smt2print_parse.cpp
|
||||
smt_context.cpp
|
||||
|
|
@ -151,6 +150,7 @@ add_executable(test-z3
|
|||
symbol.cpp
|
||||
symbol_table.cpp
|
||||
tbv.cpp
|
||||
term_enumeration.cpp
|
||||
theory_dl.cpp
|
||||
theory_pb.cpp
|
||||
timeout.cpp
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ static void test_ackr_bound_probe() {
|
|||
// by the BV solver back to a model for the original formula (with UF).
|
||||
// The two null-pointer guards in ackr_model_converter.cpp are exercised here.
|
||||
//
|
||||
static void test_ackermannize_bv_model() {
|
||||
[[maybe_unused]] static void test_ackermannize_bv_model() {
|
||||
Z3_config cfg = Z3_mk_config();
|
||||
Z3_context ctx = Z3_mk_context(cfg);
|
||||
Z3_del_config(cfg);
|
||||
|
|
|
|||
|
|
@ -81,6 +81,29 @@ void test_bvneg() {
|
|||
std::cout << r << "\n";
|
||||
}
|
||||
|
||||
{
|
||||
Z3_sort bv1 = Z3_mk_bv_sort(ctx, 1);
|
||||
Z3_sort bv64 = Z3_mk_bv_sort(ctx, 64);
|
||||
Z3_ast x = Z3_mk_fresh_const(ctx, "x", bv1);
|
||||
Z3_ast sx = Z3_mk_sign_ext(ctx, 63, x);
|
||||
Z3_ast zero = Z3_mk_int64(ctx, 0, bv64);
|
||||
Z3_ast minus_one = Z3_mk_int64(ctx, -1, bv64);
|
||||
Z3_ast args[2] = { Z3_mk_eq(ctx, sx, zero), Z3_mk_eq(ctx, sx, minus_one) };
|
||||
Z3_ast claim = Z3_mk_or(ctx, 2, args);
|
||||
|
||||
Z3_solver_push(ctx, s);
|
||||
Z3_solver_assert(ctx, s, Z3_mk_not(ctx, claim));
|
||||
ENSURE(Z3_solver_check(ctx, s) == Z3_L_FALSE);
|
||||
Z3_solver_pop(ctx, s, 1);
|
||||
|
||||
Z3_solver_push(ctx, s);
|
||||
Z3_solver_assert(ctx, s, Z3_mk_eq(ctx, sx, minus_one));
|
||||
std::string smt2 = Z3_solver_to_string(ctx, s);
|
||||
// Bit-vector numerals must print in canonical unsigned SMT2 form.
|
||||
ENSURE(smt2.find("(_ bv-") == std::string::npos);
|
||||
Z3_solver_pop(ctx, s, 1);
|
||||
}
|
||||
|
||||
Z3_solver_dec_ref(ctx, s);
|
||||
Z3_del_config(cfg);
|
||||
Z3_del_context(ctx);
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ static void test_bug_translate_null_target() {
|
|||
// Z3_translate(ctx, x, nullptr) would crash - no null check on target
|
||||
// The function checks c == target (line 1517) but doesn't check target != nullptr first
|
||||
// So mk_c(target) on line 1522 dereferences nullptr
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
Z3_get_error_code(ctx);
|
||||
std::cout << " [BUG DOCUMENTED] Z3_translate(ctx, ast, nullptr) would crash\n";
|
||||
std::cout << " No null check on target before mk_c(target) at api_ast.cpp:1522\n";
|
||||
|
||||
|
|
@ -596,7 +596,6 @@ static void test_bug_array_sort_mismatch() {
|
|||
|
||||
// Create Array(Int, Int)
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_sort bool_sort = Z3_mk_bool_sort(ctx);
|
||||
Z3_sort arr_sort = Z3_mk_array_sort(ctx, int_sort, int_sort);
|
||||
|
||||
Z3_ast arr = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "a"), arr_sort);
|
||||
|
|
@ -651,7 +650,7 @@ static void test_bug_substitute_null_arrays() {
|
|||
|
||||
// With num_exprs=0, null arrays should be fine
|
||||
Z3_ast r = Z3_substitute(ctx, x, 0, nullptr, nullptr);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
Z3_get_error_code(ctx);
|
||||
if (r != nullptr) {
|
||||
std::cout << " substitute(x, 0, null, null) OK: " << Z3_ast_to_string(ctx, r) << "\n";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ static void test_pop() {
|
|||
TestNode* list = nullptr;
|
||||
TestNode node1(1);
|
||||
TestNode::push_to_front(list, &node1);
|
||||
TestNode* popped = TestNode::pop(list);
|
||||
[[maybe_unused]] TestNode* popped = TestNode::pop(list);
|
||||
SASSERT(popped == &node1);
|
||||
SASSERT(list == nullptr);
|
||||
SASSERT(popped->next() == popped);
|
||||
|
|
|
|||
|
|
@ -303,14 +303,13 @@ class test_doc_cls {
|
|||
bool_vector to_merge(N, false);
|
||||
bit_vector discard_cols;
|
||||
discard_cols.resize(N, false);
|
||||
unsigned num_bits = 1;
|
||||
union_find_default_ctx union_ctx;
|
||||
subset_ints equalities(union_ctx);
|
||||
unsigned lo = N;
|
||||
equalities.mk_var();
|
||||
for (unsigned i = 1; i < N; ++i) {
|
||||
to_merge[i] = (m_ran(2) == 0);
|
||||
if (!to_merge[i]) ++num_bits; else lo = i;
|
||||
if (to_merge[i]) lo = i;
|
||||
equalities.mk_var();
|
||||
}
|
||||
if (lo == N) return;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ int vals[N];
|
|||
|
||||
static void tst1() {
|
||||
int_set h1;
|
||||
int size = 0;
|
||||
for (int i = 1; i < N; i ++) {
|
||||
int v = rand() % (N / 2);
|
||||
h1.insert(v);
|
||||
|
|
@ -92,7 +91,7 @@ static void tst2() {
|
|||
ENSURE(contains(h1, elem));
|
||||
n++;
|
||||
}
|
||||
ENSURE(n == h1.size());
|
||||
ENSURE(n == static_cast<int>(h1.size()));
|
||||
}
|
||||
ENSURE(h1.size() == h2.size());
|
||||
// std::cout << "size: " << h1.size() << ", capacity: " << h1.capacity() << "\n"; std::cout.flush();
|
||||
|
|
@ -194,7 +193,7 @@ void test_hashtable_iterators() {
|
|||
ht.insert(3);
|
||||
|
||||
int count = 0;
|
||||
for (const auto& elem : ht) {
|
||||
for ([[maybe_unused]] const auto& elem : ht) {
|
||||
++count;
|
||||
}
|
||||
VERIFY(count == 3);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ expr_ref hilbert_basis_validate::mk_validate(hilbert_basis& hb) {
|
|||
bool is_initial;
|
||||
hb.get_basis_solution(i, v, is_initial);
|
||||
|
||||
for (unsigned j = 0; xs.size() < v.size(); ++j) {
|
||||
for (; xs.size() < v.size(); ) {
|
||||
xs.push_back(m.mk_fresh_const("x", a.mk_int()));
|
||||
}
|
||||
|
||||
|
|
|
|||
261
src/test/lcube.cpp
Normal file
261
src/test/lcube.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/*++
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
lcube.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Tests for the largest cube test of Bromberger and Weidenbach
|
||||
(Fast Cube Tests for LIA Constraint Solving, IJCAR 2016),
|
||||
implemented in int_cube::find_largest_cube().
|
||||
|
||||
This file lives directly under src/test (not src/test/lp) so that the
|
||||
scripts/mk_make.py build, which only compiles the top-level test
|
||||
directory, links tst_lcube().
|
||||
|
||||
Author:
|
||||
|
||||
Lev Nachmanson (levnach)
|
||||
|
||||
--*/
|
||||
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
#include "util/debug.h"
|
||||
#include "util/params.h"
|
||||
#include "math/lp/int_cube.h"
|
||||
#include "math/lp/int_solver.h"
|
||||
#include "math/lp/lar_solver.h"
|
||||
#include "math/lp/numeric_pair.h"
|
||||
|
||||
namespace lp {
|
||||
|
||||
// tests for the largest cube test of Bromberger and Weidenbach
|
||||
namespace lcube_test {
|
||||
|
||||
struct ineq {
|
||||
vector<std::pair<mpq, unsigned>> m_coeffs;
|
||||
lconstraint_kind m_kind;
|
||||
mpq m_rs;
|
||||
};
|
||||
|
||||
// builds for every inequality a term with the bound and solves
|
||||
static void setup(lar_solver& solver, const vector<ineq>& ineqs, svector<unsigned>* term_columns = nullptr) {
|
||||
unsigned term_ext = 1000;
|
||||
for (const auto& in : ineqs) {
|
||||
unsigned t = solver.add_term(in.m_coeffs, term_ext++);
|
||||
solver.add_var_bound(t, in.m_kind, in.m_rs);
|
||||
if (term_columns)
|
||||
term_columns->push_back(t);
|
||||
}
|
||||
auto st = solver.solve();
|
||||
VERIFY(st == lp_status::OPTIMAL || st == lp_status::FEASIBLE);
|
||||
}
|
||||
|
||||
static void verify_model(const lar_solver& solver, const vector<ineq>& ineqs) {
|
||||
for (const auto& in : ineqs) {
|
||||
impq v;
|
||||
for (const auto& p : in.m_coeffs)
|
||||
v += solver.get_column_value(p.second) * p.first;
|
||||
switch (in.m_kind) {
|
||||
case lconstraint_kind::LE: VERIFY(v <= impq(in.m_rs)); break;
|
||||
case lconstraint_kind::GE: VERIFY(v >= impq(in.m_rs)); break;
|
||||
default: VERIFY(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void verify_int_values(const lar_solver& solver, std::initializer_list<unsigned> vars) {
|
||||
for (unsigned j : vars)
|
||||
VERIFY(solver.get_column_value(j).is_int());
|
||||
}
|
||||
|
||||
// The example of Bromberger and Weidenbach: 3x1 - x2 <= 0, -2x1 - x2 <= -2, -2x1 + x2 <= 1.
|
||||
// The largest cube is smaller than the unit cube, the rounded center is not a solution,
|
||||
// no coordinate flip repairs it (the only integer solution lies outside the lattice cell
|
||||
// of the center): expect undef and an intact solver state.
|
||||
static void test_paper_example_undef() {
|
||||
std::cout << "lcube: paper example, expecting undef\n";
|
||||
lar_solver solver;
|
||||
unsigned x1 = solver.add_named_var(0, true, "x1");
|
||||
unsigned x2 = solver.add_named_var(1, true, "x2");
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(3), x1}, {mpq(-1), x2}}, lconstraint_kind::LE, mpq(0)});
|
||||
ineqs.push_back(ineq{{{mpq(-2), x1}, {mpq(-1), x2}}, lconstraint_kind::LE, mpq(-2)});
|
||||
ineqs.push_back(ineq{{{mpq(-2), x1}, {mpq(1), x2}}, lconstraint_kind::LE, mpq(1)});
|
||||
setup(solver, ineqs);
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::undef);
|
||||
VERIFY(solver.ax_is_correct());
|
||||
}
|
||||
|
||||
// 3x1 - x2 <= 0, -2x1 - x2 <= -1, -2x1 + x2 <= 1: the largest cube has
|
||||
// edge 4/17 with the center (3/17, 1) that rounds to the solution (0, 1),
|
||||
// while the unit cube test fails: the largest cube test is stronger here.
|
||||
static void test_beats_unit_cube() {
|
||||
std::cout << "lcube: beating the unit cube test\n";
|
||||
lar_solver solver;
|
||||
unsigned x1 = solver.add_named_var(0, true, "x1");
|
||||
unsigned x2 = solver.add_named_var(1, true, "x2");
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(3), x1}, {mpq(-1), x2}}, lconstraint_kind::LE, mpq(0)});
|
||||
ineqs.push_back(ineq{{{mpq(-2), x1}, {mpq(-1), x2}}, lconstraint_kind::LE, mpq(-1)});
|
||||
ineqs.push_back(ineq{{{mpq(-2), x1}, {mpq(1), x2}}, lconstraint_kind::LE, mpq(1)});
|
||||
svector<unsigned> tcols;
|
||||
setup(solver, ineqs, &tcols);
|
||||
// move the solution to a fractional feasible point: the cube tests only
|
||||
// run when the current solution has fractional integer variables
|
||||
solver.set_column_value_test(x1, impq(mpq(1, 4)));
|
||||
solver.set_column_value_test(x2, impq(mpq(5, 4)));
|
||||
solver.set_column_value_test(tcols[0], impq(mpq(-1, 2)));
|
||||
solver.set_column_value_test(tcols[1], impq(mpq(-7, 4)));
|
||||
solver.set_column_value_test(tcols[2], impq(mpq(3, 4)));
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s)();
|
||||
std::cout << "unit cube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::undef);
|
||||
m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
verify_int_values(solver, {x1, x2});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
// 9/10 <= x + y + r <= 11/10, -11/10 <= x - y + r <= 1/10, 0 <= r <= 1/10,
|
||||
// x, y integer, r real. The real variable keeps the terms and their bounds
|
||||
// non-integer. A fractional center, e.g. (1/2, 1/2), rounds to an infeasible
|
||||
// point that is repaired by flipping one coordinate: expect sat.
|
||||
static void test_flip_repair() {
|
||||
std::cout << "lcube: rounding repair\n";
|
||||
lar_solver solver;
|
||||
unsigned x = solver.add_named_var(0, true, "x");
|
||||
unsigned y = solver.add_named_var(1, true, "y");
|
||||
unsigned r = solver.add_named_var(2, false, "r");
|
||||
solver.add_var_bound(r, lconstraint_kind::GE, mpq(0));
|
||||
solver.add_var_bound(r, lconstraint_kind::LE, mpq(1, 10));
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(1), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(9, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(1), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(11, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-1), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(-11, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-1), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(1, 10)});
|
||||
setup(solver, ineqs);
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m)
|
||||
<< ", flip successes: " << solver.settings().stats().m_lcube_flip_success << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
verify_int_values(solver, {x, y});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
// 3x + 5y >= 7 alone has infinite lattice width: the edge length is
|
||||
// unbounded and any cube center of edge >= 1 rounds to a solution.
|
||||
static void test_infinite_lattice_width() {
|
||||
std::cout << "lcube: infinite lattice width\n";
|
||||
lar_solver solver;
|
||||
unsigned x = solver.add_named_var(0, true, "x");
|
||||
unsigned y = solver.add_named_var(1, true, "y");
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(3), x}, {mpq(5), y}}, lconstraint_kind::GE, mpq(7)});
|
||||
setup(solver, ineqs);
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
verify_int_values(solver, {x, y});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
// 0 <= x + 2y + r <= 8, 0 <= x - 2y + r <= 8: the maximal edge length is
|
||||
// 8/3 >= 1, so the rounded center is guaranteed to be a solution.
|
||||
static void test_edge_at_least_one() {
|
||||
std::cout << "lcube: edge length at least 1\n";
|
||||
lar_solver solver;
|
||||
unsigned x = solver.add_named_var(0, true, "x");
|
||||
unsigned y = solver.add_named_var(1, true, "y");
|
||||
unsigned r = solver.add_named_var(2, false, "r");
|
||||
solver.add_var_bound(r, lconstraint_kind::GE, mpq(0));
|
||||
solver.add_var_bound(r, lconstraint_kind::LE, mpq(1, 10));
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(2), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(0)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(2), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(8)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-2), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(0)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-2), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(8)});
|
||||
setup(solver, ineqs);
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
verify_int_values(solver, {x, y});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
// runs the flip-repair instance through int_solver::check() with the
|
||||
// lp.lcube parameter set and the cube period lowered to 1: verifies the
|
||||
// dispatch and the parameter plumbing
|
||||
static void test_dispatch() {
|
||||
std::cout << "lcube: dispatch through int_solver::check\n";
|
||||
lar_solver solver;
|
||||
params_ref p;
|
||||
p.set_bool("lcube", true);
|
||||
solver.settings().updt_params(p);
|
||||
VERIFY(solver.settings().lcube());
|
||||
solver.settings().m_int_find_cube_period = 1;
|
||||
unsigned x = solver.add_named_var(0, true, "x");
|
||||
unsigned y = solver.add_named_var(1, true, "y");
|
||||
unsigned r = solver.add_named_var(2, false, "r");
|
||||
solver.add_var_bound(r, lconstraint_kind::GE, mpq(0));
|
||||
solver.add_var_bound(r, lconstraint_kind::LE, mpq(1, 10));
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(1), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(9, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(1), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(11, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-1), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(-11, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-1), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(1, 10)});
|
||||
svector<unsigned> tcols;
|
||||
setup(solver, ineqs, &tcols);
|
||||
// a fractional feasible point, so that check() does not return sat right away
|
||||
solver.set_column_value_test(x, impq(mpq(1, 2)));
|
||||
solver.set_column_value_test(y, impq(mpq(1, 2)));
|
||||
solver.set_column_value_test(r, impq(mpq(1, 10)));
|
||||
solver.set_column_value_test(tcols[0], impq(mpq(11, 10)));
|
||||
solver.set_column_value_test(tcols[1], impq(mpq(11, 10)));
|
||||
solver.set_column_value_test(tcols[2], impq(mpq(1, 10)));
|
||||
solver.set_column_value_test(tcols[3], impq(mpq(1, 10)));
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
explanation ex;
|
||||
lia_move m = i_s.check(&ex);
|
||||
std::cout << "check returned " << lia_move_to_string(m)
|
||||
<< ", lcube calls: " << solver.settings().stats().m_lcube_calls << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
VERIFY(solver.settings().stats().m_lcube_calls >= 1);
|
||||
verify_int_values(solver, {x, y});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
static void run() {
|
||||
test_paper_example_undef();
|
||||
test_beats_unit_cube();
|
||||
test_flip_repair();
|
||||
test_infinite_lattice_width();
|
||||
test_edge_at_least_one();
|
||||
test_dispatch();
|
||||
std::cout << "lcube tests passed\n";
|
||||
}
|
||||
} // namespace lcube_test
|
||||
} // namespace lp
|
||||
|
||||
void tst_lcube() {
|
||||
lp::lcube_test::run();
|
||||
}
|
||||
|
|
@ -1645,7 +1645,7 @@ void test_maximize_term() {
|
|||
lia_move lm = i_solver.check(&ex);
|
||||
VERIFY(lm == lia_move::sat);
|
||||
impq term_max;
|
||||
lp_status st = solver.maximize_term(term_2x_pl_2y, term_max);
|
||||
lp_status st = solver.maximize_term(term_2x_pl_2y, term_max, /*fix_int_cols*/ true);
|
||||
|
||||
std::cout << "status = " << lp_status_to_string(st) << std::endl;
|
||||
std::cout << "term_max = " << term_max << std::endl;
|
||||
|
|
|
|||
|
|
@ -113,6 +113,9 @@
|
|||
X(api_bug) \
|
||||
X(api_special_relations) \
|
||||
X(arith_rewriter) \
|
||||
X(range_predicate) \
|
||||
X(regex_range_collapse) \
|
||||
X(seq_rewriter) \
|
||||
X(check_assumptions) \
|
||||
X(smt_context) \
|
||||
X(theory_dl) \
|
||||
|
|
@ -137,6 +140,7 @@
|
|||
X(zstring)
|
||||
|
||||
#define FOR_EACH_EXTRA_TEST(X, X_ARGV) \
|
||||
X(tptp) \
|
||||
X(ext_numeral) \
|
||||
X(interval) \
|
||||
X(value_generator) \
|
||||
|
|
@ -190,18 +194,15 @@
|
|||
X(sls_test) \
|
||||
X(scoped_vector) \
|
||||
X(sls_seq_plugin) \
|
||||
X(seq_nielsen) \
|
||||
X(seq_parikh) \
|
||||
X(nseq_basic) \
|
||||
X(seq_regex) \
|
||||
X(nseq_zipt) \
|
||||
X(euf_sgraph) \
|
||||
X(euf_seq_plugin) \
|
||||
X(ho_matcher) \
|
||||
X(finite_set) \
|
||||
X(finite_set_rewriter) \
|
||||
X(seq_split) \
|
||||
X(fpa)
|
||||
X(fpa) \
|
||||
X(seq_regex_bisim) \
|
||||
X(term_enumeration) \
|
||||
X(lcube) \
|
||||
X(psmt)
|
||||
|
||||
#define FOR_EACH_TEST(X, X_ARGV) \
|
||||
FOR_EACH_ALL_TEST(X, X_ARGV) \
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ static void hit_me(char const* wm) {
|
|||
Z3_mk_bv_sort(ctx,i);
|
||||
|
||||
}
|
||||
catch (std::bad_alloc) {
|
||||
catch (const std::bad_alloc&) {
|
||||
std::cout << "caught\n";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "model/model.h"
|
||||
#include "model/model_evaluator.h"
|
||||
#include "model/func_interp.h"
|
||||
#include "model/model_pp.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
|
|
@ -65,5 +66,29 @@ void tst_model_evaluator() {
|
|||
eval(e, v);
|
||||
std::cout << e << " " << v << "\n";
|
||||
}
|
||||
|
||||
{
|
||||
func_interp fi2(m, 1);
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
fi2.set_else(zero);
|
||||
for (unsigned i = 0; i < 600; ++i) {
|
||||
expr_ref arg(a.mk_int(rational(i)), m);
|
||||
expr* args[1] = { arg.get() };
|
||||
fi2.insert_entry(args, i == 599 ? one.get() : zero.get());
|
||||
}
|
||||
fi2.compress();
|
||||
SASSERT(fi2.num_entries() == 1);
|
||||
|
||||
expr_ref removed_arg(a.mk_int(0), m);
|
||||
[[maybe_unused]] expr* removed_args[1] = { removed_arg.get() };
|
||||
SASSERT(fi2.get_entry(removed_args) == nullptr);
|
||||
|
||||
expr_ref kept_arg(a.mk_int(599), m);
|
||||
expr* kept_args[1] = { kept_arg.get() };
|
||||
[[maybe_unused]] func_entry* kept = fi2.get_entry(kept_args);
|
||||
SASSERT(kept != nullptr);
|
||||
SASSERT(kept->get_result() == one.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ static void project(nlsat::solver& s, nlsat::explain& ex, nlsat::var x, unsigned
|
|||
std::cout << "\n";
|
||||
}
|
||||
|
||||
static void project_fa(nlsat::solver& s, nlsat::explain& ex, nlsat::var x, unsigned num, nlsat::literal const* lits) {
|
||||
static nlsat::scoped_literal_vector project_fa(nlsat::solver& s, nlsat::explain& ex, nlsat::var x, unsigned num, nlsat::literal const* lits) {
|
||||
std::cout << "Project ";
|
||||
nlsat::scoped_literal_vector result(s);
|
||||
ex.compute_conflict_explanation(num, lits, result);
|
||||
|
|
@ -464,6 +464,7 @@ static void project_fa(nlsat::solver& s, nlsat::explain& ex, nlsat::var x, unsig
|
|||
s.display(std::cout << " ", ~lits[i]);
|
||||
}
|
||||
std::cout << ")\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
static nlsat::literal mk_gt(nlsat::solver& s, nlsat::poly* p) {
|
||||
|
|
@ -490,6 +491,39 @@ static nlsat::literal mk_root_eq(nlsat::solver& s, nlsat::poly* p, nlsat::var x,
|
|||
return nlsat::literal(b, false);
|
||||
}
|
||||
|
||||
static bool contains_var(nlsat::var_vector const& vars, nlsat::var x) {
|
||||
for (auto v : vars) {
|
||||
if (v == x)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool clause_contains_root_dependency(
|
||||
nlsat::solver& s,
|
||||
nlsat::scoped_literal_vector const& result,
|
||||
nlsat::atom::kind kind,
|
||||
nlsat::var target,
|
||||
unsigned root_index,
|
||||
nlsat::var dep1,
|
||||
nlsat::var dep2,
|
||||
nlsat::var dep3) {
|
||||
nlsat::pmanager& pm = s.pm();
|
||||
nlsat::var_vector vars;
|
||||
for (auto l : result) {
|
||||
nlsat::atom* a = s.bool_var2atom(l.var());
|
||||
if (!a || !a->is_root_atom() || a->get_kind() != kind)
|
||||
continue;
|
||||
nlsat::root_atom* ra = nlsat::to_root_atom(a);
|
||||
if (ra->x() != target || ra->i() != root_index || pm.max_var(ra->p()) != target)
|
||||
continue;
|
||||
s.vars(l, vars);
|
||||
if (contains_var(vars, dep1) && contains_var(vars, dep2) && contains_var(vars, dep3))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void set_assignment_value(nlsat::assignment& as, anum_manager& am, nlsat::var v, rational const& val) {
|
||||
scoped_anum tmp(am);
|
||||
am.set(tmp, val.to_mpq());
|
||||
|
|
@ -1183,8 +1217,8 @@ static void tst_15() {
|
|||
auto cell = lws.single_cell();
|
||||
}
|
||||
|
||||
// Test case for unsound lemma lws2380 - comparing standard projection vs levelwise
|
||||
// The issue: x7 is unconstrained in levelwise output but affects the section polynomial
|
||||
// Historical lws2380 regression test: both projection paths should preserve
|
||||
// the x7-linked section/root constraints that witness the projected dependency.
|
||||
static void tst_16() {
|
||||
// enable_trace("nlsat_explain");
|
||||
|
||||
|
|
@ -1283,8 +1317,9 @@ static void tst_16() {
|
|||
lits.push_back(mk_gt(s, p0.get())); // x13 > 0
|
||||
lits.push_back(mk_gt(s, p1.get())); // p1 > 0
|
||||
|
||||
project_fa(s, ex, x13, lits.size(), lits.data());
|
||||
auto result = project_fa(s, ex, x13, lits.size(), lits.data());
|
||||
std::cout << "\n";
|
||||
ENSURE(clause_contains_root_dependency(s, result, nlsat::atom::ROOT_EQ, x11, 1, x7, x8, x10));
|
||||
};
|
||||
|
||||
run_test(false); // Standard projection
|
||||
|
|
@ -2144,11 +2179,11 @@ static void tst_22() {
|
|||
}
|
||||
}
|
||||
|
||||
if (all_false) {
|
||||
if (all_false)
|
||||
std::cout << "*** ALL literals FALSE at counterexample - LEMMA IS UNSOUND! ***\n";
|
||||
} else {
|
||||
else
|
||||
std::cout << "At least one literal is TRUE - lemma is sound at this point\n";
|
||||
}
|
||||
ENSURE(!all_false);
|
||||
};
|
||||
|
||||
run_test(false); // lws=false (buggy)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ static void tst2() {
|
|||
m.enable_concurrent(true);
|
||||
|
||||
vector<std::pair<cell *, int> > object_coeff_pairs;
|
||||
unsigned num_resets = 0;
|
||||
[[maybe_unused]] unsigned num_resets = 0;
|
||||
|
||||
for (unsigned i = 0; i < 100000; ++i) {
|
||||
unsigned idx = rand() % 6;
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@ static void test_polymorphic_datatype_api() {
|
|||
std::cout << "triple_int_bool: " << Z3_sort_to_string(ctx, triple_int_bool) << "\n";
|
||||
|
||||
// Get constructors and accessors from the instantiated datatype
|
||||
Z3_func_decl mk_triple_int_bool = Z3_get_datatype_sort_constructor(ctx, triple_int_bool, 0);
|
||||
[[maybe_unused]] Z3_func_decl mk_triple_int_bool = Z3_get_datatype_sort_constructor(ctx, triple_int_bool, 0);
|
||||
Z3_func_decl first_int_bool = Z3_get_datatype_sort_constructor_accessor(ctx, triple_int_bool, 0, 0);
|
||||
Z3_func_decl second_int_bool = Z3_get_datatype_sort_constructor_accessor(ctx, triple_int_bool, 0, 1);
|
||||
[[maybe_unused]] Z3_func_decl second_int_bool = Z3_get_datatype_sort_constructor_accessor(ctx, triple_int_bool, 0, 1);
|
||||
Z3_func_decl third_int_bool = Z3_get_datatype_sort_constructor_accessor(ctx, triple_int_bool, 0, 2);
|
||||
|
||||
std::cout << "Got constructors and accessors from instantiated datatype\n";
|
||||
|
|
|
|||
153
src/test/psmt.cpp
Normal file
153
src/test/psmt.cpp
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
psmt.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Tests for the parallel SMT tactic (psmt).
|
||||
Regression test for GitHub issue #9985 (deadlock on psmt).
|
||||
|
||||
--*/
|
||||
#ifndef SINGLE_THREAD
|
||||
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/array_decl_plugin.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "smt/smt_solver.h"
|
||||
#include "smt/tactic/smt_tactic_core.h"
|
||||
#include "tactic/goal.h"
|
||||
#include "tactic/tactic.h"
|
||||
#include <iostream>
|
||||
|
||||
// Regression test for GitHub issue #9985:
|
||||
//
|
||||
// psmt used to deadlock when every CDCL worker returned l_undef with a
|
||||
// reason other than "max-conflicts-reached" (e.g., theory incompleteness).
|
||||
// batch_manager::set_unknown() changed the terminal state but did not
|
||||
// notify backbone workers or the core-minimizer worker that were blocked on
|
||||
// their condition variables. Those workers blocked forever.
|
||||
//
|
||||
// Fix: set_unknown() now calls m_bb_cv.notify_all() and
|
||||
// m_core_min_cv.notify_all() so all waiting helper threads observe the
|
||||
// state change and exit cleanly.
|
||||
//
|
||||
// We exercise three cases:
|
||||
// 1. SAT – trivially satisfiable boolean formula.
|
||||
// 2. UNSAT – trivially unsatisfiable boolean formula.
|
||||
// 3. UNKNOWN (no deadlock) – formula whose root cube is theory-incomplete.
|
||||
// The formula uses (as-array f) terms for a function f : Int -> Bool.
|
||||
// The array theory marks itself incomplete for as-array, so every
|
||||
// CDCL worker returns l_undef with "(incomplete (theory array))",
|
||||
// triggering the set_unknown path that used to deadlock.
|
||||
static void tst_psmt_worker() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
params_ref p;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 1. SAT test: assert (or x (not x)) – always true
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
tactic_ref t = mk_parallel_smt_tactic(m, p);
|
||||
goal_ref g = alloc(goal, m, false, true, false);
|
||||
expr_ref x(m.mk_const(symbol("x"), m.mk_bool_sort()), m);
|
||||
g->assert_expr(m.mk_or(x, m.mk_not(x)));
|
||||
|
||||
model_ref md;
|
||||
labels_vec labels;
|
||||
proof_ref pr(m);
|
||||
expr_dependency_ref core(m);
|
||||
std::string reason_unknown;
|
||||
lbool r = check_sat(*t, g, md, labels, pr, core, reason_unknown);
|
||||
SASSERT(r == l_true);
|
||||
(void)r;
|
||||
std::cout << "psmt SAT: " << r << "\n";
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 2. UNSAT test: assert (and x (not x))
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
tactic_ref t = mk_parallel_smt_tactic(m, p);
|
||||
goal_ref g = alloc(goal, m, false, true, false);
|
||||
expr_ref x(m.mk_const(symbol("x"), m.mk_bool_sort()), m);
|
||||
g->assert_expr(m.mk_and(x, m.mk_not(x)));
|
||||
|
||||
model_ref md;
|
||||
labels_vec labels;
|
||||
proof_ref pr(m);
|
||||
expr_dependency_ref core(m);
|
||||
std::string reason_unknown;
|
||||
lbool r = check_sat(*t, g, md, labels, pr, core, reason_unknown);
|
||||
SASSERT(r == l_false);
|
||||
(void)r;
|
||||
std::cout << "psmt UNSAT: " << r << "\n";
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 3. UNKNOWN (deadlock regression) test.
|
||||
//
|
||||
// Reproduce: (declare-fun f (Int) Bool)
|
||||
// (declare-fun g (Int) Bool)
|
||||
// (assert (distinct f g))
|
||||
// (check-sat-using psmt)
|
||||
//
|
||||
// In SMT-LIB2, the function symbols f and g are lifted to array terms
|
||||
// via (as-array f) and (as-array g). The array theory is explicitly
|
||||
// incomplete for as-array, so check_sat returns l_undef with reason
|
||||
// "(incomplete (theory array))". Previously set_unknown() forgot to
|
||||
// notify backbone and core-minimizer workers' condition variables,
|
||||
// causing a deadlock; now it does.
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
tactic_ref t = mk_parallel_smt_tactic(m, p);
|
||||
goal_ref g = alloc(goal, m, false, true, false);
|
||||
|
||||
// Build func_decls: f : Int -> Bool, g : Int -> Bool
|
||||
arith_util arith(m);
|
||||
sort* int_s = arith.mk_int();
|
||||
sort* domain[1] = { int_s };
|
||||
func_decl* f_decl = m.mk_func_decl(symbol("f_fn"), 1, domain, m.mk_bool_sort());
|
||||
func_decl* g_decl = m.mk_func_decl(symbol("g_fn"), 1, domain, m.mk_bool_sort());
|
||||
|
||||
// (as-array f) and (as-array g): array representations of f and g
|
||||
array_util autil(m);
|
||||
expr_ref f_arr(autil.mk_as_array(f_decl), m);
|
||||
expr_ref g_arr(autil.mk_as_array(g_decl), m);
|
||||
|
||||
// (distinct (as-array f) (as-array g))
|
||||
expr* dist_args[2] = { f_arr, g_arr };
|
||||
expr_ref distinct_fg(m.mk_distinct(2, dist_args), m);
|
||||
g->assert_expr(distinct_fg);
|
||||
|
||||
model_ref md;
|
||||
labels_vec labels;
|
||||
proof_ref pr(m);
|
||||
expr_dependency_ref core(m);
|
||||
std::string reason_unknown;
|
||||
lbool r = check_sat(*t, g, md, labels, pr, core, reason_unknown);
|
||||
// The result must be l_undef (theory-incomplete).
|
||||
// If the fix is absent, this call deadlocks instead of returning.
|
||||
SASSERT(r == l_undef);
|
||||
(void)r;
|
||||
std::cout << "psmt UNKNOWN (no deadlock): " << r << "\n";
|
||||
}
|
||||
|
||||
std::cout << "psmt tests passed\n";
|
||||
}
|
||||
|
||||
void tst_psmt() {
|
||||
tst_psmt_worker();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void tst_psmt() {
|
||||
// Single-threaded build: parallel tactic degrades to sequential.
|
||||
// No deadlock is possible; nothing to test.
|
||||
}
|
||||
|
||||
#endif
|
||||
260
src/test/range_predicate.cpp
Normal file
260
src/test/range_predicate.cpp
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
test/range_predicate.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Unit tests for the range-algebra value type seq::range_predicate.
|
||||
|
||||
The tests exercise:
|
||||
* factory constructors and canonical-form invariants,
|
||||
* extensional equality and total ordering,
|
||||
* Boolean operations (|, &, ~, -, ^) on hand-picked instances,
|
||||
* exhaustive verification of de-Morgan and lattice laws on a
|
||||
small character domain, by enumerating every subset.
|
||||
|
||||
Author:
|
||||
|
||||
Margus Veanes (veanes) 2026
|
||||
|
||||
--*/
|
||||
|
||||
#include "ast/rewriter/seq_range_predicate.h"
|
||||
#include "util/debug.h"
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
using seq::range_predicate;
|
||||
|
||||
namespace {
|
||||
|
||||
// Build a range_predicate from a bitmask over [0, max_char] for testing.
|
||||
range_predicate from_mask(uint64_t mask, unsigned max_char) {
|
||||
range_predicate r = range_predicate::empty(max_char);
|
||||
for (unsigned c = 0; c <= max_char; ++c)
|
||||
if ((mask >> c) & 1u)
|
||||
r = r | range_predicate::singleton(c, max_char);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Convert a range_predicate back to a bitmask for cross-checking.
|
||||
uint64_t to_mask(range_predicate const& r) {
|
||||
uint64_t mask = 0;
|
||||
for (unsigned c = 0; c <= r.max_char(); ++c)
|
||||
if (r.contains(c))
|
||||
mask |= (uint64_t(1) << c);
|
||||
return mask;
|
||||
}
|
||||
|
||||
void test_factories() {
|
||||
auto e = range_predicate::empty(255);
|
||||
ENSURE(e.is_empty());
|
||||
ENSURE(!e.is_top());
|
||||
ENSURE(e.num_ranges() == 0);
|
||||
ENSURE(e.cardinality() == 0);
|
||||
|
||||
auto t = range_predicate::top(255);
|
||||
ENSURE(!t.is_empty());
|
||||
ENSURE(t.is_top());
|
||||
ENSURE(t.num_ranges() == 1);
|
||||
ENSURE(t.cardinality() == 256);
|
||||
ENSURE(t.contains(0));
|
||||
ENSURE(t.contains(255));
|
||||
|
||||
auto s = range_predicate::singleton(42, 255);
|
||||
ENSURE(s.num_ranges() == 1);
|
||||
ENSURE(s.cardinality() == 1);
|
||||
ENSURE(s.contains(42));
|
||||
ENSURE(!s.contains(41));
|
||||
unsigned c = 0;
|
||||
ENSURE(s.is_singleton(c));
|
||||
ENSURE(c == 42);
|
||||
|
||||
auto r = range_predicate::range(10, 20, 255);
|
||||
ENSURE(r.num_ranges() == 1);
|
||||
ENSURE(r.cardinality() == 11);
|
||||
ENSURE(r.contains(10));
|
||||
ENSURE(r.contains(20));
|
||||
ENSURE(!r.contains(9));
|
||||
ENSURE(!r.contains(21));
|
||||
|
||||
// Reversed bounds produce empty.
|
||||
auto bad = range_predicate::range(20, 10, 255);
|
||||
ENSURE(bad.is_empty());
|
||||
|
||||
// Clipping at max_char.
|
||||
auto clipped = range_predicate::range(200, 1000, 255);
|
||||
ENSURE(clipped.num_ranges() == 1);
|
||||
ENSURE(clipped[0] == std::make_pair(200u, 255u));
|
||||
}
|
||||
|
||||
void test_equality_and_order() {
|
||||
auto a = range_predicate::range(1, 5, 31);
|
||||
auto b = range_predicate::range(1, 5, 31);
|
||||
auto c = range_predicate::range(1, 6, 31);
|
||||
ENSURE(a == b);
|
||||
ENSURE(a != c);
|
||||
ENSURE(a.hash() == b.hash());
|
||||
ENSURE(a < c || c < a);
|
||||
ENSURE(!(a < a));
|
||||
|
||||
auto empty = range_predicate::empty(31);
|
||||
ENSURE(empty < a);
|
||||
|
||||
// Canonical merging of adjacent ranges.
|
||||
auto d = range_predicate::range(0, 4, 31) | range_predicate::range(5, 10, 31);
|
||||
auto e = range_predicate::range(0, 10, 31);
|
||||
ENSURE(d == e);
|
||||
}
|
||||
|
||||
void test_union_intersection_hand() {
|
||||
unsigned const M = 31;
|
||||
auto a = range_predicate::range(0, 4, M) | range_predicate::range(10, 14, M);
|
||||
auto b = range_predicate::range(3, 11, M);
|
||||
|
||||
auto u = a | b; // [0,14]
|
||||
ENSURE(u.num_ranges() == 1);
|
||||
ENSURE(u[0] == std::make_pair(0u, 14u));
|
||||
|
||||
auto i = a & b; // [3,4] U [10,11]
|
||||
ENSURE(i.num_ranges() == 2);
|
||||
ENSURE(i[0] == std::make_pair(3u, 4u));
|
||||
ENSURE(i[1] == std::make_pair(10u, 11u));
|
||||
|
||||
auto d = a - b; // [0,2] U [12,14]
|
||||
ENSURE(d.num_ranges() == 2);
|
||||
ENSURE(d[0] == std::make_pair(0u, 2u));
|
||||
ENSURE(d[1] == std::make_pair(12u, 14u));
|
||||
|
||||
auto x = a ^ b; // [0,2] U [5,9] U [12,14]
|
||||
ENSURE(x.num_ranges() == 3);
|
||||
ENSURE(x[0] == std::make_pair(0u, 2u));
|
||||
ENSURE(x[1] == std::make_pair(5u, 9u));
|
||||
ENSURE(x[2] == std::make_pair(12u, 14u));
|
||||
}
|
||||
|
||||
void test_complement_hand() {
|
||||
unsigned const M = 10;
|
||||
auto e = range_predicate::empty(M);
|
||||
ENSURE((~e).is_top());
|
||||
auto t = range_predicate::top(M);
|
||||
ENSURE((~t).is_empty());
|
||||
|
||||
// ~([2,3] U [7,8]) = [0,1] U [4,6] U [9,10]
|
||||
auto a = range_predicate::range(2, 3, M) | range_predicate::range(7, 8, M);
|
||||
auto na = ~a;
|
||||
ENSURE(na.num_ranges() == 3);
|
||||
ENSURE(na[0] == std::make_pair(0u, 1u));
|
||||
ENSURE(na[1] == std::make_pair(4u, 6u));
|
||||
ENSURE(na[2] == std::make_pair(9u, 10u));
|
||||
|
||||
// ~([0,4]) = [5,10]
|
||||
auto b = range_predicate::range(0, 4, M);
|
||||
auto nb = ~b;
|
||||
ENSURE(nb.num_ranges() == 1);
|
||||
ENSURE(nb[0] == std::make_pair(5u, 10u));
|
||||
|
||||
// ~([5,10]) = [0,4]
|
||||
auto cnb = ~nb;
|
||||
ENSURE(cnb == b);
|
||||
}
|
||||
|
||||
// Exhaustively verify the lattice / de-Morgan laws on a small domain
|
||||
// by enumerating every possible subset (bitmask).
|
||||
void test_exhaustive_laws() {
|
||||
unsigned const M = 5; // 6 characters -> 64 subsets
|
||||
unsigned const N = 1u << (M + 1);
|
||||
for (unsigned i = 0; i < N; ++i) {
|
||||
range_predicate A = from_mask(i, M);
|
||||
ENSURE(to_mask(A) == i);
|
||||
// ~ ~ A == A
|
||||
ENSURE(~~A == A);
|
||||
// A | ~A == top
|
||||
ENSURE((A | ~A).is_top());
|
||||
// A & ~A == empty
|
||||
ENSURE((A & ~A).is_empty());
|
||||
// cardinality matches popcount
|
||||
unsigned pop = 0;
|
||||
for (unsigned k = 0; k <= M; ++k) if ((i >> k) & 1u) ++pop;
|
||||
ENSURE(A.cardinality() == pop);
|
||||
}
|
||||
for (unsigned i = 0; i < N; ++i) {
|
||||
range_predicate A = from_mask(i, M);
|
||||
for (unsigned j = 0; j < N; ++j) {
|
||||
range_predicate B = from_mask(j, M);
|
||||
// Bitmask reference semantics.
|
||||
ENSURE(to_mask(A | B) == (i | j));
|
||||
ENSURE(to_mask(A & B) == (i & j));
|
||||
ENSURE(to_mask(A - B) == (i & ~j & ((1u << (M + 1)) - 1u)));
|
||||
ENSURE(to_mask(A ^ B) == (i ^ j));
|
||||
// de-Morgan
|
||||
ENSURE(~(A | B) == (~A & ~B));
|
||||
ENSURE(~(A & B) == (~A | ~B));
|
||||
// Commutativity
|
||||
ENSURE((A | B) == (B | A));
|
||||
ENSURE((A & B) == (B & A));
|
||||
// (A - B) == A & ~B
|
||||
ENSURE((A - B) == (A & ~B));
|
||||
// (A ^ B) == (A | B) - (A & B)
|
||||
ENSURE((A ^ B) == ((A | B) - (A & B)));
|
||||
// Extensional equality is reflexive on equal masks.
|
||||
if (i == j) {
|
||||
ENSURE(A == B);
|
||||
ENSURE(A.hash() == B.hash());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_total_order_strict() {
|
||||
unsigned const M = 5;
|
||||
unsigned const N = 1u << (M + 1);
|
||||
// Strict total order: for any distinct A, B exactly one of A<B, B<A holds.
|
||||
for (unsigned i = 0; i < N; ++i) {
|
||||
range_predicate A = from_mask(i, M);
|
||||
ENSURE(!(A < A));
|
||||
for (unsigned j = i + 1; j < N; ++j) {
|
||||
range_predicate B = from_mask(j, M);
|
||||
bool lt = A < B;
|
||||
bool gt = B < A;
|
||||
ENSURE(lt != gt);
|
||||
ENSURE(lt || gt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_display() {
|
||||
std::ostringstream oss;
|
||||
oss << range_predicate::empty(31);
|
||||
ENSURE(oss.str() == "[]");
|
||||
|
||||
oss.str("");
|
||||
oss << range_predicate::range(3, 7, 31);
|
||||
ENSURE(oss.str() == "[3-7]");
|
||||
|
||||
oss.str("");
|
||||
oss << range_predicate::singleton(9, 31);
|
||||
ENSURE(oss.str() == "[9]");
|
||||
|
||||
oss.str("");
|
||||
auto p = range_predicate::range(0, 2, 31) | range_predicate::singleton(5, 31);
|
||||
oss << p;
|
||||
ENSURE(oss.str() == "[0-2,5]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void tst_range_predicate() {
|
||||
test_factories();
|
||||
test_equality_and_order();
|
||||
test_union_intersection_hand();
|
||||
test_complement_hand();
|
||||
test_exhaustive_laws();
|
||||
test_total_order_strict();
|
||||
test_display();
|
||||
std::cout << "range_predicate unit tests passed\n";
|
||||
}
|
||||
260
src/test/regex_range_collapse.cpp
Normal file
260
src/test/regex_range_collapse.cpp
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
regex_range_collapse.cpp - unit tests
|
||||
|
||||
--*/
|
||||
|
||||
#include "ast/rewriter/seq_range_collapse.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "util/util.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
using seq::range_predicate;
|
||||
using seq::regex_to_range_predicate;
|
||||
using seq::range_predicate_to_regex;
|
||||
|
||||
static void check(bool ok, char const* what) {
|
||||
if (!ok) {
|
||||
std::cerr << "regex_range_collapse FAILED: " << what << "\n";
|
||||
ENSURE(false);
|
||||
}
|
||||
}
|
||||
|
||||
static expr_ref mk_singleton_str(seq_util& u, unsigned c) {
|
||||
return expr_ref(u.str.mk_string(zstring(c)), u.get_manager());
|
||||
}
|
||||
|
||||
static bool extract_range_chars(seq_util& u, expr* e, unsigned& lo, unsigned& hi) {
|
||||
expr* lo_e = nullptr; expr* hi_e = nullptr;
|
||||
expr *s = nullptr;
|
||||
zstring str;
|
||||
if (u.re.is_to_re(e, s) && u.str.is_string(s, str) && str.length() == 1) {
|
||||
lo = hi = str[0];
|
||||
return true;
|
||||
}
|
||||
else if (u.re.is_range(e, lo_e, hi_e) && u.str.is_string(lo_e) && u.str.is_string(hi_e)) {
|
||||
zstring lo_str, hi_str;
|
||||
u.str.is_string(lo_e, lo_str);
|
||||
u.str.is_string(hi_e, hi_str);
|
||||
if (lo_str.length() == 1 && hi_str.length() == 1) {
|
||||
lo = lo_str[0];
|
||||
hi = hi_str[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!u.re.is_range(e, lo_e, hi_e))
|
||||
return false;
|
||||
// Accept either string-constant or (seq.unit (Char N)) bound form.
|
||||
if (u.re.is_range(e, lo, hi))
|
||||
return true;
|
||||
expr* lc = nullptr; expr* hc = nullptr;
|
||||
if (u.str.is_unit(lo_e, lc) && u.is_const_char(lc, lo) &&
|
||||
u.str.is_unit(hi_e, hc) && u.is_const_char(hc, hi))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void run() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
seq_util u(m);
|
||||
unsigned const M = u.max_char();
|
||||
|
||||
sort* str_sort = u.str.mk_string_sort();
|
||||
sort* re_sort = u.re.mk_re(str_sort);
|
||||
|
||||
// primitives
|
||||
{
|
||||
range_predicate p(M);
|
||||
check(regex_to_range_predicate(u, u.re.mk_empty(re_sort), p) && p.is_empty(),
|
||||
"re.empty -> empty");
|
||||
check(regex_to_range_predicate(u, u.re.mk_full_char(re_sort), p) && p.is_top(),
|
||||
"re.full_char -> top");
|
||||
}
|
||||
// re.range "a" "z"
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref a = mk_singleton_str(u, 'a');
|
||||
expr_ref z = mk_singleton_str(u, 'z');
|
||||
expr_ref r(u.re.mk_range(a, z), m);
|
||||
check(regex_to_range_predicate(u, r, p) && p.num_ranges() == 1 &&
|
||||
p[0].first == 'a' && p[0].second == 'z',
|
||||
"re.range a z -> [a,z]");
|
||||
}
|
||||
// Disjoint union: (a..z) | (0..9)
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'z')), m);
|
||||
expr_ref r2(u.re.mk_range(mk_singleton_str(u, '0'), mk_singleton_str(u, '9')), m);
|
||||
expr_ref un(u.re.mk_union(r1, r2), m);
|
||||
check(regex_to_range_predicate(u, un, p) && p.num_ranges() == 2,
|
||||
"(a-z)|(0-9) -> 2 ranges");
|
||||
// canonical order: lower lo first
|
||||
check(p[0].first == '0' && p[0].second == '9' && p[1].first == 'a' && p[1].second == 'z',
|
||||
"(a-z)|(0-9) ranges in canonical order");
|
||||
}
|
||||
// Overlapping union: (a..c) | (b..f) -> (a..f)
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'c')), m);
|
||||
expr_ref r2(u.re.mk_range(mk_singleton_str(u, 'b'), mk_singleton_str(u, 'f')), m);
|
||||
expr_ref un(u.re.mk_union(r1, r2), m);
|
||||
check(regex_to_range_predicate(u, un, p) && p.num_ranges() == 1 &&
|
||||
p[0].first == 'a' && p[0].second == 'f',
|
||||
"(a-c)|(b-f) -> (a-f)");
|
||||
}
|
||||
// Adjacent union: (a..c) | (d..f) -> (a..f) (canonical predicate merges adjacent)
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'c')), m);
|
||||
expr_ref r2(u.re.mk_range(mk_singleton_str(u, 'd'), mk_singleton_str(u, 'f')), m);
|
||||
expr_ref un(u.re.mk_union(r1, r2), m);
|
||||
check(regex_to_range_predicate(u, un, p) && p.num_ranges() == 1 &&
|
||||
p[0].first == 'a' && p[0].second == 'f',
|
||||
"(a-c)|(d-f) -> (a-f) via adjacency");
|
||||
}
|
||||
// Disjoint intersection: (a..z) & (0..9) -> empty
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'z')), m);
|
||||
expr_ref r2(u.re.mk_range(mk_singleton_str(u, '0'), mk_singleton_str(u, '9')), m);
|
||||
expr_ref ix(u.re.mk_inter(r1, r2), m);
|
||||
check(regex_to_range_predicate(u, ix, p) && p.is_empty(),
|
||||
"(a-z)&(0-9) -> empty");
|
||||
}
|
||||
// Overlapping intersection: (a..f) & (c..z) -> (c..f)
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'f')), m);
|
||||
expr_ref r2(u.re.mk_range(mk_singleton_str(u, 'c'), mk_singleton_str(u, 'z')), m);
|
||||
expr_ref ix(u.re.mk_inter(r1, r2), m);
|
||||
check(regex_to_range_predicate(u, ix, p) && p.num_ranges() == 1 &&
|
||||
p[0].first == 'c' && p[0].second == 'f',
|
||||
"(a-f)&(c-z) -> (c-f)");
|
||||
}
|
||||
// Complement: re.complement is intentionally NOT a char-class op
|
||||
// (it operates over Σ*), so it must NOT be translated.
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'z')), m);
|
||||
expr_ref cmp(u.re.mk_complement(r1), m);
|
||||
check(!regex_to_range_predicate(u, cmp, p),
|
||||
"re.comp of range is NOT translatable (sequence-level complement)");
|
||||
}
|
||||
// Diff: (a..f) \ (c..z) -> (a..b)
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'f')), m);
|
||||
expr_ref r2(u.re.mk_range(mk_singleton_str(u, 'c'), mk_singleton_str(u, 'z')), m);
|
||||
expr_ref df(u.re.mk_diff(r1, r2), m);
|
||||
check(regex_to_range_predicate(u, df, p) && p.num_ranges() == 1 &&
|
||||
p[0].first == 'a' && p[0].second == 'b',
|
||||
"(a-f) \\ (c-z) -> (a-b)");
|
||||
}
|
||||
// Negative: re.* of a range is NOT a char class
|
||||
{
|
||||
range_predicate p(M);
|
||||
expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'z')), m);
|
||||
expr_ref star(u.re.mk_star(r1), m);
|
||||
check(!regex_to_range_predicate(u, star, p),
|
||||
"re.* of range not translatable");
|
||||
}
|
||||
|
||||
// Negative: a regex whose element type is NOT a sequence of
|
||||
// characters (here (Seq Int)) must be rejected outright, even for
|
||||
// shapes that structurally resemble char-class operators.
|
||||
{
|
||||
range_predicate p(M);
|
||||
arith_util a(m);
|
||||
sort* int_seq = u.str.mk_seq(a.mk_int());
|
||||
sort* int_re = u.re.mk_re(int_seq);
|
||||
check(!regex_to_range_predicate(u, u.re.mk_empty(int_re), p),
|
||||
"re.empty over (Seq Int) is NOT a char class");
|
||||
check(!regex_to_range_predicate(u, u.re.mk_full_char(int_re), p),
|
||||
"re.full_char over (Seq Int) is NOT a char class");
|
||||
}
|
||||
|
||||
// ---- materialization round-trip ----
|
||||
|
||||
// empty -> re.empty
|
||||
{
|
||||
range_predicate p = range_predicate::empty(M);
|
||||
expr_ref e = range_predicate_to_regex(u, p, str_sort);
|
||||
check(u.re.is_empty(e), "empty -> re.empty");
|
||||
}
|
||||
// top -> re.full_char
|
||||
{
|
||||
range_predicate p = range_predicate::top(M);
|
||||
expr_ref e = range_predicate_to_regex(u, p, str_sort);
|
||||
check(u.re.is_full_char(e), "top -> re.full_char");
|
||||
}
|
||||
// single range -> re.range
|
||||
{
|
||||
range_predicate p = range_predicate::range('a', 'z', M);
|
||||
expr_ref e = range_predicate_to_regex(u, p, str_sort);
|
||||
unsigned lo = 0, hi = 0;
|
||||
check(extract_range_chars(u, e, lo, hi) && lo == 'a' && hi == 'z',
|
||||
"[a-z] -> re.range a z");
|
||||
}
|
||||
// singleton -> re.range c c
|
||||
{
|
||||
range_predicate p = range_predicate::singleton('A', M);
|
||||
expr_ref e = range_predicate_to_regex(u, p, str_sort);
|
||||
unsigned lo = 0, hi = 0;
|
||||
check(extract_range_chars(u, e, lo, hi) && lo == 'A' && hi == 'A',
|
||||
"{A} -> re.range A A");
|
||||
}
|
||||
// 2 ranges -> re.union(range_0, range_1) in canonical order
|
||||
{
|
||||
range_predicate p = range_predicate::range('0', '9', M)
|
||||
| range_predicate::range('a', 'z', M);
|
||||
expr_ref e = range_predicate_to_regex(u, p, str_sort);
|
||||
expr* a = nullptr; expr* b = nullptr;
|
||||
check(u.re.is_union(e, a, b), "2-range -> union");
|
||||
unsigned lo0 = 0, hi0 = 0, lo1 = 0, hi1 = 0;
|
||||
check(extract_range_chars(u, a, lo0, hi0) && lo0 == '0' && hi0 == '9',
|
||||
"union arg0 = (0-9) (canonical: lower lo first)");
|
||||
check(extract_range_chars(u, b, lo1, hi1) && lo1 == 'a' && hi1 == 'z',
|
||||
"union arg1 = (a-z)");
|
||||
}
|
||||
// 3 ranges -> right-associated union
|
||||
{
|
||||
range_predicate p = range_predicate::range(0, 5, M)
|
||||
| range_predicate::range(10, 15, M)
|
||||
| range_predicate::range(20, 25, M);
|
||||
expr_ref e = range_predicate_to_regex(u, p, str_sort);
|
||||
expr* a = nullptr; expr* rest = nullptr;
|
||||
check(u.re.is_union(e, a, rest), "3-range -> union(...)");
|
||||
unsigned lo = 0, hi = 0;
|
||||
check(extract_range_chars(u, a, lo, hi) && lo == 0 && hi == 5, "first arg = (0-5)");
|
||||
expr* b = nullptr; expr* c = nullptr;
|
||||
check(u.re.is_union(rest, b, c), "rest is union(...,...)");
|
||||
check(extract_range_chars(u, b, lo, hi) && lo == 10 && hi == 15, "second range");
|
||||
check(extract_range_chars(u, c, lo, hi) && lo == 20 && hi == 25, "third range");
|
||||
}
|
||||
// Round-trip identity for an arbitrary range-set
|
||||
{
|
||||
range_predicate p_in = range_predicate::range('a', 'c', M)
|
||||
| range_predicate::range('m', 'p', M)
|
||||
| range_predicate::range('x', 'z', M);
|
||||
expr_ref e = range_predicate_to_regex(u, p_in, str_sort);
|
||||
range_predicate p_out(M);
|
||||
check(regex_to_range_predicate(u, e, p_out), "round-trip translatable");
|
||||
check(p_in == p_out, "round-trip equal");
|
||||
}
|
||||
|
||||
std::cerr << "regex_range_collapse tests passed\n";
|
||||
}
|
||||
}
|
||||
|
||||
void tst_regex_range_collapse() {
|
||||
run();
|
||||
}
|
||||
127
src/test/seq_regex_bisim.cpp
Normal file
127
src/test/seq_regex_bisim.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// Regression test for the seq::derive::intersect_intervals bug.
|
||||
//
|
||||
// Background: derive uses a path-tracking interval set to compute symbolic
|
||||
// derivatives. The intersect_intervals routine used to react to a single
|
||||
// disjoint interval by dropping the entire kept suffix and skipping the rest
|
||||
// of the list, which silently killed valid branches in derivatives such as
|
||||
// D(a|b). That made the bisimulation procedure conclude bogus equalities
|
||||
// like a* == (a|b)*.
|
||||
//
|
||||
// This file also covers the seq::derive top-level-cache poisoning bug.
|
||||
// `m_top_cache` is keyed only by the regex; the routine used to populate it
|
||||
// while `m_ele` was set to a *concrete* character, baking that character
|
||||
// into the cached "symbolic" derivative. Subsequent calls with the same
|
||||
// regex but a different ele then returned a stale concrete answer instead
|
||||
// of the true symbolic derivative. The simplest victim is
|
||||
// (str.in_re "aP" (re.++ (re.* "a") "P"))
|
||||
// which used to return false because the derivative wrt 'a' was cached and
|
||||
// re-used as the derivative wrt 'P'.
|
||||
#include "ast/ast.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/seq_decl_plugin.h"
|
||||
#include "ast/rewriter/seq_rewriter.h"
|
||||
#include "ast/rewriter/seq_regex_bisim.h"
|
||||
#include "ast/rewriter/th_rewriter.h"
|
||||
#include <iostream>
|
||||
|
||||
static void test_a_star_neq_ab_star() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
seq_util u(m);
|
||||
seq_rewriter rw(m);
|
||||
|
||||
sort_ref str_sort(u.str.mk_string_sort(), m);
|
||||
|
||||
zstring sa("a"), sb("b");
|
||||
expr_ref re_a(u.re.mk_to_re(u.str.mk_string(sa)), m);
|
||||
expr_ref re_b(u.re.mk_to_re(u.str.mk_string(sb)), m);
|
||||
expr_ref a_star(u.re.mk_star(re_a), m);
|
||||
expr_ref ab(u.re.mk_union(re_a, re_b), m);
|
||||
expr_ref ab_star(u.re.mk_star(ab), m);
|
||||
|
||||
expr_ref d_ab = rw.mk_brz_derivative(ab);
|
||||
std::cout << "D(a|b) = " << mk_pp(d_ab, m) << "\n";
|
||||
|
||||
// Both the 'a' branch and the 'b' branch of D(a|b) must reach epsilon.
|
||||
// Collect the regex leaves of the symbolic derivative and require at
|
||||
// least two distinct accepting leaves (one for 'a' and one for 'b').
|
||||
expr_ref_vector leaves(m);
|
||||
auto collect = [&](expr* e, auto&& self) -> void {
|
||||
expr* c, *t, *f;
|
||||
if (m.is_ite(e, c, t, f) || u.re.is_union(e, t, f)) {
|
||||
self(t, self);
|
||||
self(f, self);
|
||||
return;
|
||||
}
|
||||
if (u.re.is_empty(e)) return;
|
||||
leaves.push_back(e);
|
||||
};
|
||||
collect(d_ab, collect);
|
||||
unsigned nullable_leaves = 0;
|
||||
for (expr* l : leaves) {
|
||||
expr_ref n = rw.is_nullable(l);
|
||||
if (m.is_true(n)) ++nullable_leaves;
|
||||
}
|
||||
std::cout << "D(a|b) leaves=" << leaves.size()
|
||||
<< " nullable=" << nullable_leaves << "\n";
|
||||
ENSURE(nullable_leaves >= 2);
|
||||
|
||||
// Bisim must report the two languages are not equivalent.
|
||||
seq::regex_bisim bisim(rw);
|
||||
lbool eq = bisim.are_equivalent(a_star, ab_star);
|
||||
std::cout << "bisim(a*, (a|b)*) = "
|
||||
<< (eq == l_true ? "true" : eq == l_false ? "false" : "undef") << "\n";
|
||||
ENSURE(eq == l_false);
|
||||
}
|
||||
|
||||
// Regression for the derive top-level-cache poisoning bug.
|
||||
// Take r = (re.* "a") ++ "P" and check str.in_re "aP" r. Before the fix
|
||||
// the first per-char derivative call (wrt 'a') populated m_top_cache with
|
||||
// 'a' baked into the symbolic ITE-tree, so the next call (wrt 'P') returned
|
||||
// that stale cached value instead of computing D_P(r) = epsilon, making
|
||||
// str.in_re wrongly return false.
|
||||
static void test_derive_cache_per_ele() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
seq_util u(m);
|
||||
seq_rewriter rw(m);
|
||||
|
||||
sort_ref str_sort(u.str.mk_string_sort(), m);
|
||||
|
||||
zstring sa("a"), sP("P"), s_aP("aP");
|
||||
expr_ref re_a(u.re.mk_to_re(u.str.mk_string(sa)), m);
|
||||
expr_ref re_P(u.re.mk_to_re(u.str.mk_string(sP)), m);
|
||||
expr_ref a_star(u.re.mk_star(re_a), m);
|
||||
expr_ref r(u.re.mk_concat(a_star, re_P), m);
|
||||
expr_ref aP(u.str.mk_string(s_aP), m);
|
||||
|
||||
// Compute D_'a'(a*P) and D_'P'(a*P) directly via mk_derivative.
|
||||
// Before the fix, m_top_cache was populated while m_ele = ele (the
|
||||
// concrete char), so the second call hit the stale cached answer from
|
||||
// the first. After the fix the cache is keyed by a symbolic var, so
|
||||
// each concrete-ele substitution produces the right answer.
|
||||
expr_ref ch_a(u.mk_char('a'), m);
|
||||
expr_ref ch_P(u.mk_char('P'), m);
|
||||
expr_ref d_a = rw.mk_derivative(ch_a, r);
|
||||
expr_ref d_P = rw.mk_derivative(ch_P, r);
|
||||
std::cout << "D_a(a*P) = " << mk_pp(d_a, m) << "\n";
|
||||
std::cout << "D_P(a*P) = " << mk_pp(d_P, m) << "\n";
|
||||
|
||||
// D_P(a*P) must be nullable (it accepts the empty suffix), while
|
||||
// D_a(a*P) must not be (it still needs a trailing 'P').
|
||||
expr_ref n_a = rw.is_nullable(d_a);
|
||||
expr_ref n_P = rw.is_nullable(d_P);
|
||||
th_rewriter trw(m);
|
||||
trw(n_a);
|
||||
trw(n_P);
|
||||
std::cout << "nullable(D_a) = " << mk_pp(n_a, m) << "\n";
|
||||
std::cout << "nullable(D_P) = " << mk_pp(n_P, m) << "\n";
|
||||
ENSURE(m.is_false(n_a));
|
||||
ENSURE(m.is_true(n_P));
|
||||
}
|
||||
|
||||
void tst_seq_regex_bisim() {
|
||||
test_a_star_neq_ab_star();
|
||||
test_derive_cache_per_ele();
|
||||
}
|
||||
171
src/test/seq_rewriter.cpp
Normal file
171
src/test/seq_rewriter.cpp
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
/*++
|
||||
Copyright (c) 2024 Microsoft Corporation
|
||||
|
||||
Regression tests for seq_rewriter smart constructors for regex ranges.
|
||||
|
||||
Tests:
|
||||
1. Empty range (lo > hi) → re.none
|
||||
2. Singleton range (lo == hi) → str.to_re lo
|
||||
3. Range ∩ Range → reduced range or re.none
|
||||
4. Range ∪ Range → merged range for overlapping/adjacent
|
||||
5. Complement of range → one or two ranges
|
||||
6. Downstream operators absorb empty ranges correctly
|
||||
--*/
|
||||
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/rewriter/th_rewriter.h"
|
||||
#include "ast/seq_decl_plugin.h"
|
||||
#include <iostream>
|
||||
|
||||
// Build a single-char string literal expression.
|
||||
static expr_ref mk_str(ast_manager& m, seq_util& su, unsigned c) {
|
||||
return expr_ref(su.str.mk_string(zstring(c)), m);
|
||||
}
|
||||
|
||||
void tst_seq_rewriter() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
th_rewriter rw(m);
|
||||
seq_util su(m);
|
||||
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
|
||||
auto range = [&](unsigned lo, unsigned hi) -> expr_ref {
|
||||
return expr_ref(su.re.mk_range(mk_str(m, su, lo), mk_str(m, su, hi)), m);
|
||||
};
|
||||
|
||||
// Arbitrary regex variable for downstream tests.
|
||||
app_ref R(m.mk_fresh_const("R", re_sort), m);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 1. Empty range (lo > hi) → re.none
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e = range('z', 'a');
|
||||
rw(e);
|
||||
std::cout << "empty range lo>hi: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(su.re.is_empty(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 2. Singleton range (lo == hi) → str.to_re lo
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e = range('a', 'a');
|
||||
rw(e);
|
||||
std::cout << "singleton range: " << mk_pp(e, m) << "\n";
|
||||
expr* inner = nullptr;
|
||||
ENSURE(su.re.is_to_re(e, inner));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 3. Range intersection: overlapping → smaller range
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_inter(range('a', 'z'), range('f', 'k')), m);
|
||||
rw(e);
|
||||
std::cout << "range inter overlapping: " << mk_pp(e, m) << "\n";
|
||||
unsigned lo = 0, hi = 0;
|
||||
ENSURE(su.re.is_range(e, lo, hi) && lo == 'f' && hi == 'k');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 4. Range intersection: disjoint → re.none
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_inter(range('a', 'f'), range('k', 'z')), m);
|
||||
rw(e);
|
||||
std::cout << "range inter disjoint: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(su.re.is_empty(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 5. Range intersection: touching at boundary → singleton (str.to_re "f")
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_inter(range('a', 'f'), range('f', 'z')), m);
|
||||
rw(e);
|
||||
std::cout << "range inter touching: " << mk_pp(e, m) << "\n";
|
||||
expr* inner = nullptr;
|
||||
ENSURE(su.re.is_to_re(e, inner));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 6. Range union: overlapping → merged range
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_union(range('a', 'f'), range('e', 'k')), m);
|
||||
rw(e);
|
||||
std::cout << "range union overlapping: " << mk_pp(e, m) << "\n";
|
||||
unsigned lo = 0, hi = 0;
|
||||
ENSURE(su.re.is_range(e, lo, hi) && lo == 'a' && hi == 'k');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 7. Range union: adjacent → merged range
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_union(range('a', 'f'), range('g', 'k')), m);
|
||||
rw(e);
|
||||
std::cout << "range union adjacent: " << mk_pp(e, m) << "\n";
|
||||
unsigned lo = 0, hi = 0;
|
||||
ENSURE(su.re.is_range(e, lo, hi) && lo == 'a' && hi == 'k');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 8. Range union: disjoint → stays as union
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_union(range('a', 'c'), range('m', 'z')), m);
|
||||
rw(e);
|
||||
std::cout << "range union disjoint (stays as union): " << mk_pp(e, m) << "\n";
|
||||
ENSURE(!su.re.is_range(e));
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 11. Downstream: (re.* (re.range "z" "a")) → str.to_re ""
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_star(range('z', 'a')), m);
|
||||
rw(e);
|
||||
std::cout << "star of empty range: " << mk_pp(e, m) << "\n";
|
||||
expr* inner = nullptr;
|
||||
// star of empty → epsilon (str.to_re "")
|
||||
ENSURE(su.re.is_to_re(e, inner) && su.str.is_empty(inner));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 12. Downstream: concat absorbs empty range → re.none
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_concat(R, su.re.mk_concat(range('z', 'a'), R)), m);
|
||||
rw(e);
|
||||
std::cout << "concat absorbs empty range: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(su.re.is_empty(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 13. Downstream: union absorbs empty range → R
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_union(R, range('z', 'a')), m);
|
||||
rw(e);
|
||||
std::cout << "union absorbs empty range: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(e.get() == R.get());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 14. Downstream: inter absorbs empty range → re.none
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_inter(R, range('z', 'a')), m);
|
||||
rw(e);
|
||||
std::cout << "inter absorbs empty range: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(su.re.is_empty(e));
|
||||
}
|
||||
|
||||
std::cout << "tst_seq_rewriter: all tests passed\n";
|
||||
}
|
||||
|
|
@ -199,7 +199,7 @@ struct test_seq {
|
|||
ptr_vector<expr> const& lhs(expr* eq) {
|
||||
auto& ev = get_eval(eq);
|
||||
if (ev.lhs.empty()) {
|
||||
expr* x, * y;
|
||||
expr* x = nullptr, * y = nullptr;
|
||||
VERIFY(m.is_eq(eq, x, y));
|
||||
seq.str.get_concat(x, ev.lhs);
|
||||
seq.str.get_concat(y, ev.rhs);
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ namespace bv {
|
|||
}
|
||||
|
||||
|
||||
static void test_eval1() {
|
||||
[[maybe_unused]] static void test_eval1() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
bv_util bv(m);
|
||||
|
|
@ -262,7 +262,7 @@ static void test_eval1() {
|
|||
}
|
||||
}
|
||||
|
||||
static void test_repair1() {
|
||||
[[maybe_unused]] static void test_repair1() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
bv_util bv(m);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ Copyright (c) 2015 Microsoft Corporation
|
|||
|
||||
#include "smt/smt_context.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
|
||||
void tst_smt_context()
|
||||
{
|
||||
|
|
@ -34,4 +35,31 @@ void tst_smt_context()
|
|||
}
|
||||
|
||||
ctx.check();
|
||||
|
||||
{
|
||||
arith_util a(m);
|
||||
expr_ref x(m.mk_var(2, a.mk_int()), m);
|
||||
expr_ref x4(m.mk_var(1, a.mk_int()), m);
|
||||
expr_ref y(m.mk_var(0, a.mk_int()), m);
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref two(a.mk_int(2), m);
|
||||
expr_ref_vector conjs(m);
|
||||
conjs.push_back(a.mk_gt(x, y));
|
||||
conjs.push_back(a.mk_gt(zero, x4));
|
||||
conjs.push_back(a.mk_gt(zero, a.mk_uminus(y)));
|
||||
conjs.push_back(a.mk_lt(zero, a.mk_uminus(a.mk_mul(two, y))));
|
||||
expr_ref body(m.mk_and(conjs), m);
|
||||
|
||||
sort* y_sort = a.mk_int();
|
||||
symbol y_name("y");
|
||||
body = m.mk_exists(1, &y_sort, &y_name, body);
|
||||
|
||||
sort* sorts[2] = { a.mk_int(), a.mk_int() };
|
||||
symbol names[2] = { symbol("x"), symbol("x4") };
|
||||
expr_ref q(m.mk_forall(2, sorts, names, body), m);
|
||||
|
||||
smt::context qctx(m, params);
|
||||
qctx.assert_expr(q);
|
||||
VERIFY(l_false == qctx.check());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
309
src/test/term_enumeration.cpp
Normal file
309
src/test/term_enumeration.cpp
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
/*++
|
||||
Copyright (c) 2024 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
tst_term_enumeration.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Test term enumeration module
|
||||
|
||||
--*/
|
||||
|
||||
|
||||
#include "ast/rewriter/term_enumeration.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/bv_decl_plugin.h"
|
||||
#include "ast/array_decl_plugin.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/rewriter/th_rewriter.h"
|
||||
#include "util/obj_hashtable.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
static void tst_basic_enumeration() {
|
||||
std::cout << "=== test basic enumeration ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add some leaf productions (constants)
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
te.add_production(zero);
|
||||
te.add_production(one);
|
||||
|
||||
// Enumerate terms of Int sort
|
||||
sort* int_sort = a.mk_int();
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
std::cout << "Term: " << mk_pp(e, m) << "\n";
|
||||
count++;
|
||||
if (count >= 5) break; // Limit output
|
||||
}
|
||||
|
||||
ENSURE(count >= 2); // At least 0 and 1
|
||||
std::cout << "Enumerated " << count << " terms\n";
|
||||
}
|
||||
|
||||
static void tst_enumeration_with_operators() {
|
||||
std::cout << "=== test enumeration with operators ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add leaf productions
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
te.add_production(zero);
|
||||
te.add_production(one);
|
||||
|
||||
// Add operator productions (+ and *)
|
||||
// Get func_decl by creating an app and extracting the decl
|
||||
app_ref tmp_add(a.mk_add(zero, one), m);
|
||||
app_ref tmp_mul(a.mk_mul(zero, one), m);
|
||||
func_decl* add_decl = tmp_add->get_decl();
|
||||
func_decl* mul_decl = tmp_mul->get_decl();
|
||||
te.add_production(add_decl);
|
||||
te.add_production(mul_decl);
|
||||
|
||||
sort* int_sort = a.mk_int();
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
std::cout << "Term: " << mk_pp(e, m) << "\n";
|
||||
count++;
|
||||
if (count >= 20) break; // Limit output
|
||||
}
|
||||
|
||||
ENSURE(count >= 2); // At least the leaves
|
||||
std::cout << "Enumerated " << count << " terms with operators\n";
|
||||
}
|
||||
|
||||
static void tst_observational_equivalence_filter() {
|
||||
std::cout << "=== test observational equivalence filter ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
th_rewriter rw(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
te.add_production(zero);
|
||||
te.add_production(one);
|
||||
|
||||
app_ref tmp_add(a.mk_add(zero, one), m);
|
||||
te.add_production(tmp_add->get_decl());
|
||||
|
||||
sort* int_sort = a.mk_int();
|
||||
obj_hashtable<expr> seen;
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
expr_ref r(m);
|
||||
rw(e, r);
|
||||
ENSURE(r == e);
|
||||
ENSURE(!seen.contains(r));
|
||||
seen.insert(r);
|
||||
count++;
|
||||
if (count >= 20) break;
|
||||
}
|
||||
|
||||
ENSURE(count >= 2);
|
||||
}
|
||||
|
||||
static void tst_display() {
|
||||
std::cout << "=== test display ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add leaf productions
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
te.add_production(zero);
|
||||
te.add_production(one);
|
||||
|
||||
// Add operator productions
|
||||
app_ref tmp_add(a.mk_add(zero, one), m);
|
||||
func_decl* add_decl = tmp_add->get_decl();
|
||||
te.add_production(add_decl);
|
||||
|
||||
sort* int_sort = a.mk_int();
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
(void)e;
|
||||
count++;
|
||||
if (count >= 10) break;
|
||||
}
|
||||
|
||||
std::cout << "Internal state after enumeration:\n";
|
||||
std::ostringstream oss;
|
||||
te.display(oss);
|
||||
std::cout << oss.str();
|
||||
|
||||
// Verify display produced some output
|
||||
ENSURE(!oss.str().empty());
|
||||
}
|
||||
|
||||
static void tst_bitvector_enumeration() {
|
||||
std::cout << "=== test bitvector enumeration ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
bv_util bv(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add bitvector constants
|
||||
unsigned bv_size = 8;
|
||||
expr_ref bv_zero(bv.mk_numeral(0, bv_size), m);
|
||||
expr_ref bv_one(bv.mk_numeral(1, bv_size), m);
|
||||
te.add_production(bv_zero);
|
||||
te.add_production(bv_one);
|
||||
|
||||
// Add bvadd operator
|
||||
app_ref tmp_add(bv.mk_bv_add(bv_zero, bv_one), m);
|
||||
func_decl* bvadd = tmp_add->get_decl();
|
||||
te.add_production(bvadd);
|
||||
|
||||
sort* bv8 = bv.mk_sort(bv_size);
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(bv8)) {
|
||||
std::cout << "BV Term: " << mk_pp(e, m) << "\n";
|
||||
count++;
|
||||
if (count >= 10) break;
|
||||
}
|
||||
|
||||
ENSURE(count >= 2);
|
||||
std::cout << "Enumerated " << count << " bitvector terms\n";
|
||||
}
|
||||
|
||||
static void tst_multiple_sorts() {
|
||||
std::cout << "=== test multiple sorts ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add Int constants
|
||||
expr_ref i_zero(a.mk_int(0), m);
|
||||
expr_ref i_one(a.mk_int(1), m);
|
||||
te.add_production(i_zero);
|
||||
te.add_production(i_one);
|
||||
|
||||
// Add Real constants
|
||||
expr_ref r_zero(a.mk_real(0), m);
|
||||
expr_ref r_one(a.mk_real(1), m);
|
||||
te.add_production(r_zero);
|
||||
te.add_production(r_one);
|
||||
|
||||
// Enumerate Int terms
|
||||
sort* int_sort = a.mk_int();
|
||||
unsigned int_count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
std::cout << "Int Term: " << mk_pp(e, m) << "\n";
|
||||
int_count++;
|
||||
if (int_count >= 5) break;
|
||||
}
|
||||
|
||||
ENSURE(int_count >= 2);
|
||||
std::cout << "Enumerated " << int_count << " Int terms\n";
|
||||
}
|
||||
|
||||
static void tst_nested_array_enumeration() {
|
||||
std::cout << "=== test nested array enumeration (Array(A, Array(B, A))) ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
array_util arr(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Create uninterpreted sorts A and B
|
||||
sort_ref sort_A(m.mk_uninterpreted_sort(symbol("A")), m);
|
||||
sort_ref sort_B(m.mk_uninterpreted_sort(symbol("B")), m);
|
||||
|
||||
// Create nested array sort: Array(B, A) - arrays indexed by B returning A
|
||||
sort_ref array_B_A(arr.mk_array_sort(sort_B, sort_A), m);
|
||||
|
||||
// Create outer array sort: Array(A, Array(B, A)) - arrays indexed by A returning Array(B,A)
|
||||
sort_ref array_A_arrayBA(arr.mk_array_sort(sort_A, array_B_A), m);
|
||||
|
||||
std::cout << "Sort A: " << mk_pp(sort_A.get(), m) << "\n";
|
||||
std::cout << "Sort B: " << mk_pp(sort_B.get(), m) << "\n";
|
||||
std::cout << "Sort Array(B, A): " << mk_pp(array_B_A.get(), m) << "\n";
|
||||
std::cout << "Sort Array(A, Array(B, A)): " << mk_pp(array_A_arrayBA.get(), m) << "\n";
|
||||
|
||||
// Add constants of sort A
|
||||
app_ref a0(m.mk_const(symbol("a0"), sort_A), m);
|
||||
app_ref a1(m.mk_const(symbol("a1"), sort_A), m);
|
||||
te.add_production(a0);
|
||||
te.add_production(a1);
|
||||
|
||||
// Add constants of sort B
|
||||
app_ref b0(m.mk_const(symbol("b0"), sort_B), m);
|
||||
app_ref b1(m.mk_const(symbol("b1"), sort_B), m);
|
||||
te.add_production(b0);
|
||||
te.add_production(b1);
|
||||
|
||||
// Add a constant array of inner type Array(B, A) - const_array(a0) : Array(B, A)
|
||||
app_ref const_inner(arr.mk_const_array(array_B_A, a0), m);
|
||||
te.add_production(const_inner);
|
||||
|
||||
// Add a constant array of outer type Array(A, Array(B, A))
|
||||
app_ref const_outer(arr.mk_const_array(array_A_arrayBA, const_inner), m);
|
||||
te.add_production(const_outer);
|
||||
|
||||
// Add store operator for the inner array type Array(B, A)
|
||||
// store(array, index, value) : store(Array(B,A), B, A) -> Array(B,A)
|
||||
expr* store_inner_args[3] = { const_inner.get(), b0.get(), a0.get() };
|
||||
app_ref tmp_store_inner(arr.mk_store(3, store_inner_args), m);
|
||||
func_decl* store_inner_decl = tmp_store_inner->get_decl();
|
||||
te.add_production(store_inner_decl);
|
||||
|
||||
// Add store operator for the outer array type Array(A, Array(B, A))
|
||||
// store(array, index, value) : store(Array(A, Array(B,A)), A, Array(B,A)) -> Array(A, Array(B,A))
|
||||
expr* store_outer_args[3] = { const_outer.get(), a0.get(), const_inner.get() };
|
||||
app_ref tmp_store_outer(arr.mk_store(3, store_outer_args), m);
|
||||
func_decl* store_outer_decl = tmp_store_outer->get_decl();
|
||||
te.add_production(store_outer_decl);
|
||||
|
||||
// Add select operator for the outer array (returns Array(B, A))
|
||||
// select(Array(A, Array(B,A)), A) -> Array(B, A)
|
||||
app_ref tmp_select_outer(arr.mk_select(const_outer.get(), a0.get()), m);
|
||||
func_decl* select_outer_decl = tmp_select_outer->get_decl();
|
||||
te.add_production(select_outer_decl);
|
||||
|
||||
// Enumerate terms of the nested array sort Array(A, Array(B, A))
|
||||
std::cout << "\nEnumerating terms of sort Array(A, Array(B, A)):\n";
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(array_A_arrayBA)) {
|
||||
std::cout << " Term " << count << ": " << mk_pp(e, m) << "\n";
|
||||
count++;
|
||||
if (count >= 15) break; // Limit output
|
||||
}
|
||||
|
||||
ENSURE(count >= 1); // At least the constant array
|
||||
std::cout << "Enumerated " << count << " terms of sort Array(A, Array(B, A))\n";
|
||||
|
||||
te.display(std::cout);
|
||||
}
|
||||
|
||||
void tst_term_enumeration() {
|
||||
tst_basic_enumeration();
|
||||
tst_enumeration_with_operators();
|
||||
tst_observational_equivalence_filter();
|
||||
tst_display();
|
||||
tst_bitvector_enumeration();
|
||||
tst_multiple_sorts();
|
||||
tst_nested_array_enumeration();
|
||||
std::cout << "All term_enumeration tests passed!\n";
|
||||
}
|
||||
|
|
@ -111,6 +111,30 @@ R"(tff(c1,conjecture, $let([a: $int, b: $int], [a := 1, b := 2], $less($sum(a,b)
|
|||
"% SZS status Theorem"},
|
||||
{"tff-let-nested",
|
||||
R"(tff(c1,conjecture, $let(a: $int, a := 5, $let(b: $int, b := 3, $less(b,a)))).)",
|
||||
"% SZS status Theorem"},
|
||||
// Parenthesized negation binds only the next literal, not the whole
|
||||
// disjunction: "( ~ p | q )" is "(~p) | q", not "~(p | q)". With p
|
||||
// asserted this is satisfiable (q true); the old whole-formula negation
|
||||
// made it spuriously unsatisfiable.
|
||||
{"fof-paren-negation-precedence",
|
||||
R"(fof(a1,axiom, ( ~ p | q )).
|
||||
fof(a2,axiom, p).)",
|
||||
"% SZS status Satisfiable"},
|
||||
// A TPTP quantifier binds tighter than the binary connectives, so
|
||||
// "! [X] : p(X) => g" is "(! [X] : p(X)) => g". With p(a), ~p(b), ~g the
|
||||
// antecedent is false, so the implication holds (Theorem). The old parse
|
||||
// "! [X] : (p(X) => g)" was false at X=a and reported CounterSatisfiable.
|
||||
{"fof-quantifier-binds-tighter-than-implies",
|
||||
R"(fof(a1,axiom, p(a)).
|
||||
fof(a2,axiom, ~ p(b)).
|
||||
fof(a3,axiom, ~ g).
|
||||
fof(c1,conjecture, ! [X] : p(X) => g).)",
|
||||
"% SZS status Theorem"},
|
||||
// Mixed Int/Real equality must use the arithmetic to_real coercion, not
|
||||
// an uninterpreted boxing function: $to_int(3.0) = 3.0 is valid because
|
||||
// to_real(3) = 3.0. Boxing severed the link and reported CounterSatisfiable.
|
||||
{"tff-int-real-equality-coercion",
|
||||
R"(tff(c1,conjecture, $to_int(3.0) = 3.0).)",
|
||||
"% SZS status Theorem"}
|
||||
};
|
||||
for (auto const& c : cases) {
|
||||
|
|
@ -123,4 +147,51 @@ R"(tff(c1,conjecture, $let(a: $int, a := 5, $let(b: $int, b := 3, $less(b,a)))).
|
|||
unsigned code = run_tptp("tff(c1,conjecture, $less(1/0,1)).", out, err);
|
||||
ENSURE(code == ERR_PARSER);
|
||||
ENSURE(err.find("denominator of rational literal cannot be zero") != std::string::npos);
|
||||
|
||||
// SZS status cross-checking against the annotated input status.
|
||||
|
||||
// Matching annotation: no BUG flag.
|
||||
{
|
||||
std::string o = run_tptp(
|
||||
R"(% Status : Unsatisfiable
|
||||
cnf(c1,axiom, p(X)).
|
||||
cnf(c2,axiom, ~ p(a)).)");
|
||||
ENSURE(o.find("% SZS status Unsatisfiable") != std::string::npos);
|
||||
ENSURE(o.find("BUG") == std::string::npos);
|
||||
}
|
||||
|
||||
// Contradicting annotation (says Satisfiable, z3 finds Unsatisfiable): BUG.
|
||||
{
|
||||
std::string o = run_tptp(
|
||||
R"(% SZS status Satisfiable
|
||||
cnf(c1,axiom, p(X)).
|
||||
cnf(c2,axiom, ~ p(a)).)");
|
||||
ENSURE(o.find("BUG") != std::string::npos);
|
||||
ENSURE(o.find("expected Satisfiable") != std::string::npos);
|
||||
}
|
||||
|
||||
// Contradicting annotation (says Unsatisfiable, z3 finds Satisfiable): BUG.
|
||||
{
|
||||
std::string o = run_tptp(
|
||||
R"(% Status : Unsatisfiable
|
||||
fof(a1,axiom, p(a)).)");
|
||||
ENSURE(o.find("BUG") != std::string::npos);
|
||||
}
|
||||
|
||||
// Theorem annotation matches z3's Theorem verdict for conjectures: no BUG.
|
||||
{
|
||||
std::string o = run_tptp(
|
||||
R"(% SZS status Theorem
|
||||
fof(a1,axiom, ! [X] : (human(X) => mortal(X))).
|
||||
fof(a2,axiom, human(socrates)).
|
||||
fof(c1,conjecture, mortal(socrates)).)");
|
||||
ENSURE(o.find("% SZS status Theorem") != std::string::npos);
|
||||
ENSURE(o.find("BUG") == std::string::npos);
|
||||
}
|
||||
|
||||
// Unannotated input: nothing to check, no BUG.
|
||||
{
|
||||
std::string o = run_tptp("fof(a1,axiom, p(a)).");
|
||||
ENSURE(o.find("BUG") == std::string::npos);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue