mirror of
https://github.com/Z3Prover/z3
synced 2025-04-28 03:15:50 +00:00
490 lines
16 KiB
C++
490 lines
16 KiB
C++
/*++
|
|
Copyright (c) 2015 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
symbolic_automata_def.h
|
|
|
|
Abstract:
|
|
|
|
Symbolic Automata over Boolean Algebras, a la Margus Veanes Automata library.
|
|
|
|
Author:
|
|
|
|
Nikolaj Bjorner (nbjorner) 2016-02-27.
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
|
|
#pragma once
|
|
|
|
|
|
#include "math/automata/symbolic_automata.h"
|
|
#include "util/hashtable.h"
|
|
#include "util/vector.h"
|
|
|
|
|
|
|
|
|
|
template<class T, class M>
|
|
typename symbolic_automata<T, M>::automaton_t* symbolic_automata<T, M>::mk_total(automaton_t& a) {
|
|
unsigned dead_state = a.num_states();
|
|
moves_t mvs, new_mvs;
|
|
for (unsigned i = 0; i < dead_state; ++i) {
|
|
mvs.reset();
|
|
a.get_moves_from(i, mvs, true);
|
|
refs_t vs(m);
|
|
|
|
for (unsigned j = 0; j < mvs.size(); ++j) {
|
|
vs.push_back(mvs[j].t());
|
|
}
|
|
ref_t cond(m_ba.mk_not(m_ba.mk_or(vs.size(), vs.data())), m);
|
|
lbool is_sat = m_ba.is_sat(cond);
|
|
if (is_sat == l_undef) {
|
|
return nullptr;
|
|
}
|
|
if (is_sat == l_true) {
|
|
new_mvs.push_back(move_t(m, i, dead_state, cond));
|
|
}
|
|
}
|
|
if (new_mvs.empty()) {
|
|
return a.clone();
|
|
}
|
|
new_mvs.push_back(move_t(m, dead_state, dead_state, m_ba.mk_true()));
|
|
|
|
// TBD private: automaton_t::append_moves(0, a, new_mvs);
|
|
|
|
return alloc(automaton_t, m, a.init(), a.final_states(), new_mvs);
|
|
}
|
|
|
|
template<class T, class M>
|
|
typename symbolic_automata<T, M>::automaton_t* symbolic_automata<T, M>::mk_minimize(automaton_t& a) {
|
|
if (a.is_empty()) {
|
|
return a.clone();
|
|
}
|
|
|
|
if (a.is_epsilon()) {
|
|
return a.clone();
|
|
}
|
|
// SASSERT(a.is_deterministic());
|
|
|
|
scoped_ptr<automaton_t> fa = mk_total(a);
|
|
if (!fa) {
|
|
return 0;
|
|
}
|
|
return mk_minimize_total(*fa.get());
|
|
}
|
|
|
|
|
|
template<class T, class M>
|
|
void symbolic_automata<T, M>::add_block(block const& p1, unsigned p0_index, unsigned_vector& blocks, vector<block>& pblocks, unsigned_vector& W) {
|
|
block& p0 = pblocks[p0_index];
|
|
if (p1.size() < p0.size()) {
|
|
unsigned p1_index = pblocks.size();
|
|
pblocks.push_back(p1);
|
|
for (uint_set::iterator it = p1.begin(), end = p1.end(); it != end; ++it) {
|
|
p0.remove(*it);
|
|
blocks[*it] = p1_index;
|
|
}
|
|
if (W.contains(p0_index)) {
|
|
W.push_back(p1_index);
|
|
}
|
|
else if (p0.size() <= p1.size()) {
|
|
W.push_back(p0_index);
|
|
}
|
|
else {
|
|
W.push_back(p1_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class T, class M>
|
|
typename symbolic_automata<T, M>::automaton_t* symbolic_automata<T, M>::mk_minimize_total(automaton_t& a) {
|
|
vector<block> pblocks;
|
|
unsigned_vector blocks;
|
|
unsigned_vector non_final;
|
|
for (unsigned i = 0; i < a.num_states(); ++i) {
|
|
if (!a.is_final_state(i)) {
|
|
non_final.push_back(i);
|
|
blocks.push_back(1);
|
|
}
|
|
else {
|
|
blocks.push_back(0);
|
|
}
|
|
}
|
|
pblocks.push_back(block(a.final_states())); // 0 |-> final states
|
|
pblocks.push_back(block(non_final)); // 1 |-> non-final states
|
|
|
|
unsigned_vector W;
|
|
W.push_back(pblocks[0].size() > pblocks[1].size() ? 1 : 0);
|
|
|
|
refs_t trail(m);
|
|
u_map<T*> gamma;
|
|
moves_t mvs;
|
|
while (!W.empty()) {
|
|
block R(pblocks[W.back()]);
|
|
W.pop_back();
|
|
gamma.reset();
|
|
uint_set::iterator it = R.begin(), end = R.end();
|
|
for (; it != end; ++it) {
|
|
unsigned dst = *it;
|
|
mvs.reset();
|
|
a.get_moves_to(dst, mvs);
|
|
for (unsigned i = 0; i < mvs.size(); ++i) {
|
|
unsigned src = mvs[i].src();
|
|
if (pblocks[src].size() > 1) {
|
|
T* t = mvs[i].t();
|
|
T* t1;
|
|
if (gamma.find(src, t1)) {
|
|
t = m_ba.mk_or(t, t1);
|
|
trail.push_back(t);
|
|
}
|
|
gamma.insert(src, t);
|
|
}
|
|
}
|
|
}
|
|
uint_set relevant1;
|
|
typedef typename u_map<T*>::iterator gamma_iterator;
|
|
gamma_iterator gend = gamma.end();
|
|
for (gamma_iterator git = gamma.begin(); git != gend; ++git) {
|
|
unsigned p0A_index = blocks[git->m_key];
|
|
if (relevant1.contains(p0A_index)) {
|
|
continue;
|
|
}
|
|
relevant1.insert(p0A_index);
|
|
block& p0A = pblocks[p0A_index];
|
|
block p1;
|
|
for (gamma_iterator it = gamma.begin(); it != gend; ++it) {
|
|
if (p0A.contains(it->m_key)) p1.insert(it->m_key);
|
|
}
|
|
|
|
add_block(p1, p0A_index, blocks, pblocks, W);
|
|
|
|
bool iterate = true;
|
|
while (iterate) {
|
|
iterate = false;
|
|
uint_set relevant2;
|
|
for (gamma_iterator it = gamma.begin(); it != gend; ++it) {
|
|
unsigned p0B_index = blocks[it->m_key];
|
|
if (pblocks[p0B_index].size() <= 1 || relevant2.contains(p0B_index)) {
|
|
continue;
|
|
}
|
|
relevant2.insert(p0B_index);
|
|
block const& p0B = pblocks[p0B_index];
|
|
uint_set::iterator bi = p0B.begin(), be = p0B.end();
|
|
|
|
block p1;
|
|
p1.insert(*bi);
|
|
bool split_found = false;
|
|
ref_t psi(gamma[*bi], m);
|
|
++bi;
|
|
for (; bi != be; ++bi) {
|
|
unsigned q = *bi;
|
|
ref_t phi(gamma[q], m);
|
|
if (split_found) {
|
|
ref_t phi_and_psi(m_ba.mk_and(phi, psi), m);
|
|
switch (m_ba.is_sat(phi_and_psi)) {
|
|
case l_true:
|
|
p1.insert(q);
|
|
break;
|
|
case l_undef:
|
|
return nullptr;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
ref_t psi_min_phi(m_ba.mk_and(psi, m_ba.mk_not(phi)), m);
|
|
lbool is_sat = m_ba.is_sat(psi_min_phi);
|
|
if (is_sat == l_undef) {
|
|
return nullptr;
|
|
}
|
|
if (is_sat == l_true) {
|
|
psi = psi_min_phi;
|
|
split_found = true;
|
|
continue;
|
|
}
|
|
// psi is a subset of phi
|
|
ref_t phi_min_psi(m_ba.mk_and(phi, m_ba.mk_not(psi)), m);
|
|
is_sat = m_ba.is_sat(phi_min_psi);
|
|
if (is_sat == l_undef) {
|
|
return nullptr;
|
|
}
|
|
else if (is_sat == l_false) {
|
|
p1.insert(q); // psi and phi are equivalent
|
|
}
|
|
else {
|
|
p1.clear();
|
|
p1.insert(q);
|
|
psi = phi_min_psi;
|
|
split_found = true;
|
|
}
|
|
}
|
|
}
|
|
if (p1.size() < p0B.size() && p0B.size() > 2) iterate = true;
|
|
add_block(p1, p0B_index, blocks, pblocks, W);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned new_init = pblocks[blocks[a.init()]].get_representative();
|
|
|
|
// set moves
|
|
u2_map<T*> conds;
|
|
svector<unsigned_pair> keys;
|
|
moves_t new_moves;
|
|
|
|
for (unsigned i = 0; i < a.num_states(); ++i) {
|
|
unsigned src = pblocks[blocks[i]].get_representative();
|
|
typename automaton_t::moves const& mvs = a.get_moves_from(i);
|
|
for (unsigned j = 0; j < mvs.size(); ++j) {
|
|
unsigned dst = pblocks[blocks[mvs[j].dst()]].get_representative();
|
|
unsigned_pair st(src, dst);
|
|
T* t = 0;
|
|
if (conds.find(st, t)) {
|
|
t = m_ba.mk_or(t, mvs[j].t());
|
|
trail.push_back(t);
|
|
conds.insert(st, t);
|
|
}
|
|
else {
|
|
conds.insert(st, mvs[j].t());
|
|
keys.push_back(st);
|
|
}
|
|
}
|
|
}
|
|
for (unsigned i = 0; i < keys.size(); ++i) {
|
|
unsigned_pair st = keys[i];
|
|
new_moves.push_back(move_t(m, st.first, st.second, conds[st]));
|
|
}
|
|
// set final states.
|
|
unsigned_vector new_final;
|
|
uint_set new_final_set;
|
|
for (unsigned i = 0; i < a.final_states().size(); ++i) {
|
|
unsigned f = pblocks[blocks[a.final_states()[i]]].get_representative();
|
|
if (!new_final_set.contains(f)) {
|
|
new_final_set.insert(f);
|
|
new_final.push_back(f);
|
|
}
|
|
}
|
|
|
|
return alloc(automaton_t, m, new_init, new_final, new_moves);
|
|
}
|
|
|
|
template<class T, class M>
|
|
typename symbolic_automata<T, M>::automaton_t* symbolic_automata<T, M>::mk_determinstic(automaton_t& a) {
|
|
return mk_determinstic_param(a);
|
|
}
|
|
|
|
template<class T, class M>
|
|
typename symbolic_automata<T, M>::automaton_t* symbolic_automata<T, M>::mk_complement(automaton_t& a) {
|
|
return mk_determinstic_param(a, true);
|
|
}
|
|
|
|
template<class T, class M>
|
|
typename symbolic_automata<T, M>::automaton_t*
|
|
symbolic_automata<T, M>::mk_determinstic_param(automaton_t& a, bool flip_acceptance) {
|
|
vector<std::pair<vector<bool>, ref_t> > min_terms;
|
|
vector<ref_t> predicates;
|
|
|
|
map<uint_set, unsigned, uint_set::hash, uint_set::eq> s2id; // set of states to unique id
|
|
vector<uint_set> id2s; // unique id to set of b-states
|
|
uint_set set;
|
|
unsigned_vector vector;
|
|
moves_t new_mvs; // moves in the resulting automaton
|
|
unsigned_vector new_final_states; // new final states
|
|
unsigned p_state_id = 0; // next state identifier
|
|
|
|
TRACE("seq", tout << "mk-deterministic " << flip_acceptance << " " << set << " " << a.is_final_configuration(set) << "\n";);
|
|
// adds non-final states of a to final if flipping and final otherwise
|
|
unsigned_vector init_states;
|
|
a.get_epsilon_closure(a.init(), init_states);
|
|
for (unsigned s : init_states) {
|
|
set.insert(s);
|
|
}
|
|
if (a.is_final_configuration(set) != flip_acceptance) {
|
|
new_final_states.push_back(p_state_id);
|
|
}
|
|
|
|
s2id.insert(set, p_state_id++); // the index to the initial state is 0
|
|
id2s.push_back(set);
|
|
|
|
::vector<uint_set> todo; //States to visit
|
|
todo.push_back(set);
|
|
|
|
uint_set state;
|
|
moves_t mvsA;
|
|
|
|
new_mvs.reset();
|
|
|
|
// or just make todo a vector whose indices coincide with state_id.
|
|
while (!todo.empty()) {
|
|
uint_set state = todo.back();
|
|
|
|
unsigned state_id = s2id[state];
|
|
todo.pop_back();
|
|
mvsA.reset();
|
|
|
|
min_terms.reset();
|
|
predicates.reset();
|
|
|
|
a.get_moves_from_states(state, mvsA);
|
|
|
|
for (unsigned j = 0; j < mvsA.size(); ++j) {
|
|
ref_t mv_guard(mvsA[j].t(),m);
|
|
predicates.push_back(mv_guard);
|
|
}
|
|
|
|
min_terms = generate_min_terms(predicates);
|
|
for (unsigned j = 0; j < min_terms.size(); ++j) {
|
|
set = uint_set();
|
|
for (unsigned i = 0; i < mvsA.size(); ++i) {
|
|
if (min_terms[j].first[i])
|
|
set.insert(mvsA[i].dst());
|
|
}
|
|
|
|
bool is_new = !s2id.contains(set);
|
|
if (is_new) {
|
|
TRACE("seq", tout << "mk-deterministic " << flip_acceptance << " " << set << " " << a.is_final_configuration(set) << "\n";);
|
|
if (a.is_final_configuration(set) != flip_acceptance) {
|
|
new_final_states.push_back(p_state_id);
|
|
}
|
|
|
|
s2id.insert(set, p_state_id++);
|
|
id2s.push_back(set);
|
|
todo.push_back(set);
|
|
}
|
|
new_mvs.push_back(move_t(m, state_id, s2id[set], min_terms[j].second));
|
|
}
|
|
}
|
|
|
|
if (new_final_states.empty()) {
|
|
return alloc(automaton_t, m);
|
|
}
|
|
|
|
return alloc(automaton_t, m, 0, new_final_states, new_mvs);
|
|
}
|
|
|
|
|
|
|
|
template<class T, class M>
|
|
typename symbolic_automata<T, M>::automaton_t* symbolic_automata<T, M>::mk_product(automaton_t& a, automaton_t& b) {
|
|
u2_map<unsigned> pair2id;
|
|
unsigned_pair init_pair(a.init(), b.init());
|
|
svector<unsigned_pair> todo;
|
|
todo.push_back(init_pair);
|
|
pair2id.insert(init_pair, 0);
|
|
moves_t mvs;
|
|
unsigned_vector final;
|
|
unsigned_vector a_init, b_init;
|
|
a.get_epsilon_closure(a.init(), a_init);
|
|
bool a_init_is_final = false, b_init_is_final = false;
|
|
for (unsigned ia : a_init) {
|
|
if (a.is_final_state(ia)) {
|
|
a_init_is_final = true;
|
|
b.get_epsilon_closure(b.init(), b_init);
|
|
for (unsigned ib : b_init) {
|
|
if (b.is_final_state(ib)) {
|
|
b_init_is_final = true;
|
|
final.push_back(0);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsigned n = 1;
|
|
moves_t mvsA, mvsB;
|
|
while (!todo.empty()) {
|
|
unsigned_pair curr_pair = todo.back();
|
|
todo.pop_back();
|
|
unsigned src = pair2id[curr_pair];
|
|
mvsA.reset(); mvsB.reset();
|
|
a.get_moves_from(curr_pair.first, mvsA, true);
|
|
b.get_moves_from(curr_pair.second, mvsB, true);
|
|
for (unsigned i = 0; i < mvsA.size(); ++i) {
|
|
for (unsigned j = 0; j < mvsB.size(); ++j) {
|
|
ref_t ab(m_ba.mk_and(mvsA[i].t(), mvsB[j].t()), m);
|
|
lbool is_sat = m_ba.is_sat(ab);
|
|
if (is_sat == l_false) {
|
|
continue;
|
|
}
|
|
else if (is_sat == l_undef) {
|
|
return nullptr;
|
|
}
|
|
unsigned_pair tgt_pair(mvsA[i].dst(), mvsB[j].dst());
|
|
unsigned tgt;
|
|
if (!pair2id.find(tgt_pair, tgt)) {
|
|
tgt = n++;
|
|
pair2id.insert(tgt_pair, tgt);
|
|
todo.push_back(tgt_pair);
|
|
if (a.is_final_state(tgt_pair.first) && b.is_final_state(tgt_pair.second)) {
|
|
final.push_back(tgt);
|
|
}
|
|
}
|
|
mvs.push_back(move_t(m, src, tgt, ab));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (final.empty()) {
|
|
return alloc(automaton_t, m);
|
|
}
|
|
vector<moves_t> inv(n, moves_t());
|
|
for (unsigned i = 0; i < mvs.size(); ++i) {
|
|
move_t const& mv = mvs[i];
|
|
inv[mv.dst()].push_back(move_t(m, mv.dst(), mv.src(), mv.t()));
|
|
}
|
|
|
|
bool_vector back_reachable(n, false);
|
|
for (unsigned f : final) {
|
|
back_reachable[f] = true;
|
|
}
|
|
|
|
unsigned_vector stack(final);
|
|
while (!stack.empty()) {
|
|
unsigned state = stack.back();
|
|
stack.pop_back();
|
|
moves_t const& mv = inv[state];
|
|
for (unsigned i = 0; i < mv.size(); ++i) {
|
|
state = mv[i].dst();
|
|
if (!back_reachable[state]) {
|
|
back_reachable[state] = true;
|
|
stack.push_back(state);
|
|
}
|
|
}
|
|
}
|
|
|
|
moves_t mvs1;
|
|
for (unsigned i = 0; i < mvs.size(); ++i) {
|
|
move_t const& mv = mvs[i];
|
|
if (back_reachable[mv.dst()]) {
|
|
mvs1.push_back(mv);
|
|
}
|
|
}
|
|
if (mvs1.empty()) {
|
|
if (a_init_is_final && b_init_is_final) {
|
|
// special case: automaton has no moves, but the initial state is final on both sides
|
|
// this results in the automaton which accepts the empty sequence and nothing else
|
|
final.clear();
|
|
final.push_back(0);
|
|
return alloc(automaton_t, m, 0, final, mvs1);
|
|
} else {
|
|
return alloc(automaton_t, m);
|
|
}
|
|
}
|
|
else {
|
|
return alloc(automaton_t, m, 0, final, mvs1);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template<class T, class M>
|
|
typename symbolic_automata<T, M>::automaton_t* symbolic_automata<T, M>::mk_difference(automaton_t& a, automaton_t& b) {
|
|
return mk_product(a,mk_complement(b));
|
|
}
|
|
|