// -*- coding: utf-8 -*- // Copyright (C) 2008, 2010, 2011, 2013, 2014, 2015 Laboratoire de // recherche et développement de l'Epita (LRDE). // Copyright (C) 2004, 2005 Laboratoire d'Informatique de Paris 6 // (LIP6), département Systèmes Répartis Coopératifs (SRC), Université // Pierre et Marie Curie. // // This file is part of Spot, a model checking library. // // Spot is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // Spot is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public // License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //#define TRACE #include #ifdef TRACE #define trace std::cerr #else #define trace while (0) std::cerr #endif #include #include #include #include "twa/twa.hh" #include "misc/hash.hh" #include "emptiness.hh" #include "emptiness_stats.hh" #include "gv04.hh" #include "bfssteps.hh" namespace spot { namespace { struct stack_entry { const state* s; // State stored in stack entry. twa_succ_iterator* lasttr; // Last transition explored from this state. int lowlink; // Lowlink value if this entry. int pre; // DFS predecessor. int acc; // Accepting state link. }; struct gv04: public emptiness_check, public ec_statistics { // Map of visited states. typedef std::unordered_map hash_type; hash_type h; // Stack of visited states on the path. typedef std::vector stack_type; stack_type stack; int top; // Top of SCC stack. int dftop; // Top of DFS stack. bool violation; // Whether an accepting run was found. gv04(const const_twa_ptr& a, option_map o) : emptiness_check(a, o) { assert(a->num_sets() <= 1); } ~gv04() { for (stack_type::iterator i = stack.begin(); i != stack.end(); ++i) a_->release_iter(i->lasttr); hash_type::const_iterator s = h.begin(); while (s != h.end()) { // Advance the iterator before deleting the "key" pointer. const state* ptr = s->first; ++s; ptr->destroy(); } } virtual emptiness_check_result_ptr check() { top = dftop = -1; violation = false; push(a_->get_init_state(), false); while (!violation && dftop >= 0) { trace << "Main iteration (top = " << top << ", dftop = " << dftop << ", s = " << a_->format_state(stack[dftop].s) << ')' << std::endl; twa_succ_iterator* iter = stack[dftop].lasttr; bool cont; if (!iter) { iter = stack[dftop].lasttr = a_->succ_iter(stack[dftop].s); cont = iter->first(); } else { cont = iter->next(); } if (!cont) { trace << " No more successors" << std::endl; pop(); } else { const state* s_prime = iter->current_state(); bool acc = a_->acc().accepting(iter->current_acceptance_conditions()); inc_transitions(); trace << " Next successor: s_prime = " << a_->format_state(s_prime) << (acc ? " (with accepting link)" : ""); hash_type::const_iterator i = h.find(s_prime); if (i == h.end()) { trace << " is a new state." << std::endl; push(s_prime, acc); } else { if (i->second < stack.size() && stack[i->second].s->compare(s_prime) == 0) { // s_prime has a clone on stack trace << " is on stack." << std::endl; // This is an addition to GV04 to support TBA. violation |= acc; lowlinkupdate(dftop, i->second); } else { trace << " has been seen, but is no longer on stack." << std::endl; } s_prime->destroy(); } } set_states(h.size()); } if (violation) return std::make_shared(*this); return nullptr; } void push(const state* s, bool accepting) { trace << " push(s = " << a_->format_state(s) << ", accepting = " << accepting << ")\n"; h[s] = ++top; stack_entry ss = { s, nullptr, top, dftop, 0 }; if (accepting) ss.acc = top - 1; // This differs from GV04 to support TBA. else if (dftop >= 0) ss.acc = stack[dftop].acc; else ss.acc = -1; trace << " s.lowlink = " << top << std::endl << " s.acc = " << ss.acc << std::endl; stack.push_back(ss); dftop = top; inc_depth(); } void pop() { trace << " pop()\n"; int p = stack[dftop].pre; if (p >= 0) lowlinkupdate(p, dftop); if (stack[dftop].lowlink == dftop) { assert(static_cast(top + 1) == stack.size()); for (int i = top; i >= dftop; --i) { a_->release_iter(stack[i].lasttr); stack.pop_back(); dec_depth(); } top = dftop - 1; } dftop = p; } void lowlinkupdate(int f, int t) { trace << " lowlinkupdate(f = " << f << ", t = " << t << ")\n t.lowlink = " << stack[t].lowlink << "\n f.lowlink = " << stack[f].lowlink << "\n f.acc = " << stack[f].acc << '\n'; int stack_t_lowlink = stack[t].lowlink; if (stack_t_lowlink <= stack[f].lowlink) { if (stack_t_lowlink <= stack[f].acc) violation = true; stack[f].lowlink = stack_t_lowlink; trace << " f.lowlink updated to " << stack[f].lowlink << '\n'; } } virtual std::ostream& print_stats(std::ostream& os) const { os << h.size() << " unique states visited\n"; os << transitions() << " transitions explored\n"; os << max_depth() << " items max on stack\n"; return os; } struct result: public emptiness_check_result, public acss_statistics { gv04& data; result(gv04& data) : emptiness_check_result(data.automaton(), data.options()), data(data) { } void update_lowlinks() { // Transitively update the lowlinks, so we can use them in // to check SCC inclusion for (int i = 0; i <= data.top; ++i) { int l = data.stack[i].lowlink; if (l < i) { int ll = data.stack[i].lowlink = data.stack[l].lowlink; for (int j = i - 1; data.stack[j].lowlink != ll; --j) data.stack[j].lowlink = ll; } } } virtual unsigned acss_states() const { // Gross! const_cast(this)->update_lowlinks(); int scc = data.stack[data.dftop].lowlink; int j = data.dftop; int s = 0; while (j >= 0 && data.stack[j].lowlink == scc) { --j; ++s; } assert(s > 0); return s; } virtual twa_run_ptr accepting_run() { auto res = std::make_shared(); update_lowlinks(); #ifdef TRACE for (int i = 0; i <= data.top; ++i) { trace << "state " << i << " (" << data.a_->format_state(data.stack[i].s) << ") has lowlink = " << data.stack[i].lowlink << std::endl; } #endif // We will use the root of the last SCC as the start of the // cycle. int scc_root = data.stack[data.dftop].lowlink; assert(scc_root >= 0); // Construct the prefix by unwinding the DFS stack before // scc_root. int father = data.stack[scc_root].pre; while (father >= 0) { twa_run::step st = { data.stack[father].s->clone(), data.stack[father].lasttr->current_condition(), data.stack[father].lasttr->current_acceptance_conditions() }; res->prefix.push_front(st); father = data.stack[father].pre; } // Construct the cycle in two phases. A first BFS finds the // shortest path from scc_root to an accepting transition. // A second BFS then search a path back to scc_root. If // there is no acceptance conditions we just use the second // BFS to find a cycle around scc_root. struct first_bfs: bfs_steps { const gv04& data; int scc_root; result* r; first_bfs(result* r, int scc_root) : bfs_steps(r->data.automaton()), data(r->data), scc_root(scc_root), r(r) { } virtual const state* filter(const state* s) { // Do not escape the SCC hash_type::const_iterator j = data.h.find(s); if (// This state was never visited so far. j == data.h.end() // Or it was discarded || j->second >= data.stack.size() // Or it was discarded (but its stack slot reused) || data.stack[j->second].s->compare(s) // Or it is still on the stack but not in the SCC || data.stack[j->second].lowlink < scc_root) { s->destroy(); return nullptr; } r->inc_ars_cycle_states(); s->destroy(); return j->first; } virtual bool match(twa_run::step& step, const state*) { return step.acc != 0U; } }; struct second_bfs: first_bfs { const state* target; second_bfs(result* r, int scc_root, const state* target) : first_bfs(r, scc_root), target(target) { } virtual bool match(twa_run::step&, const state* s) { return s == target; } }; const state* bfs_start = data.stack[scc_root].s; const state* bfs_end = bfs_start; if (a_->num_sets() > 0) { first_bfs b1(this, scc_root); bfs_start = b1.search(bfs_start, res->cycle); assert(bfs_start); } if (bfs_start != bfs_end || res->cycle.empty()) { second_bfs b2(this, scc_root, bfs_end); bfs_start = b2.search(bfs_start, res->cycle); assert(bfs_start == bfs_end); } assert(res->cycle.begin() != res->cycle.end()); return res; } }; }; } // anonymous emptiness_check_ptr explicit_gv04_check(const const_twa_ptr& a, option_map o) { return std::make_shared(a, o); } }