3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-03-07 13:54:53 +00:00
z3/src/test/seq_nielsen.cpp
copilot-swe-agent[bot] 7c328647de Move seq_nielsen from src/ast/rewriter to src/smt/seq with new smt_seq component
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
2026-03-03 00:17:10 +00:00

466 lines
13 KiB
C++

/*++
Copyright (c) 2026 Microsoft Corporation
Module Name:
seq_nielsen.cpp
Abstract:
Unit tests for the Nielsen graph framework (seq_nielsen.h).
Tests constraint types, node/edge construction, substitution
application, and graph population.
--*/
#include "util/util.h"
#include "ast/euf/euf_sgraph.h"
#include "smt/seq/seq_nielsen.h"
#include "ast/reg_decl_plugins.h"
#include "ast/ast_pp.h"
#include <iostream>
// test dep_tracker basic operations
static void test_dep_tracker() {
std::cout << "test_dep_tracker\n";
// empty tracker
seq::dep_tracker d0;
SASSERT(d0.empty());
// tracker with one bit set
seq::dep_tracker d1(8, 3);
SASSERT(!d1.empty());
// tracker with another bit
seq::dep_tracker d2(8, 5);
SASSERT(!d2.empty());
// merge
seq::dep_tracker d3 = d1;
d3.merge(d2);
SASSERT(!d3.empty());
SASSERT(d3.is_superset(d1));
SASSERT(d3.is_superset(d2));
SASSERT(!d1.is_superset(d2));
// equality
seq::dep_tracker d4(8, 3);
SASSERT(d1 == d4);
SASSERT(d1 != d2);
}
// test str_eq constraint creation and operations
static void test_str_eq() {
std::cout << "test_str_eq\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
euf::snode* e = sg.mk_empty();
seq::dep_tracker dep(4, 0);
// basic equality
seq::str_eq eq1(x, y, dep);
SASSERT(!eq1.is_trivial());
SASSERT(eq1.contains_var(x));
SASSERT(eq1.contains_var(y));
SASSERT(!eq1.contains_var(a));
// trivial equality: same node
seq::str_eq eq2(x, x, dep);
SASSERT(eq2.is_trivial());
// trivial equality: both empty
seq::str_eq eq3(e, e, dep);
SASSERT(eq3.is_trivial());
// sorting: lower id first
seq::str_eq eq4(y, x, dep);
eq4.sort();
SASSERT(eq4.m_lhs->id() <= eq4.m_rhs->id());
// contains_var with concat
euf::snode* xa = sg.mk_concat(x, a);
seq::str_eq eq5(xa, y, dep);
SASSERT(eq5.contains_var(x));
SASSERT(eq5.contains_var(y));
SASSERT(!eq5.contains_var(e));
}
// test str_mem constraint creation and operations
static void test_str_mem() {
std::cout << "test_str_mem\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq_util seq(m);
sort_ref str_sort(seq.str.mk_string_sort(), m);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* e = sg.mk_empty();
// create a regex: re.all (.*)
expr_ref star_fc(seq.re.mk_full_seq(str_sort), m);
euf::snode* regex = sg.mk(star_fc);
seq::dep_tracker dep(4, 1);
seq::str_mem mem(x, regex, e, 0, dep);
// x in regex is primitive (x is a single variable)
SASSERT(mem.is_primitive());
SASSERT(mem.contains_var(x));
// concatenation is not primitive
euf::snode* a = sg.mk_char('A');
euf::snode* xa = sg.mk_concat(x, a);
seq::str_mem mem2(xa, regex, e, 1, dep);
SASSERT(!mem2.is_primitive());
SASSERT(mem2.contains_var(x));
}
// test nielsen_subst
static void test_nielsen_subst() {
std::cout << "test_nielsen_subst\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
euf::snode* e = sg.mk_empty();
seq::dep_tracker dep;
// eliminating substitution: x -> A (x does not appear in A)
seq::nielsen_subst s1(x, a, dep);
SASSERT(s1.is_eliminating());
// eliminating substitution: x -> empty
seq::nielsen_subst s2(x, e, dep);
SASSERT(s2.is_eliminating());
// non-eliminating substitution: x -> concat(A, x)
euf::snode* ax = sg.mk_concat(a, x);
seq::nielsen_subst s3(x, ax, dep);
SASSERT(!s3.is_eliminating());
// eliminating substitution: x -> y (x not in y)
seq::nielsen_subst s4(x, y, dep);
SASSERT(s4.is_eliminating());
}
// test nielsen_node creation and constraint management
static void test_nielsen_node() {
std::cout << "test_nielsen_node\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq_util seq(m);
sort_ref str_sort(seq.str.mk_string_sort(), m);
seq::nielsen_graph ng(sg);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
seq::nielsen_node* root = ng.mk_node();
SASSERT(root->id() == 0);
SASSERT(root->str_eqs().empty());
SASSERT(root->str_mems().empty());
SASSERT(root->is_progress());
SASSERT(root->reason() == seq::backtrack_reason::unevaluated);
// add constraints
seq::dep_tracker dep;
root->add_str_eq(seq::str_eq(x, y, dep));
root->add_str_eq(seq::str_eq(sg.mk_concat(x, a), sg.mk_concat(a, y), dep));
SASSERT(root->str_eqs().size() == 2);
// regex membership
expr_ref re_all(seq.re.mk_full_seq(str_sort), m);
euf::snode* regex = sg.mk(re_all);
euf::snode* empty = sg.mk_empty();
root->add_str_mem(seq::str_mem(x, regex, empty, 0, dep));
SASSERT(root->str_mems().size() == 1);
// clone from parent
seq::nielsen_node* child = ng.mk_node();
child->clone_from(*root);
SASSERT(child->str_eqs().size() == 2);
SASSERT(child->str_mems().size() == 1);
SASSERT(child->id() == 1);
}
// test nielsen_edge creation
static void test_nielsen_edge() {
std::cout << "test_nielsen_edge\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq::nielsen_graph ng(sg);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
// create parent and child nodes
seq::nielsen_node* parent = ng.mk_node();
seq::dep_tracker dep;
parent->add_str_eq(seq::str_eq(x, y, dep));
seq::nielsen_node* child = ng.mk_child(parent);
// create edge with substitution x -> A
seq::nielsen_edge* edge = ng.mk_edge(parent, child, true);
edge->add_subst(seq::nielsen_subst(x, a, dep));
SASSERT(edge->src() == parent);
SASSERT(edge->tgt() == child);
SASSERT(edge->is_progress());
SASSERT(edge->subst().size() == 1);
SASSERT(parent->outgoing().size() == 1);
SASSERT(parent->outgoing()[0] == edge);
}
// test nielsen_graph population from external constraints
static void test_nielsen_graph_populate() {
std::cout << "test_nielsen_graph_populate\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq_util seq(m);
sort_ref str_sort(seq.str.mk_string_sort(), m);
seq::nielsen_graph ng(sg);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
// add string equality: x = y
ng.add_str_eq(x, y);
SASSERT(ng.root() != nullptr);
SASSERT(ng.root()->str_eqs().size() == 1);
SASSERT(ng.num_nodes() == 1);
// add regex membership: x in .*
expr_ref re_all(seq.re.mk_full_seq(str_sort), m);
euf::snode* regex = sg.mk(re_all);
ng.add_str_mem(x, regex);
SASSERT(ng.root()->str_mems().size() == 1);
SASSERT(ng.root()->str_mems()[0].m_id == 0);
// add another equality: concat(x, A) = concat(A, y)
euf::snode* xa = sg.mk_concat(x, a);
euf::snode* ay = sg.mk_concat(a, y);
ng.add_str_eq(xa, ay);
SASSERT(ng.root()->str_eqs().size() == 2);
// display for visual inspection
ng.display(std::cout);
}
// test substitution application on nielsen_node
static void test_nielsen_subst_apply() {
std::cout << "test_nielsen_subst_apply\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq_util seq(m);
sort_ref str_sort(seq.str.mk_string_sort(), m);
seq::nielsen_graph ng(sg);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
euf::snode* b = sg.mk_char('B');
euf::snode* e = sg.mk_empty();
// create node with constraint: concat(x, A) = concat(B, y)
seq::nielsen_node* node = ng.mk_node();
seq::dep_tracker dep;
euf::snode* xa = sg.mk_concat(x, a);
euf::snode* by = sg.mk_concat(b, y);
node->add_str_eq(seq::str_eq(xa, by, dep));
// apply substitution x -> empty
seq::nielsen_subst s(x, e, dep);
node->apply_subst(sg, s);
// after x -> empty: lhs should be just A, rhs still concat(B, y)
SASSERT(node->str_eqs().size() == 1);
auto const& eq = node->str_eqs()[0];
// a should remain (after x replaced with empty, concat(empty, A) = A)
std::cout << " lhs len=" << eq.m_lhs->length() << " rhs len=" << eq.m_rhs->length() << "\n";
}
// test Nielsen graph reset
static void test_nielsen_graph_reset() {
std::cout << "test_nielsen_graph_reset\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq::nielsen_graph ng(sg);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
ng.add_str_eq(x, y);
SASSERT(ng.num_nodes() == 1);
SASSERT(ng.root() != nullptr);
ng.reset();
SASSERT(ng.num_nodes() == 0);
SASSERT(ng.root() == nullptr);
}
// test constructing a basic Nielsen expansion tree
// x = Ay: split into x -> eps (progress) or x -> Ax (non-progress)
static void test_nielsen_expansion() {
std::cout << "test_nielsen_expansion\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq::nielsen_graph ng(sg);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
euf::snode* ay = sg.mk_concat(a, y);
// root: x = Ay
ng.add_str_eq(x, ay);
seq::nielsen_node* root = ng.root();
SASSERT(root->str_eqs().size() == 1);
seq::dep_tracker dep;
// branch 1: x -> eps (eliminating, progress)
euf::snode* e = sg.mk_empty();
seq::nielsen_node* child1 = ng.mk_child(root);
seq::nielsen_subst s1(x, e, dep);
child1->apply_subst(sg, s1);
seq::nielsen_edge* edge1 = ng.mk_edge(root, child1, true);
edge1->add_subst(s1);
// branch 2: x -> Ax (non-eliminating, non-progress)
euf::snode* ax = sg.mk_concat(a, x);
seq::nielsen_node* child2 = ng.mk_child(root);
seq::nielsen_subst s2(x, ax, dep);
child2->apply_subst(sg, s2);
seq::nielsen_edge* edge2 = ng.mk_edge(root, child2, false);
edge2->add_subst(s2);
SASSERT(ng.num_nodes() == 3);
SASSERT(root->outgoing().size() == 2);
SASSERT(edge1->is_progress());
SASSERT(!edge2->is_progress());
// verify substitution effects on child1: eps = Ay becomes empty = Ay
SASSERT(child1->str_eqs().size() == 1);
ng.display(std::cout);
}
// test run index management
static void test_run_idx() {
std::cout << "test_run_idx\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq::nielsen_graph ng(sg);
SASSERT(ng.run_idx() == 0);
ng.inc_run_idx();
SASSERT(ng.run_idx() == 1);
ng.inc_run_idx();
SASSERT(ng.run_idx() == 2);
}
// test multiple regex memberships
static void test_multiple_memberships() {
std::cout << "test_multiple_memberships\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq_util seq(m);
sort_ref str_sort(seq.str.mk_string_sort(), m);
seq::nielsen_graph ng(sg);
euf::snode* x = sg.mk_var(symbol("x"));
// x in .*
expr_ref re_all(seq.re.mk_full_seq(str_sort), m);
euf::snode* regex1 = sg.mk(re_all);
ng.add_str_mem(x, regex1);
// x in re.union(to_re("A"), to_re("B"))
expr_ref ch_a(seq.str.mk_char('A'), m);
expr_ref unit_a(seq.str.mk_unit(ch_a), m);
expr_ref ch_b(seq.str.mk_char('B'), m);
expr_ref unit_b(seq.str.mk_unit(ch_b), m);
expr_ref to_re_a(seq.re.mk_to_re(unit_a), m);
expr_ref to_re_b(seq.re.mk_to_re(unit_b), m);
expr_ref re_union(seq.re.mk_union(to_re_a, to_re_b), m);
euf::snode* regex2 = sg.mk(re_union);
ng.add_str_mem(x, regex2);
SASSERT(ng.root() != nullptr);
SASSERT(ng.root()->str_mems().size() == 2);
SASSERT(ng.root()->str_mems()[0].m_id == 0);
SASSERT(ng.root()->str_mems()[1].m_id == 1);
ng.display(std::cout);
}
// test backedge setting (cycle detection support)
static void test_backedge() {
std::cout << "test_backedge\n";
ast_manager m;
reg_decl_plugins(m);
euf::sgraph sg(m);
seq::nielsen_graph ng(sg);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
ng.add_str_eq(x, y);
seq::nielsen_node* root = ng.root();
seq::nielsen_node* child = ng.mk_child(root);
// set backedge from child to root (cycle)
child->set_backedge(root);
SASSERT(child->backedge() == root);
SASSERT(root->backedge() == nullptr);
}
void tst_seq_nielsen() {
test_dep_tracker();
test_str_eq();
test_str_mem();
test_nielsen_subst();
test_nielsen_node();
test_nielsen_edge();
test_nielsen_graph_populate();
test_nielsen_subst_apply();
test_nielsen_graph_reset();
test_nielsen_expansion();
test_run_idx();
test_multiple_memberships();
test_backedge();
}