mirror of
https://github.com/Z3Prover/z3
synced 2026-07-16 03:55:42 +00:00
Replaced stabilizers by landing decomposition (faster!)
Rank membership constraints by estimated size of their automaton Some refactoring Some bug fixes
This commit is contained in:
parent
5fc81bd1ae
commit
596cc14e83
18 changed files with 996 additions and 1316 deletions
|
|
@ -71,227 +71,6 @@ static void test_seq_regex_is_full() {
|
|||
std::cout << " ok: re.all not recognized as empty\n";
|
||||
}
|
||||
|
||||
// Test 4: strengthened_stabilizer — null safety
|
||||
static void test_strengthened_stabilizer_null() {
|
||||
std::cout << "test_strengthened_stabilizer_null\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
|
||||
SASSERT(nr.strengthened_stabilizer(nullptr, nullptr) == nullptr);
|
||||
|
||||
seq_util su(m);
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
|
||||
SASSERT(nr.strengthened_stabilizer(full_re, nullptr) == nullptr);
|
||||
SASSERT(nr.strengthened_stabilizer(nullptr, full_re) == nullptr);
|
||||
std::cout << " ok\n";
|
||||
}
|
||||
|
||||
// Test 5: strengthened_stabilizer — single char cycle on a*
|
||||
// Regex a*, history = 'a'. D('a', a*) = a* (sub-cycle back to start).
|
||||
// Stabilizer body should be to_re("a").
|
||||
static void test_strengthened_stabilizer_single_char() {
|
||||
std::cout << "test_strengthened_stabilizer_single_char\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
// Build a*
|
||||
const expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode const* re_star_a = sg.mk(star_a);
|
||||
|
||||
// Build history = char 'a' (single token, no concat needed)
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* history = tok_a;
|
||||
|
||||
euf::snode const* result = nr.strengthened_stabilizer(re_star_a, history);
|
||||
// Should produce a non-null stabilizer body (to_re("a"))
|
||||
SASSERT(result != nullptr);
|
||||
std::cout << " ok: a* with history 'a' -> non-null stabilizer\n";
|
||||
}
|
||||
|
||||
// Test 6: strengthened_stabilizer — two-char cycle with sub-cycle
|
||||
// Regex (ab)*, history = 'a', 'b'. D('a', (ab)*) = b(ab)*, D('b', b(ab)*) = (ab)*
|
||||
// This should detect a sub-cycle and build a stabilizer body.
|
||||
static void test_strengthened_stabilizer_two_char() {
|
||||
std::cout << "test_strengthened_stabilizer_two_char\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
// Build (ab)*
|
||||
const expr_ref ab(su.re.mk_to_re(su.str.mk_string("ab")), m);
|
||||
const expr_ref star_ab(su.re.mk_star(ab), m);
|
||||
euf::snode const* re_star_ab = sg.mk(star_ab);
|
||||
|
||||
// Build history: concat(char_a, char_b) using string concat
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* tok_b = sg.mk_char('b');
|
||||
euf::snode const* history = sg.mk_concat(tok_a, tok_b);
|
||||
|
||||
euf::snode const* result = nr.strengthened_stabilizer(re_star_ab, history);
|
||||
// Should produce a non-null stabilizer body
|
||||
SASSERT(result != nullptr);
|
||||
std::cout << " ok: (ab)* with history 'ab' -> non-null stabilizer\n";
|
||||
}
|
||||
|
||||
// Test 7: get_filtered_stabilizer_star — no stabilizers registered
|
||||
static void test_filtered_stabilizer_star_empty() {
|
||||
std::cout << "test_filtered_stabilizer_star_empty\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
const seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
|
||||
euf::snode const* result = nr.get_filtered_stabilizer_star(full_re, tok_a);
|
||||
SASSERT(result == nullptr);
|
||||
std::cout << " ok: no stabilizers -> nullptr\n";
|
||||
}
|
||||
|
||||
// Test 8: get_filtered_stabilizer_star — with registered stabilizer that passes filter
|
||||
static void test_filtered_stabilizer_star_with_stab() {
|
||||
std::cout << "test_filtered_stabilizer_star_with_stab\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
// Build a* as the regex state
|
||||
const expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode const* re_star_a = sg.mk(star_a);
|
||||
|
||||
// Register a stabilizer: to_re("b") — only accepts "b"
|
||||
const expr_ref stab_b(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
euf::snode const* stab_b_sn = sg.mk(stab_b);
|
||||
nr.add_stabilizer(re_star_a, stab_b_sn);
|
||||
|
||||
// Exclude char 'a': D('a', to_re("b")) should be fail
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* result = nr.get_filtered_stabilizer_star(re_star_a, tok_a);
|
||||
// to_re("b") should pass the filter → result is star(to_re("b"))
|
||||
SASSERT(result != nullptr);
|
||||
SASSERT(result->is_star());
|
||||
std::cout << " ok: filter keeps to_re('b') when excluding 'a'\n";
|
||||
}
|
||||
|
||||
// Test 9: get_filtered_stabilizer_star — stabilizer filtered out
|
||||
static void test_filtered_stabilizer_star_filtered() {
|
||||
std::cout << "test_filtered_stabilizer_star_filtered\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
// Build a* as the regex state
|
||||
const expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode const* re_star_a = sg.mk(star_a);
|
||||
|
||||
// Register a stabilizer: to_re("a") — accepts "a"
|
||||
const expr_ref stab_a(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode const* stab_a_sn = sg.mk(stab_a);
|
||||
nr.add_stabilizer(re_star_a, stab_a_sn);
|
||||
|
||||
// Exclude char 'a': D('a', to_re("a")) is NOT fail → filtered out
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* result = nr.get_filtered_stabilizer_star(re_star_a, tok_a);
|
||||
SASSERT(result == nullptr);
|
||||
std::cout << " ok: filter removes to_re('a') when excluding 'a'\n";
|
||||
}
|
||||
|
||||
// Test 10: extract_cycle_history — basic extraction
|
||||
static void test_extract_cycle_history_basic() {
|
||||
std::cout << "test_extract_cycle_history_basic\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* tok_b = sg.mk_char('b');
|
||||
euf::snode const* tok_c = sg.mk_char('c');
|
||||
|
||||
// Ancestor history: just 'a' (length 1)
|
||||
euf::snode const* anc_hist = tok_a;
|
||||
|
||||
// Current history: concat(concat(a, b), c) = a,b,c (length 3)
|
||||
euf::snode const* cur_hist = sg.mk_concat(sg.mk_concat(tok_a, tok_b), tok_c);
|
||||
|
||||
euf::snode const* empty_str = sg.mk_empty_seq(str_sort);
|
||||
const seq::dep_tracker empty_dep = nullptr;
|
||||
|
||||
const seq::str_mem ancestor(m, empty_str, full_re, empty_dep);
|
||||
const seq::str_mem current(m, empty_str, full_re, empty_dep);
|
||||
|
||||
euf::snode const* cycle = nr.extract_cycle_history(current, ancestor);
|
||||
// Should return the last 2 tokens (b, c)
|
||||
SASSERT(cycle != nullptr);
|
||||
SASSERT(cycle->length() == 2);
|
||||
std::cout << " ok: extracted cycle of length 2\n";
|
||||
}
|
||||
|
||||
// Test 11: extract_cycle_history — null ancestor history
|
||||
static void test_extract_cycle_history_null_ancestor() {
|
||||
std::cout << "test_extract_cycle_history_null_ancestor\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* tok_b = sg.mk_char('b');
|
||||
euf::snode const* cur_hist = sg.mk_concat(tok_a, tok_b);
|
||||
euf::snode const* empty_str = sg.mk_empty_seq(str_sort);
|
||||
const seq::dep_tracker empty_dep = nullptr;
|
||||
|
||||
// Ancestor has no history (nullptr)
|
||||
const seq::str_mem ancestor(m, empty_str, full_re, empty_dep);
|
||||
const seq::str_mem current(m, empty_str, full_re, empty_dep);
|
||||
|
||||
euf::snode const* cycle = nr.extract_cycle_history(current, ancestor);
|
||||
// With null ancestor history, entire current history is the cycle
|
||||
SASSERT(cycle != nullptr);
|
||||
SASSERT(cycle->length() == 2);
|
||||
std::cout << " ok: null ancestor -> full history as cycle\n";
|
||||
}
|
||||
|
||||
// Test 12: BFS emptiness — re.none (empty language) is empty
|
||||
static void test_bfs_empty_none() {
|
||||
std::cout << "test_bfs_empty_none\n";
|
||||
|
|
@ -490,63 +269,6 @@ static void test_char_set_is_subset() {
|
|||
std::cout << " ok\n";
|
||||
}
|
||||
|
||||
// Test: stabilizer store basic operations
|
||||
static void test_stabilizer_store_basic() {
|
||||
std::cout << "test_stabilizer_store_basic\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
euf::snode const* a_sn = sg.mk(a_re);
|
||||
euf::snode const* b_sn = sg.mk(b_re);
|
||||
|
||||
SASSERT(!nr.has_stabilizers(a_sn));
|
||||
nr.add_stabilizer(a_sn, b_sn);
|
||||
SASSERT(nr.has_stabilizers(a_sn));
|
||||
SASSERT(nr.get_stabilizer_union(a_sn) == b_sn);
|
||||
|
||||
// dedup: adding same stabilizer again
|
||||
nr.add_stabilizer(a_sn, b_sn);
|
||||
auto* stabs = nr.get_stabilizers(a_sn);
|
||||
SASSERT(stabs && stabs->size() == 1);
|
||||
|
||||
// reset
|
||||
nr.reset_stabilizers();
|
||||
SASSERT(!nr.has_stabilizers(a_sn));
|
||||
|
||||
std::cout << " ok\n";
|
||||
}
|
||||
|
||||
// Test: self-stabilizing flag
|
||||
static void test_self_stabilizing() {
|
||||
std::cout << "test_self_stabilizing\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode const* a_sn = sg.mk(a_re);
|
||||
|
||||
SASSERT(!nr.is_self_stabilizing(a_sn));
|
||||
nr.set_self_stabilizing(a_sn);
|
||||
SASSERT(nr.is_self_stabilizing(a_sn));
|
||||
|
||||
// star should be detected as self-stabilizing
|
||||
const expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
euf::snode const* star_sn = sg.mk(star_a);
|
||||
SASSERT(nr.compute_self_stabilizing(star_sn));
|
||||
|
||||
std::cout << " ok\n";
|
||||
}
|
||||
|
||||
// Test: check_intersection_emptiness — SAT case
|
||||
static void test_check_intersection_sat() {
|
||||
std::cout << "test_check_intersection_sat\n";
|
||||
|
|
@ -600,85 +322,6 @@ static void test_check_intersection_unsat() {
|
|||
std::cout << " ok: to_re(a) ∩ to_re(b) is empty\n";
|
||||
}
|
||||
|
||||
// Test: is_language_subset — true case
|
||||
static void test_is_language_subset_true() {
|
||||
std::cout << "test_is_language_subset_true\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
// a* ⊆ (a|b)* should be true
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
const expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
const expr_ref star_ab(su.re.mk_star(ab_union), m);
|
||||
|
||||
euf::snode const* subset = sg.mk(star_a);
|
||||
euf::snode const* superset = sg.mk(star_ab);
|
||||
|
||||
const lbool result = nr.is_language_subset(subset, superset);
|
||||
SASSERT(result == l_true);
|
||||
std::cout << " ok: a* ⊆ (a|b)*\n";
|
||||
}
|
||||
|
||||
// Test: is_language_subset — false case
|
||||
static void test_is_language_subset_false() {
|
||||
std::cout << "test_is_language_subset_false\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
// (a|b)* ⊄ a* should be false (b ∈ (a|b)* but b ∉ a*)
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
const expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
const expr_ref star_ab(su.re.mk_star(ab_union), m);
|
||||
|
||||
euf::snode const* subset = sg.mk(star_ab);
|
||||
euf::snode const* superset = sg.mk(star_a);
|
||||
|
||||
const lbool result = nr.is_language_subset(subset, superset);
|
||||
SASSERT(result == l_false);
|
||||
std::cout << " ok: (a|b)* ⊄ a*\n";
|
||||
}
|
||||
|
||||
// Test: is_language_subset — trivial cases
|
||||
static void test_is_language_subset_trivial() {
|
||||
std::cout << "test_is_language_subset_trivial\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
|
||||
// ∅ ⊆ anything = true
|
||||
const expr_ref none(su.re.mk_empty(su.re.mk_re(str_sort)), m);
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode const* empty_sn = sg.mk(none);
|
||||
euf::snode const* a_sn = sg.mk(a_re);
|
||||
SASSERT(nr.is_language_subset(empty_sn, a_sn) == l_true);
|
||||
|
||||
// anything ⊆ Σ* = true
|
||||
const expr_ref full(su.re.mk_full_seq(su.re.mk_re(str_sort)), m);
|
||||
euf::snode const* full_sn = sg.mk(full);
|
||||
SASSERT(nr.is_language_subset(a_sn, full_sn) == l_true);
|
||||
|
||||
// L ⊆ L = true (same pointer)
|
||||
SASSERT(nr.is_language_subset(a_sn, a_sn) == l_true);
|
||||
|
||||
std::cout << " ok\n";
|
||||
}
|
||||
|
||||
// Regression test: representative chosen from bounds must respect accumulated excludes.
|
||||
// Example language is [A-Z] \ {"A"}, so a valid witness exists (e.g., "B") but not "A".
|
||||
static void test_some_seq_in_re_excluded_low_regression() {
|
||||
|
|
@ -815,14 +458,6 @@ void tst_seq_regex() {
|
|||
test_seq_regex_instantiation();
|
||||
test_seq_regex_is_empty();
|
||||
test_seq_regex_is_full();
|
||||
test_strengthened_stabilizer_null();
|
||||
test_strengthened_stabilizer_single_char();
|
||||
test_strengthened_stabilizer_two_char();
|
||||
test_filtered_stabilizer_star_empty();
|
||||
test_filtered_stabilizer_star_with_stab();
|
||||
test_filtered_stabilizer_star_filtered();
|
||||
test_extract_cycle_history_basic();
|
||||
test_extract_cycle_history_null_ancestor();
|
||||
test_bfs_empty_none();
|
||||
test_bfs_nonempty_full();
|
||||
test_bfs_nonempty_to_re();
|
||||
|
|
@ -832,13 +467,8 @@ void tst_seq_regex() {
|
|||
test_bfs_empty_complement_full();
|
||||
// New tests for regex membership completion
|
||||
test_char_set_is_subset();
|
||||
test_stabilizer_store_basic();
|
||||
test_self_stabilizing();
|
||||
test_check_intersection_sat();
|
||||
test_check_intersection_unsat();
|
||||
test_is_language_subset_true();
|
||||
test_is_language_subset_false();
|
||||
test_is_language_subset_trivial();
|
||||
test_some_seq_in_re_excluded_low_regression();
|
||||
test_some_seq_in_re_inter_loop_regression();
|
||||
// test_bfs_null_safety has a pre-existing failure, run it last
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue