// -*- coding: utf-8 -*- // Copyright (C) 2010, 2011, 2012, 2013, 2014 Laboratoire de Recherche // et Développement de l'Epita. // Copyright (C) 2003, 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 . #include #include "tgbatba.hh" #include "bddprint.hh" #include "ltlast/constant.hh" #include "misc/hashfunc.hh" namespace spot { namespace { /// \brief A state for spot::tgba_tba_proxy. /// /// This state is in fact a pair of states: the state from the tgba /// automaton, and a state of the "counter" (we use a pointer /// to the position in the cycle_acc_ list). class state_tba_proxy: public state { typedef tgba_tba_proxy::cycle_list::const_iterator iterator; public: state_tba_proxy(state* s, iterator acc) : s_(s), acc_(acc) { } // Note: There is a default copy constructor, needed by // std::unordered_set. It does not clone the state "s", because the // destructor will not destroy it either. Actually, the states // are all destroyed in the tgba_tba_proxy destructor. virtual ~state_tba_proxy() { } void destroy() const { } state* real_state() const { return s_; } bdd acceptance_cond() const { return *acc_; } iterator acceptance_iterator() const { return acc_; } virtual int compare(const state* other) const { const state_tba_proxy* o = down_cast(other); assert(o); // Do not simply return "o - this", it might not fit in an int. if (o < this) return -1; if (o > this) return 1; return 0; } virtual size_t hash() const { return wang32_hash(s_->hash()) ^ wang32_hash(acc_->id()); } virtual state_tba_proxy* clone() const { return const_cast(this); } private: state* s_; iterator acc_; }; struct state_tba_proxy_hash { size_t operator()(const state_tba_proxy& s) const { return s.state_tba_proxy::hash(); } }; struct state_tba_proxy_equal { bool operator()(const state_tba_proxy& left, const state_tba_proxy& right) const { if (left.acceptance_iterator() != right.acceptance_iterator()) return false; return left.real_state()->compare(right.real_state()) == 0; } }; typedef std::unordered_set uniq_map_t; typedef std::pair state_ptr_bool_t; struct state_ptr_bool_hash: public std::unary_function { size_t operator()(const state_ptr_bool_t& s) const { if (s.second) return s.first->hash() ^ 12421; else return s.first->hash(); } }; struct state_ptr_bool_equal: public std::binary_function { bool operator()(const state_ptr_bool_t& left, const state_ptr_bool_t& right) const { if (left.second != right.second) return false; return left.first->compare(right.first) == 0; } }; /// \brief Iterate over the successors of tgba_tba_proxy computed /// on the fly. class tgba_tba_proxy_succ_iterator: public tgba_succ_iterator { typedef tgba_tba_proxy::cycle_list list; typedef tgba_tba_proxy::cycle_list::const_iterator iterator; public: tgba_tba_proxy_succ_iterator(const state* rs, tgba::succ_iterable&& iterable, iterator expected, const list& cycle, bdd the_acceptance_cond, const tgba_tba_proxy* aut) : the_acceptance_cond_(the_acceptance_cond) { recycle(rs, std::move(iterable), expected, cycle, aut); } void recycle(const state* rs, tgba::succ_iterable&& iterable, iterator expected, const list& cycle, const tgba_tba_proxy* aut) { if (!transmap_.empty()) { translist_.clear(); transmap_.clear(); } for (auto it: iterable) { bool accepting; bdd acc = it->current_acceptance_conditions(); // As an extra optimization step, gather the acceptance // conditions common to all outgoing transitions of the // destination state. We will later add these to "acc" to // pretend they are already present on this transition. state* odest = it->current_state(); bdd otheracc = aut->common_acceptance_conditions_of_original_state(odest); iterator next; // bddtrue is a special condition used for tgba_sba_proxy // to denote the (N+1)th copy of the state, after all // acceptance conditions have been traversed. Such state // is always accepting, so do not check acc for this. // bddtrue is also used by tgba_tba_proxy if the automaton // does not use acceptance conditions. In that case, all // states are accepting. if (*expected == bddtrue) { // When degeneralizing to SBA, ignore the last // expected acceptance set (the value of *prev below) // if it is common to all other outgoing transitions (of the // current state) AND if it is not used by any outgoing // transition of the destination state. // // 1) It's correct to do that, because this acceptance // set is common to other outgoing transitions. // Therefore if we make a cycle to this state we // will eventually see that acceptance set thanks // to the "pulling" of the common acceptance sets // of the destination state (cf. "odest"). // // 2) It's also desirable because it makes the // degeneralization idempotent (up to a renaming of // states). Consider the following automaton where // 1 is initial and => marks accepting transitions: // 1=>1, 1=>2, 2->2, 2->1 This is already an SBA, // with 1 as accepting state. However if you try // degeralize it without ignoring *prev, we'll get // two copies of states 2, depending on whether we // reach it using 1=>2 or from 2->2. If this // example was not clear, uncomment this following // "if" block, and play with the "degenid.test" // test case. // // 3) Ignoring all common acceptance sets would also // be correct, but it would make the // degeneralization produce larger automata in some // cases. The current condition to ignore only one // acceptance set if is this not used by the next // state is a heuristic that is compatible with // point 2) above while not causing more states to // be generated in our benchmark of 188 formulae // from the literature. if (expected != cycle.begin()) { iterator prev = expected; --prev; bdd common = aut-> common_acceptance_conditions_of_original_state(rs); if (bdd_implies(*prev, common)) { bdd u = aut-> union_acceptance_conditions_of_original_state(odest); if (!bdd_implies(*prev, u)) acc -= *prev; } } // Use the acceptance sets common to all outgoing // transition of the destination state. In case of a // self-loop, we will be adding back the acceptance // set we removed. This is what we want. acc |= otheracc; } else { // A transition in the *EXPECTED acceptance set should // be directed to the next acceptance set. If the // current transition is also in the next acceptance // set, then go to the one after, etc. // // See Denis Oddoux's PhD thesis for a nice // explanation (in French). // @PhDThesis{ oddoux.03.phd, // author = {Denis Oddoux}, // title = {Utilisation des automates alternants pour un // model-checking efficace des logiques // temporelles lin{\'e}aires.}, // school = {Universit{\'e}e Paris 7}, // year = {2003}, // address= {Paris, France}, // month = {December} // } next = expected; // Consider both the current acceptance sets, and the // acceptance sets common to the outgoing transition of // the destination state. acc |= otheracc; while (next != cycle.end() && bdd_implies(*next, acc)) ++next; if (next != cycle.end()) { accepting = false; goto next_is_set; } } // The transition is accepting. accepting = true; // Skip as much acceptance conditions as we can on our cycle. next = cycle.begin(); while (next != expected && bdd_implies(*next, acc)) ++next; next_is_set: state_tba_proxy* dest = down_cast(aut->create_state(odest, next)); // Is DEST already reachable with the same value of ACCEPTING? state_ptr_bool_t key(dest, accepting); transmap_t::iterator id = transmap_.find(key); if (id == transmap_.end()) // No { mapit_t pos = transmap_ .insert(std::make_pair(key, it->current_condition())).first; // Keep the order of the transitions in the // degeneralized automaton related to the order of the // transitions in the input automaton: in the past we // used to simply iterate over transmap_ in whatever // order the transitions were stored, but the output // would change between different runs depending on // the memory address of the states. Now we keep the // order using translist_. We only arrange it // slightly so that accepting transitions come first: // this way they are processed early during // emptiness-check. if (accepting) translist_.push_front(pos); else translist_.push_back(pos); } else // Yes, combine labels. { id->second |= it->current_condition(); dest->destroy(); } } } virtual ~tgba_tba_proxy_succ_iterator() { } // iteration bool first() { it_ = translist_.begin(); return it_ != translist_.end(); } bool next() { ++it_; return it_ != translist_.end(); } bool done() const { return it_ == translist_.end(); } // inspection state_tba_proxy* current_state() const { return (*it_)->first.first->clone(); } bdd current_condition() const { return (*it_)->second; } bdd current_acceptance_conditions() const { return (*it_)->first.second ? the_acceptance_cond_ : bddfalse; } protected: const bdd the_acceptance_cond_; typedef std::unordered_map transmap_t; transmap_t transmap_; typedef transmap_t::const_iterator mapit_t; typedef std::list translist_t; translist_t translist_; translist_t::const_iterator it_; }; } // anonymous tgba_tba_proxy::tgba_tba_proxy(const tgba* a) : a_(a), uniq_map_(new uniq_map_t) { // We will use one acceptance condition for this automata. // Let's call it Acc[True]. bdd_dict* d = get_dict(); d->register_all_variables_of(a, this); d->unregister_all_typed_variables(bdd_dict::acc, this); int v = d ->register_acceptance_variable(ltl::constant::true_instance(), this); the_acceptance_cond_ = bdd_ithvar(v); if (a->number_of_acceptance_conditions() == 0) { acc_cycle_.push_front(bddtrue); } else { // Build a cycle of expected acceptance conditions. // // The order is arbitrary, but it turns out that using // push_back instead of push_front often gives better results // because acceptance conditions at the beginning if the // cycle are more often used in the automaton. (This // surprising fact is probably related to the order in which we // declare the BDD variables during the translation.) bdd all = a_->all_acceptance_conditions(); while (all != bddfalse) { bdd next = bdd_satone(all); all -= next; acc_cycle_.push_back(next); } } } tgba_tba_proxy::~tgba_tba_proxy() { get_dict()->unregister_all_my_variables(this); accmap_t::const_iterator i = accmap_.begin(); while (i != accmap_.end()) { // Advance the iterator before deleting the key. const state* s = i->first; ++i; s->destroy(); } i = accmapu_.begin(); while (i != accmapu_.end()) { // Advance the iterator before deleting the key. const state* s = i->first; ++i; s->destroy(); } uniq_map_t* m = static_cast(uniq_map_); uniq_map_t::const_iterator j = m->begin(); while (j != m->end()) { const state* s = j->real_state(); ++j; s->destroy(); } delete m; // This has already been destroyed. // Prevent destroying by tgba::~tgba. this->last_support_conditions_input_ = 0; } state* tgba_tba_proxy::create_state(state* s, cycle_list::const_iterator acc) const { uniq_map_t* m = static_cast(uniq_map_); state_tba_proxy st(s, acc); std::pair res = m->insert(st); if (!res.second) s->destroy(); return const_cast(&(*res.first)); } state* tgba_tba_proxy::get_init_state() const { return create_state(a_->get_init_state(), acc_cycle_.begin()); } tgba_succ_iterator* tgba_tba_proxy::succ_iter(const state* st) const { const state_tba_proxy* s = down_cast(st); assert(s); const state* rs = s->real_state(); if (iter_cache_) { tgba_tba_proxy_succ_iterator* res = down_cast(iter_cache_); res->recycle(rs, a_->succ(rs), s->acceptance_iterator(), acc_cycle_, this); iter_cache_ = nullptr; return res; } return new tgba_tba_proxy_succ_iterator(rs, a_->succ(rs), s->acceptance_iterator(), acc_cycle_, the_acceptance_cond_, this); } bdd tgba_tba_proxy::common_acceptance_conditions_of_original_state(const state* s) const { // Lookup cache accmap_t::const_iterator i = accmap_.find(s); if (i != accmap_.end()) return i->second; bdd common = a_->all_acceptance_conditions(); for (auto it: a_->succ(s)) { common &= it->current_acceptance_conditions(); if (common == bddfalse) break; } // Populate cache accmap_[s->clone()] = common; return common; } bdd tgba_tba_proxy::union_acceptance_conditions_of_original_state(const state* s) const { // Lookup cache accmap_t::const_iterator i = accmapu_.find(s); if (i != accmapu_.end()) return i->second; bdd common = bddfalse; for (auto it: a_->succ(s)) common |= it->current_acceptance_conditions(); // Populate cache accmapu_[s->clone()] = common; return common; } bdd_dict* tgba_tba_proxy::get_dict() const { return a_->get_dict(); } std::string tgba_tba_proxy::format_state(const state* state) const { const state_tba_proxy* s = down_cast(state); assert(s); std::string a = bdd_format_accset(get_dict(), s->acceptance_cond()); if (a != "") a = " " + a; return a_->format_state(s->real_state()) + a; } state* tgba_tba_proxy::project_state(const state* s, const tgba* t) const { const state_tba_proxy* s2 = down_cast(s); assert(s2); if (t == this) return s2->clone(); return a_->project_state(s2->real_state(), t); } bdd tgba_tba_proxy::all_acceptance_conditions() const { return the_acceptance_cond_; } bdd tgba_tba_proxy::neg_acceptance_conditions() const { return !the_acceptance_cond_; } bdd tgba_tba_proxy::compute_support_conditions(const state* state) const { const state_tba_proxy* s = down_cast(state); assert(s); return a_->support_conditions(s->real_state()); } //////////////////////////////////////////////////////////////////////// // tgba_sba_proxy tgba_sba_proxy::tgba_sba_proxy(const tgba* a) : tgba_tba_proxy(a) { if (a->number_of_acceptance_conditions() > 0) { cycle_start_ = acc_cycle_.insert(acc_cycle_.end(), bddtrue); bdd all = a->all_acceptance_conditions(); state* init = a->get_init_state(); for (auto it: a->succ(init)) { // Look only for transitions that are accepting. if (all != it->current_acceptance_conditions()) continue; // Look only for self-loops. state* dest = it->current_state(); if (dest->compare(init) == 0) { // The initial state has an accepting self-loop. // In that case it is better to start the accepting // cycle on a "acceptance" state. This will avoid // duplication of the initial state. // The cycle_start_ points to the right starting // point already, so just return. dest->destroy(); init->destroy(); return; } dest->destroy(); } init->destroy(); } // If we arrive here either because the number of acceptance // condition is 0, or because the initial state has no accepting // self-loop, start the acceptance cycle on the first condition // (that is a non-accepting state if the number of conditions is // not 0). cycle_start_ = acc_cycle_.begin(); } state* tgba_sba_proxy::get_init_state() const { return create_state(a_->get_init_state(), cycle_start_); } bool tgba_sba_proxy::state_is_accepting(const state* state) const { const state_tba_proxy* s = down_cast(state); assert(s); return bddtrue == s->acceptance_cond(); } }