3
0
Fork 0
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:
CEisenhofer 2026-07-01 17:18:21 +02:00
commit 706f62286e
199 changed files with 11004 additions and 4584 deletions

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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";
}

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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
View 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();
}

View file

@ -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;

View file

@ -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) \

View file

@ -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";
}
}

View file

@ -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());
}
}

View file

@ -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)

View file

@ -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;

View file

@ -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
View 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

View 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";
}

View 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();
}

View 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
View 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";
}

View file

@ -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);

View file

@ -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);

View file

@ -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());
}
}

View 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";
}

View file

@ -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);
}
}