diff --git a/ChangeLog b/ChangeLog index 0a4ed8988..0dfc1fc65 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,46 @@ +2003-08-15 Alexandre Duret-Lutz + + This implements Couvreur's FM'99 ltl2tgba translation. + * src/tgba/bdddict.cc (bdd_dict::is_registered): Split as ... + (bdd_dict::is_registered_proposition, bdd_dict::is_registered_state, + bdd_dict::is_registered_accepting_variable): ... these. + * src/tgba/bdddict.hh: Likewise. + * src/tgba/tgbaexplicit.cc (tgba_explicit::set_init_state): New method. + (tgba_explicit::declare_accepting_condition): Arrange so that this + function can be called during the construction of the automaton. + (tgba_explicit::complement_all_accepting_conditions): New method. + (tgba_explicit::has_accepting_condition): Adjust to call + bdd_dict::is_registered_accepting_variable. + * src/tgba/tgbaexplicit.hh (tgba_explicit::set_init_state, + tgba_explicit::complement_all_accepting_conditions): New methods. + * src/tgbaalgos/ltl2tgba_fm.cc, src/tgbaalgos/ltl2tgba_fm.hh: + New files. + * src/tgbaalgos/Makefile.am (tgbaalgos_HEADERS, + libtgbaalgos_la_SOURCES): Add them. + * src/tgbaalgos/ltl2tgba.hh: Add bibtex entry in comment. + * src/tgbatest/Makefile.am (check_PROGRAMS): Remove spotlbtt + and tbalbtt. + (tbalbtt_SOURCES, tbalbtt_CXXFLAGS, spotlbtt_SOURCES): Remove. + * src/tgbatest/spotlbtt.cc: Delete, superseded by "ltl2tgba -F -t". + * src/tgbatest/ltl2tgba.cc: Implement the -f and -F options. + * src/tgbatest/spotlbtt.test: Use "ltl2tgba -F -t" instead of + "spotlbtt", "ltl2tgba -F -t -D" instead of "tbalbtt", and add + also check the ltl2tgba_fm translator. + * wrap/python/spot.i: Wrap ltl2tgba_fm. + * wrap/python/cgi/ltl2tgba.in: Add radio buttons to select + between ltl2tgba and ltl2tgba_fm. + * wrap/python/tests/ltl2tgba.py: Add support for the -f option. + * wrap/python/tests/ltl2tgba.test: Try the -f option. + + varnum can be augmented by other allocator. Keep track + of a local varnum (lvarnum) in each allocator. + * src/misc/bddalloc.cc (bdd_allocator::bdd_allocator): Initialize + lvarnum. + (bdd_allocator::extvarnum): New method. + (bdd_allocator::allocate_variables): Use lvarnum and extvarnum. + * src/misc/bddalloc.hh (bdd_allocator::extvarnum): New mathod. + (bdd_allocator::lvarnum): New variable. + 2003-08-14 Alexandre Duret-Lutz * src/tgba/state.hh, src/tgba/statebdd.hh, src/tgba/statebdd.cc: diff --git a/src/tgba/bdddict.cc b/src/tgba/bdddict.cc index 38115ca80..5a344690e 100644 --- a/src/tgba/bdddict.cc +++ b/src/tgba/bdddict.cc @@ -161,30 +161,33 @@ namespace spot } bool - bdd_dict::is_registered(const ltl::formula* f, const void* by_me) + bdd_dict::is_registered_proposition(const ltl::formula* f, const void* by_me) { - int var; fv_map::iterator fi = var_map.find(f); - if (fi != var_map.end()) - { - var = fi->second; - } - else - { - fi = now_map.find(f); - if (fi != now_map.end()) - { - var = fi->second; - } - else - { - fi = acc_map.find(f); - if (fi == acc_map.end()) - return false; - var = fi->second; - } - } - ref_set& s = var_refs[var]; + if (fi == var_map.end()) + return false; + ref_set& s = var_refs[fi->second]; + return s.find(by_me) != s.end(); + } + + bool + bdd_dict::is_registered_state(const ltl::formula* f, const void* by_me) + { + fv_map::iterator fi = now_map.find(f); + if (fi == now_map.end()) + return false; + ref_set& s = var_refs[fi->second]; + return s.find(by_me) != s.end(); + } + + bool + bdd_dict::is_registered_accepting_variable(const ltl::formula* f, + const void* by_me) + { + fv_map::iterator fi = acc_map.find(f); + if (fi == acc_map.end()) + return false; + ref_set& s = var_refs[fi->second]; return s.find(by_me) != s.end(); } diff --git a/src/tgba/bdddict.hh b/src/tgba/bdddict.hh index 4edc8606d..b54bbfbfb 100644 --- a/src/tgba/bdddict.hh +++ b/src/tgba/bdddict.hh @@ -91,8 +91,13 @@ namespace spot /// Usually called in the destructor if \a me. void unregister_all_my_variables(const void* me); + /// @{ /// Check whether formula \a f has already been registered by \a by_me. - bool is_registered(const ltl::formula* f, const void* by_me); + bool is_registered_proposition(const ltl::formula* f, const void* by_me); + bool is_registered_state(const ltl::formula* f, const void* by_me); + bool is_registered_accepting_variable(const ltl::formula* f, + const void* by_me); + /// @} /// \brief Dump all variables for debugging. /// \param os The output stream. diff --git a/src/tgba/tgbaexplicit.cc b/src/tgba/tgbaexplicit.cc index b490dabaf..a78e34da0 100644 --- a/src/tgba/tgbaexplicit.cc +++ b/src/tgba/tgbaexplicit.cc @@ -111,6 +111,7 @@ namespace spot state_name_map_[s] = name; // The first state we add is the inititial state. + // It can also be overridden with set_init_state(). if (! init_) init_ = s; @@ -119,6 +120,14 @@ namespace spot return i->second; } + void + tgba_explicit::set_init_state(const std::string& state) + { + tgba_explicit::state* s = add_state(state); + init_ = s; + } + + tgba_explicit::transition* tgba_explicit::create_transition(const std::string& source, const std::string& dest) @@ -159,13 +168,40 @@ namespace spot { int v = dict_->register_accepting_variable(f, this); ltl::destroy(f); - neg_accepting_conditions_ &= bdd_nithvar(v); + bdd neg = bdd_nithvar(v); + neg_accepting_conditions_ &= neg; + + // Append neg to all acceptance conditions. + ns_map::iterator i; + for (i = name_state_map_.begin(); i != name_state_map_.end(); ++i) + { + tgba_explicit::state::iterator i2; + for (i2 = i->second->begin(); i2 != i->second->end(); ++i2) + (*i2)->accepting_conditions &= neg; + } + + all_accepting_conditions_computed_ = false; + } + + void + tgba_explicit::complement_all_accepting_conditions() + { + bdd all = all_accepting_conditions(); + ns_map::iterator i; + for (i = name_state_map_.begin(); i != name_state_map_.end(); ++i) + { + tgba_explicit::state::iterator i2; + for (i2 = i->second->begin(); i2 != i->second->end(); ++i2) + { + (*i2)->accepting_conditions = all - (*i2)->accepting_conditions; + } + } } bool tgba_explicit::has_accepting_condition(ltl::formula* f) const { - return dict_->is_registered(f, this); + return dict_->is_registered_accepting_variable(f, this); } bdd @@ -185,6 +221,9 @@ namespace spot assert(0); } bdd_dict::fv_map::iterator i = dict_->acc_map.find(f); + assert(has_accepting_condition(f)); + /* If this second assert fails and the first doesn't, + things are badly broken. This has already happened. */ assert(i != dict_->acc_map.end()); ltl::destroy(f); bdd v = bdd_ithvar(i->second); diff --git a/src/tgba/tgbaexplicit.hh b/src/tgba/tgbaexplicit.hh index 74be2164b..3138a6762 100644 --- a/src/tgba/tgbaexplicit.hh +++ b/src/tgba/tgbaexplicit.hh @@ -29,6 +29,8 @@ namespace spot state* dest; }; + void set_init_state(const std::string& state); + transition* create_transition(const std::string& source, const std::string& dest); @@ -37,6 +39,7 @@ namespace spot void declare_accepting_condition(ltl::formula* f); bool has_accepting_condition(ltl::formula* f) const; void add_accepting_condition(transition* t, ltl::formula* f); + void complement_all_accepting_conditions(); // tgba interface virtual ~tgba_explicit(); diff --git a/src/tgbaalgos/Makefile.am b/src/tgbaalgos/Makefile.am index 1f6d9dd24..ba679469e 100644 --- a/src/tgbaalgos/Makefile.am +++ b/src/tgbaalgos/Makefile.am @@ -8,6 +8,7 @@ tgbaalgos_HEADERS = \ dotty.hh \ lbtt.hh \ ltl2tgba.hh \ + ltl2tgba_fm.hh \ magic.hh \ save.hh @@ -17,5 +18,6 @@ libtgbaalgos_la_SOURCES = \ dotty.cc \ lbtt.cc \ ltl2tgba.cc \ + ltl2tgba_fm.cc \ magic.cc \ save.cc diff --git a/src/tgbaalgos/ltl2tgba.hh b/src/tgbaalgos/ltl2tgba.hh index 3b38bf12e..0e6999e69 100644 --- a/src/tgbaalgos/ltl2tgba.hh +++ b/src/tgbaalgos/ltl2tgba.hh @@ -7,6 +7,23 @@ namespace spot { /// Build a spot::tgba_bdd_concrete from an LTL formula. + /// + /// This is based on the following paper. + /// \verbatim + /// @InProceedings{ couvreur.00.lacim, + /// author = {Jean-Michel Couvreur}, + /// title = {Un point de vue symbolique sur la logique temporelle + /// lin{\'e}aire}, + /// booktitle = {Actes du Colloque LaCIM 2000}, + /// month = {August}, + /// year = {2000}, + /// pages = {131--140}, + /// volume = {27}, + /// series = {Publications du LaCIM}, + /// publisher = {Universit{\'e} du Qu{\'e}bec {\`a} Montr{\'e}al}, + /// editor = {Pierre Leroux} + /// } + /// \endverbatim tgba_bdd_concrete* ltl_to_tgba(const ltl::formula* f, bdd_dict* dict); } diff --git a/src/tgbaalgos/ltl2tgba_fm.cc b/src/tgbaalgos/ltl2tgba_fm.cc new file mode 100644 index 000000000..e0af72eca --- /dev/null +++ b/src/tgbaalgos/ltl2tgba_fm.cc @@ -0,0 +1,483 @@ +#include "misc/bddalloc.hh" +#include "ltlast/visitor.hh" +#include "ltlast/allnodes.hh" +#include "ltlvisit/lunabbrev.hh" +#include "ltlvisit/nenoform.hh" +#include "ltlvisit/destroy.hh" +#include "ltlvisit/tostring.hh" +#include + +#include "tgba/tgbabddconcretefactory.hh" +#include "ltl2tgba_fm.hh" + +namespace spot +{ + using namespace ltl; + + namespace + { + + // Helper dictionary. We represent formula using a BDD to simplify + // them, and them translate the BDD back into formulae. + // + // The name of the variables are inspired from Couvreur's FM paper. + // "a" variables are promises (written "a" in the paper) + // "next" variables are X's operands (the "r_X" variables from the paper) + // "var" variables are atomic propositions. + class translate_dict: public bdd_allocator + { + public: + + translate_dict() + : bdd_allocator(), + a_set(bddtrue), + var_set(bddtrue), + next_set(bddtrue) + { + } + + ~translate_dict() + { + fv_map::iterator i; + for (i = a_map.begin(); i != a_map.end(); ++i) + ltl::destroy(i->first); + for (i = var_map.begin(); i != var_map.end(); ++i) + ltl::destroy(i->first); + for (i = next_map.begin(); i != next_map.end(); ++i) + ltl::destroy(i->first); + } + + /// Formula-to-BDD-variable maps. + typedef std::map fv_map; + /// BDD-variable-to-formula maps. + typedef std::map vf_map; + + fv_map a_map; ///< Maps formulae to "a" BDD variables + vf_map a_formula_map; ///< Maps "a" BDD variables to formulae + fv_map var_map; ///< Maps atomic propisitions to BDD variables + vf_map var_formula_map; ///< Maps BDD variables to atomic propisitions + fv_map next_map; ///< Maps "Next" variables to BDD variables + vf_map next_formula_map; ///< Maps BDD variables to "Next" variables + + bdd a_set; + bdd var_set; + bdd next_set; + + int + register_proposition(const ltl::formula* f) + { + int num; + // Do not build a variable that already exists. + fv_map::iterator sii = var_map.find(f); + if (sii != var_map.end()) + { + num = sii->second; + } + else + { + f = clone(f); + num = allocate_variables(1); + var_map[f] = num; + var_formula_map[num] = f; + } + var_set &= bdd_ithvar(num); + return num; + } + + int + register_a_variable(const ltl::formula* f) + { + int num; + // Do not build an accepting variable that already exists. + fv_map::iterator sii = a_map.find(f); + if (sii != a_map.end()) + { + num = sii->second; + } + else + { + f = clone(f); + num = allocate_variables(1); + a_map[f] = num; + a_formula_map[num] = f; + } + a_set &= bdd_ithvar(num); + return num; + } + + int + register_next_variable(const ltl::formula* f) + { + int num; + // Do not build a Next variable that already exists. + fv_map::iterator sii = next_map.find(f); + if (sii != next_map.end()) + { + num = sii->second; + } + else + { + f = clone(f); + num = allocate_variables(1); + next_map[f] = num; + next_formula_map[num] = f; + } + next_set &= bdd_ithvar(num); + return num; + } + + std::ostream& + dump(std::ostream& os) const + { + fv_map::const_iterator fi; + os << "Atomic Propositions:" << std::endl; + for (fi = var_map.begin(); fi != var_map.end(); ++fi) + { + os << " " << fi->second << ": "; + to_string(fi->first, os) << std::endl; + } + os << "a Variables:" << std::endl; + for (fi = a_map.begin(); fi != a_map.end(); ++fi) + { + os << " " << fi->second << ": a["; + to_string(fi->first, os) << "]" << std::endl; + } + os << "Next Variables:" << std::endl; + for (fi = next_map.begin(); fi != next_map.end(); ++fi) + { + os << " " << fi->second << ": Next["; + to_string(fi->first, os) << "]" << std::endl; + } + return os; + } + + ltl::formula* + var_to_formula(int var) const + { + vf_map::const_iterator isi = next_formula_map.find(var); + if (isi != next_formula_map.end()) + return ltl::clone(isi->second); + isi = a_formula_map.find(var); + if (isi != a_formula_map.end()) + return ltl::clone(isi->second); + isi = var_formula_map.find(var); + if (isi != var_formula_map.end()) + return ltl::clone(isi->second); + assert(0); + } + + ltl::formula* + conj_bdd_to_formula(bdd b) + { + if (b == bddfalse) + return ltl::constant::false_instance(); + ltl::multop::vec* v = new ltl::multop::vec; + while (b != bddtrue) + { + int var = bdd_var(b); + ltl::formula* res = var_to_formula(var); + bdd high = bdd_high(b); + if (high == bddfalse) + { + res = ltl::unop::instance(ltl::unop::Not, res); + b = bdd_low(b); + } + else + { + b = high; + } + assert(b != bddfalse); + v->push_back(res); + } + return ltl::multop::instance(ltl::multop::And, v); + } + + void + conj_bdd_to_atomic_props(tgba_explicit* a, bdd b, + tgba_explicit::transition* t) + { + assert(b != bddfalse); + while (b != bddtrue) + { + int var = bdd_var(b); + ltl::formula* ap = var_to_formula(var); + bdd high = bdd_high(b); + if (high == bddfalse) + { + a->add_neg_condition(t, ap); + b = bdd_low(b); + } + else + { + a->add_condition(t, ap); + b = high; + } + assert(b != bddfalse); + } + } + + + void + conj_bdd_to_acc(tgba_explicit* a, bdd b, tgba_explicit::transition* t) + { + assert(b != bddfalse); + while (b != bddtrue) + { + int var = bdd_var(b); + bdd high = bdd_high(b); + if (high == bddfalse) + { + // Simply ignore negated accepting variables. + b = bdd_low(b); + } + else + { + ltl::formula* ac = var_to_formula(var); + + if (! a->has_accepting_condition(ac)) + a->declare_accepting_condition(ltl::clone(ac)); + a->add_accepting_condition(t, ac); + + ltl::atomic_prop::instance_count(); + b = high; + } + assert(b != bddfalse); + } + } + }; + + + // The rewrite rules used here are adapted from Jean-Michel + // Couvreur's FM paper. + class ltl_trad_visitor: public const_visitor + { + public: + ltl_trad_visitor(translate_dict& dict) + : dict_(dict) + { + } + + virtual + ~ltl_trad_visitor() + { + } + + bdd result() const + { + return res_; + } + + void + visit(const atomic_prop* node) + { + res_ = bdd_ithvar(dict_.register_proposition(node)); + } + + void + visit(const constant* node) + { + switch (node->val()) + { + case constant::True: + res_ = bddtrue; + return; + case constant::False: + res_ = bddfalse; + return; + } + /* Unreachable code. */ + assert(0); + } + + void + visit(const unop* node) + { + switch (node->op()) + { + case unop::F: + { + // r(Fy) = r(y) + a(y)r(XFy) + bdd y = recurse(node->child()); + int a = dict_.register_a_variable(node); + int x = dict_.register_next_variable(node); + res_ = y | (bdd_ithvar(a) & bdd_ithvar(x)); + return; + } + case unop::G: + { + // r(Gy) = r(y)r(XGy) + bdd y = recurse(node->child()); + int x = dict_.register_next_variable(node); + res_ = y & bdd_ithvar(x); + return; + } + case unop::Not: + { + res_ = bdd_not(recurse(node->child())); + return; + } + case unop::X: + { + int x = dict_.register_next_variable(node->child()); + res_ = bdd_ithvar(x); + return; + } + } + /* Unreachable code. */ + assert(0); + } + + void + visit(const binop* node) + { + bdd f1 = recurse(node->first()); + bdd f2 = recurse(node->second()); + + switch (node->op()) + { + case binop::Xor: + res_ = bdd_apply(f1, f2, bddop_xor); + return; + case binop::Implies: + res_ = bdd_apply(f1, f2, bddop_imp); + return; + case binop::Equiv: + res_ = bdd_apply(f1, f2, bddop_biimp); + return; + case binop::U: + { + // r(f1 U f2) = r(f2) + a(f2)r(f1)r(X(f1 U f2)) + int a = dict_.register_a_variable(node->second()); + int x = dict_.register_next_variable(node); + res_ = f2 | (bdd_ithvar(a) & f1 & bdd_ithvar(x)); + return; + } + case binop::R: + { + // r(f1 R f2) = r(f1)r(f2) + r(f2)r(X(f1 U f2)) + int x = dict_.register_next_variable(node); + res_ = (f1 & f2) | (f2 & bdd_ithvar(x)); + return; + } + } + /* Unreachable code. */ + assert(0); + } + + void + visit(const multop* node) + { + int op = -1; + switch (node->op()) + { + case multop::And: + op = bddop_and; + res_ = bddtrue; + break; + case multop::Or: + op = bddop_or; + res_ = bddfalse; + break; + } + assert(op != -1); + unsigned s = node->size(); + for (unsigned n = 0; n < s; ++n) + { + res_ = bdd_apply(res_, recurse(node->nth(n)), op); + } + } + + bdd + recurse(const formula* f) + { + ltl_trad_visitor v(dict_); + f->accept(v); + return v.result(); + } + + + private: + translate_dict& dict_; + bdd res_; + }; + + } + + tgba_explicit* + ltl_to_tgba_fm(const ltl::formula* f, bdd_dict* dict) + { + // Normalize the formula. We want all the negations on + // the atomic propositions. We also suppress logic + // abbreviations such as <=>, =>, or XOR, since they + // would involve negations at the BDD level. + ltl::formula* f1 = ltl::unabbreviate_logic(f); + ltl::formula* f2 = ltl::negative_normal_form(f1); + ltl::destroy(f1); + + std::set formulae_seen; + std::set formulae_to_translate; + + formulae_seen.insert(f2); + formulae_to_translate.insert(f2); + + tgba_explicit* a = new tgba_explicit(dict); + + a->set_init_state(to_string(f2)); + + while (!formulae_to_translate.empty()) + { + // Pick one formula. + ltl::formula* f = *formulae_to_translate.begin(); + formulae_to_translate.erase(formulae_to_translate.begin()); + + // Translate it into a BDD to simplify it. + translate_dict d; + ltl_trad_visitor v(d); + f->accept(v); + bdd res = v.result(); + + std::string now = to_string(f); + + bdd all = res; + bdd outside = !all; + while (all != bddfalse) + { + + bdd cube = bdd_satone(all); + cube = bdd_simplify(cube, cube | outside); + all -= cube; + + ltl::formula* dest = + d.conj_bdd_to_formula(bdd_existcomp(cube, d.next_set)); + + std::string next = to_string(dest); + + tgba_explicit::transition* t = a->create_transition(now, next); + + d.conj_bdd_to_atomic_props(a, bdd_existcomp(cube, d.var_set), t); + d.conj_bdd_to_acc(a, bdd_existcomp(cube, d.a_set), t); + + + if (formulae_seen.find(dest) == formulae_seen.end()) + { + formulae_seen.insert(dest); + formulae_to_translate.insert(dest); + } + else + { + ltl::destroy(dest); + } + } + } + + // Free all formulae. + for (std::set::iterator i = formulae_seen.begin(); + i != formulae_seen.end(); ++i) + ltl::destroy(*i); + + // Turn all promises into real accepting conditions. + a->complement_all_accepting_conditions(); + return a; + } + +} diff --git a/src/tgbaalgos/ltl2tgba_fm.hh b/src/tgbaalgos/ltl2tgba_fm.hh new file mode 100644 index 000000000..3f88d9c60 --- /dev/null +++ b/src/tgbaalgos/ltl2tgba_fm.hh @@ -0,0 +1,32 @@ +#ifndef SPOT_TGBA_LTL2TGBA_FME_HH +# define SPOT_TGBA_LTL2TGBA_FME_HH + +#include "ltlast/formula.hh" +#include "tgba/tgbaexplicit.hh" + +namespace spot +{ + /// \brief Build a spot::tgba_explicit* from an LTL formula. + /// + /// This is based on the following paper. + /// \verbatim + /// @InProceedings{couvreur.99.fm, + /// author = {Jean-Michel Couvreur}, + /// title = {On-the-fly Verification of Temporal Logic}, + /// pages = {253--271}, + /// editor = {Jeannette M. Wing and Jim Woodcock and Jim Davies}, + /// booktitle = {Proceedings of the World Congress on Formal Methods in the + /// Development of Computing Systems (FM'99)}, + /// publisher = {Springer-Verlag}, + /// series = {Lecture Notes in Computer Science}, + /// volume = {1708}, + /// year = {1999}, + /// address = {Toulouse, France}, + /// month = {September}, + /// isbn = {3-540-66587-0} + /// } + /// \endverbatim + tgba_explicit* ltl_to_tgba_fm(const ltl::formula* f, bdd_dict* dict); +} + +#endif // SPOT_TGBA_LTL2TGBA_HH diff --git a/src/tgbatest/Makefile.am b/src/tgbatest/Makefile.am index 6f60dfb62..11e68ba57 100644 --- a/src/tgbatest/Makefile.am +++ b/src/tgbatest/Makefile.am @@ -12,8 +12,6 @@ check_PROGRAMS = \ ltlprod \ mixprod \ readsave \ - spotlbtt \ - tbalbtt \ tgbaread \ tripprod @@ -27,11 +25,8 @@ ltlmagic_SOURCES = ltlmagic.cc ltlprod_SOURCES = ltlprod.cc mixprod_SOURCES = mixprod.cc readsave_SOURCES = readsave.cc -tbalbtt_SOURCES = spotlbtt.cc -tbalbtt_CXXFLAGS = -DTBA tgbaread_SOURCES = tgbaread.cc tripprod_SOURCES = tripprod.cc -spotlbtt_SOURCES = spotlbtt.cc # Keep this sorted by STRENGTH. Test basic things first, # because such failures will be easier to diagnose and fix. diff --git a/src/tgbatest/ltl2tgba.cc b/src/tgbatest/ltl2tgba.cc index 9b681af77..3167499b5 100644 --- a/src/tgbatest/ltl2tgba.cc +++ b/src/tgbatest/ltl2tgba.cc @@ -1,9 +1,12 @@ #include #include +#include +#include #include "ltlvisit/destroy.hh" #include "ltlast/allnodes.hh" #include "ltlparse/public.hh" #include "tgbaalgos/ltl2tgba.hh" +#include "tgbaalgos/ltl2tgba_fm.hh" #include "tgba/bddprint.hh" #include "tgbaalgos/dotty.hh" #include "tgbaalgos/lbtt.hh" @@ -13,6 +16,7 @@ void syntax(char* prog) { std::cerr << "Usage: "<< prog << " [OPTIONS...] formula" << std::endl + << " "<< prog << " -F [OPTIONS...] file" << std::endl << std::endl << "Options:" << std::endl << " -a display the accepting_conditions BDD, not the reachability graph" @@ -20,6 +24,9 @@ syntax(char* prog) << " -A same as -a, but as a set" << std::endl << " -d turn on traces during parsing" << std::endl << " -D degeneralize the automaton" << std::endl + << " -f use Couvreur's FM algorithm for translation" + << std::endl + << " -F read the formula from the file" << std::endl << " -r display the relation BDD, not the reachability graph" << std::endl << " -R same as -r, but as a set" << std::endl @@ -36,6 +43,8 @@ main(int argc, char** argv) bool debug_opt = false; bool degeneralize_opt = false; + bool fm_opt = false; + bool file_opt = false; int output = 0; int formula_index = 0; @@ -62,6 +71,14 @@ main(int argc, char** argv) { degeneralize_opt = true; } + else if (!strcmp(argv[formula_index], "-f")) + { + fm_opt = true; + } + else if (!strcmp(argv[formula_index], "-F")) + { + file_opt = true; + } else if (!strcmp(argv[formula_index], "-r")) { output = 1; @@ -84,19 +101,46 @@ main(int argc, char** argv) } } + std::string input; + + if (file_opt) + { + std::ifstream fin(argv[formula_index]); + if (! fin) + { + std::cerr << "Cannot open " << argv[formula_index] << std::endl; + exit(2); + } + + if (! std::getline(fin, input, '\0')) + { + std::cerr << "Cannot read " << argv[formula_index] << std::endl; + exit(2); + } + } + else + { + input = argv[formula_index]; + } + spot::ltl::environment& env(spot::ltl::default_environment::instance()); spot::ltl::parse_error_list pel; - spot::ltl::formula* f = spot::ltl::parse(argv[formula_index], - pel, env, debug_opt); + spot::ltl::formula* f = spot::ltl::parse(input, pel, env, debug_opt); - exit_code = - spot::ltl::format_parse_errors(std::cerr, argv[formula_index], pel); + exit_code = spot::ltl::format_parse_errors(std::cerr, input, pel); spot::bdd_dict* dict = new spot::bdd_dict(); if (f) { - spot::tgba_bdd_concrete* concrete = spot::ltl_to_tgba(f, dict); - spot::tgba* a = concrete; + spot::tgba_bdd_concrete* concrete = 0; + spot::tgba* to_free = 0; + spot::tgba* a = 0; + + if (fm_opt) + to_free = a = spot::ltl_to_tgba_fm(f, dict); + else + to_free = a = concrete = spot::ltl_to_tgba(f, dict); + spot::ltl::destroy(f); spot::tgba* degeneralized = 0; @@ -109,20 +153,26 @@ main(int argc, char** argv) spot::dotty_reachable(std::cout, a); break; case 1: - spot::bdd_print_dot(std::cout, concrete->get_dict(), - concrete->get_core_data().relation); + if (concrete) + spot::bdd_print_dot(std::cout, concrete->get_dict(), + concrete->get_core_data().relation); break; case 2: - spot::bdd_print_dot(std::cout, concrete->get_dict(), - concrete->get_core_data().accepting_conditions); + if (concrete) + spot::bdd_print_dot(std::cout, concrete->get_dict(), + concrete-> + get_core_data().accepting_conditions); break; case 3: - spot::bdd_print_set(std::cout, concrete->get_dict(), - concrete->get_core_data().relation); + if (concrete) + spot::bdd_print_set(std::cout, concrete->get_dict(), + concrete->get_core_data().relation); break; case 4: - spot::bdd_print_set(std::cout, concrete->get_dict(), - concrete->get_core_data().accepting_conditions); + if (concrete) + spot::bdd_print_set(std::cout, concrete->get_dict(), + concrete-> + get_core_data().accepting_conditions); break; case 5: a->get_dict()->dump(std::cout); @@ -137,7 +187,7 @@ main(int argc, char** argv) if (degeneralize_opt) delete degeneralized; - delete concrete; + delete to_free; } else { diff --git a/src/tgbatest/spotlbtt.cc b/src/tgbatest/spotlbtt.cc deleted file mode 100644 index 519f5977b..000000000 --- a/src/tgbatest/spotlbtt.cc +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include -#include "ltlvisit/destroy.hh" -#include "ltlast/allnodes.hh" -#include "ltlparse/public.hh" -#include "tgbaalgos/ltl2tgba.hh" -#include "tgbaalgos/lbtt.hh" -#include "tgba/tgbatba.hh" - -void -syntax(char* prog) -{ - std::cerr << "Usage: "<< prog << " file" << std::endl; - exit(2); -} - -int -main(int argc, char** argv) -{ - int exit_code = 0; - - if (argc != 2) - syntax(argv[0]); - - std::ifstream fin(argv[1]); - if (! fin) - { - std::cerr << "Cannot open " << argv[1] << std::endl; - exit(2); - } - - std::string input; - if (! std::getline(fin, input, '\0')) - { - std::cerr << "Cannot read " << argv[1] << std::endl; - exit(2); - } - - spot::bdd_dict* dict = new spot::bdd_dict(); - - spot::ltl::environment& env(spot::ltl::default_environment::instance()); - spot::ltl::parse_error_list pel; - spot::ltl::formula* f = spot::ltl::parse(input, pel, env); - - exit_code = spot::ltl::format_parse_errors(std::cerr, input, pel); - - if (f) - { - spot::tgba* a; - spot::tgba* c = a = spot::ltl_to_tgba(f, dict); - spot::ltl::destroy(f); -#ifdef TBA - spot::tgba* d = a = new spot::tgba_tba_proxy(a); -#endif - spot::lbtt_reachable(std::cout, a); -#ifdef TBA - delete d; -#endif - delete c; - } - else - { - exit_code = 1; - } - - assert(spot::ltl::atomic_prop::instance_count() == 0); - assert(spot::ltl::unop::instance_count() == 0); - assert(spot::ltl::binop::instance_count() == 0); - assert(spot::ltl::multop::instance_count() == 0); - delete dict; - return exit_code; -} diff --git a/src/tgbatest/spotlbtt.test b/src/tgbatest/spotlbtt.test index 76b133c6b..e9059f2db 100755 --- a/src/tgbatest/spotlbtt.test +++ b/src/tgbatest/spotlbtt.test @@ -7,15 +7,29 @@ set -e cat > config <

Formula to translate:
-Options:
""" % (myself, formula) +Translator:
""" % (myself, formula) + + +trans = form.getfirst("trans", default_translator) +for opt, desc, in translators: + if trans == opt: + str = "checked" + else: + str = "" + globals()[opt] = str + print '%s
' % (opt, str, + desc) + +print """
+Options:
""" for opt, desc, arg, in options: if formula: @@ -140,7 +160,10 @@ dict = spot.bdd_dict() print '

Building automaton...', sys.stdout.flush() -concrete = spot.ltl_to_tgba(f, dict) +if trans_lacim: + automaton = spot.ltl_to_tgba(f, dict) +elif trans_fm: + automaton = spot.ltl_to_tgba_fm(f, dict) print 'done.

' sys.stdout.flush() @@ -148,14 +171,14 @@ sys.stdout.flush() if show_automaton_dot: print '
'; sys.stdout.flush()
     s = spot.ostringstream()
-    spot.dotty_reachable(s, concrete)
+    spot.dotty_reachable(s, automaton)
     print cgi.escape(s.str())
     del s
     print '
'; sys.stdout.flush() if show_automaton_gif: outfile = spot.ofstream(imgprefix + '-a.dot') - spot.dotty_reachable(outfile, concrete) + spot.dotty_reachable(outfile, automaton) del outfile os.spawnlp(os.P_WAIT, 'dot', 'dot', '-Tgif', '-Gsize=14,14', '-o', imgprefix + '-a.gif', imgprefix + '-a.dot') @@ -166,7 +189,7 @@ if show_automaton_gif: if show_degen_dot or show_degen_gif: print '

Degeneralized automaton

' - degen = spot.tgba_tba_proxy(concrete) + degen = spot.tgba_tba_proxy(automaton) if show_degen_dot: print '
'; sys.stdout.flush()
 	s = spot.ostringstream()
@@ -190,21 +213,22 @@ if show_dictionnay:
     print '

BDD dictionary

' print '
'
     sys.stdout.flush()
-    concrete.get_dict().dump(spot.get_cout())
+    automaton.get_dict().dump(spot.get_cout())
     print '
' -if show_relation_dot or show_relation_set or show_relation_gif: +if (type(automaton) == spot.tgba_bdd_concrete + and (show_relation_dot or show_relation_set or show_relation_gif)): print '

Transition relation

' if show_relation_dot: - escaped_print_dot(concrete.get_dict(), - concrete.get_core_data().relation) + escaped_print_dot(automaton.get_dict(), + automaton.get_core_data().relation) if show_relation_set: - escaped_print_set(concrete.get_dict(), - concrete.get_core_data().relation) + escaped_print_set(automaton.get_dict(), + automaton.get_core_data().relation) if show_relation_gif: outfile = spot.ofstream(imgprefix + '-b.dot') - spot.bdd_print_dot(outfile, concrete.get_dict(), - concrete.get_core_data().relation) + spot.bdd_print_dot(outfile, automaton.get_dict(), + automaton.get_core_data().relation) del outfile os.spawnlp(os.P_WAIT, 'dot', 'dot', '-Tgif', '-Gsize=14,14', '-o', imgprefix + '-b.gif', imgprefix + '-b.dot') @@ -212,18 +236,19 @@ if show_relation_dot or show_relation_set or show_relation_gif: imgprefix + '-b.gif', imgprefix + '-b.png') print '' -if show_acceptance_dot or show_acceptance_set or show_acceptance_gif: +if (type(automaton) == spot.tgba_bdd_concrete + and (show_acceptance_dot or show_acceptance_set or show_acceptance_gif)): print '

Acceptance relation

' if show_acceptance_dot: - escaped_print_dot(concrete.get_dict(), - concrete.get_core_data().accepting_conditions) + escaped_print_dot(automaton.get_dict(), + automaton.get_core_data().accepting_conditions) if show_acceptance_set: - escaped_print_set(concrete.get_dict(), - concrete.get_core_data().accepting_conditions) + escaped_print_set(automaton.get_dict(), + automaton.get_core_data().accepting_conditions) if show_acceptance_gif: outfile = spot.ofstream(imgprefix + '-c.dot') - spot.bdd_print_dot(outfile, concrete.get_dict(), - concrete.get_core_data().accepting_conditions) + spot.bdd_print_dot(outfile, automaton.get_dict(), + automaton.get_core_data().accepting_conditions) del outfile os.spawnlp(os.P_WAIT, 'dot', 'dot', '-Tgif', '-Gsize=14,14', '-o', imgprefix + '-c.gif', imgprefix + '-c.dot') @@ -237,7 +262,7 @@ if show_lbtt: print '

Conversion of the generalized automaton

' print '
'
     sys.stdout.flush()
-    spot.lbtt_reachable(spot.get_cout(), concrete)
+    spot.lbtt_reachable(spot.get_cout(), automaton)
     print '
' if degen: print '

Conversion of the degeneralized automaton

' @@ -248,9 +273,10 @@ if show_lbtt: sys.stdout.flush() -# Make sure degen is cleared before concrete. +spot.destroy(f) +# Make sure degen is cleared before automaton. del degen -del concrete +del automaton print '
' print 'ltl2tgba.py @PACKAGE_VERSION@; Spot', spot.version() diff --git a/wrap/python/spot.i b/wrap/python/spot.i index 33c128199..0f4a8405e 100644 --- a/wrap/python/spot.i +++ b/wrap/python/spot.i @@ -49,6 +49,7 @@ #include "tgba/tgbatba.hh" #include "tgbaalgos/ltl2tgba.hh" +#include "tgbaalgos/ltl2tgba_fm.hh" #include "tgbaalgos/dotty.hh" #include "tgbaalgos/lbtt.hh" #include "tgbaalgos/magic.hh" @@ -84,6 +85,7 @@ using namespace spot; %include "ltlvisit/tunabbrev.hh" %feature("new") spot::ltl_to_tgba; +%feature("new") spot::ltl_to_tgba_fm; %feature("new") spot::tgba::get_init_state; %feature("new") spot::tgba::succ_iter; %feature("new") spot::tgba_succ_iterator::current_state; @@ -104,6 +106,7 @@ using namespace spot; %include "tgba/tgbatba.hh" %include "tgbaalgos/ltl2tgba.hh" +%include "tgbaalgos/ltl2tgba_fm.hh" %include "tgbaalgos/dotty.hh" %include "tgbaalgos/lbtt.hh" %include "tgbaalgos/magic.hh" diff --git a/wrap/python/tests/ltl2tgba.py b/wrap/python/tests/ltl2tgba.py index ca84c7184..12c77a166 100755 --- a/wrap/python/tests/ltl2tgba.py +++ b/wrap/python/tests/ltl2tgba.py @@ -14,6 +14,7 @@ Options: -A same as -a, but as a set -d turn on traces during parsing -D degeneralize the automaton + -f use Couvreur's FM algorithm for translation -r display the relation BDD, not the reachability graph -R same as -r, but as a set -t display reachable states in LBTT's format @@ -23,7 +24,7 @@ Options: prog = sys.argv[0] try: - opts, args = getopt.getopt(sys.argv[1:], 'aAdDrRtv') + opts, args = getopt.getopt(sys.argv[1:], 'aAdDfrRtv') except getopt.GetoptError: usage(prog) @@ -31,6 +32,7 @@ exit_code = 0 debug_opt = 0 degeneralize_opt = None output = 0 +fm_opt = 0 for o, a in opts: if o == '-a': @@ -41,6 +43,8 @@ for o, a in opts: debug_opt = 1 elif o == '-D': degeneralize_opt = 1 + elif o == '-f': + fm_opt = 1 elif o == '-r': output = 1 elif o == '-R': @@ -65,14 +69,17 @@ p = spot.empty_parse_error_list() f = spot.parse(args[0], p, e, debug_opt) if spot.format_parse_errors(cerr, args[0], p): exit_code = 1 - + dict = spot.bdd_dict() if f: - concrete = spot.ltl_to_tgba(f, dict) + if fm_opt: + a = spot.ltl_to_tgba_fm(f, dict) + concrete = 0 + else: + a = concrete = spot.ltl_to_tgba(f, dict) spot.destroy(f) del f - a = concrete degeneralized = None if degeneralize_opt: @@ -81,18 +88,22 @@ if f: if output == 0: spot.dotty_reachable(cout, a) elif output == 1: - spot.bdd_print_dot(cout, concrete.get_dict(), - concrete.get_core_data().relation) + if concrete: + spot.bdd_print_dot(cout, concrete.get_dict(), + concrete.get_core_data().relation) elif output == 2: - spot.bdd_print_dot(cout, concrete.get_dict(), - concrete.get_core_data().accepting_conditions) + if concrete: + spot.bdd_print_dot(cout, concrete.get_dict(), + concrete.get_core_data().accepting_conditions) elif output == 3: - spot.bdd_print_set(cout, concrete.get_dict(), - concrete.get_core_data().relation) + if concrete: + spot.bdd_print_set(cout, concrete.get_dict(), + concrete.get_core_data().relation) print elif output == 4: - spot.bdd_print_set(cout, concrete.get_dict(), - concrete.get_core_data().accepting_conditions) + if concrete: + spot.bdd_print_set(cout, concrete.get_dict(), + concrete.get_core_data().accepting_conditions) print elif output == 5: a.get_dict().dump(cout) @@ -116,4 +127,3 @@ assert spot.atomic_prop.instance_count() == 0 assert spot.unop.instance_count() == 0 assert spot.binop.instance_count() == 0 assert spot.multop.instance_count() == 0 - diff --git a/wrap/python/tests/ltl2tgba.test b/wrap/python/tests/ltl2tgba.test index 3400f06cf..8fcc33c9b 100755 --- a/wrap/python/tests/ltl2tgba.test +++ b/wrap/python/tests/ltl2tgba.test @@ -14,3 +14,13 @@ set -e ./run ltl2tgba.py 'Fa & Xb & GFc & Gd' ./run ltl2tgba.py 'Fa & Xa & GFc & Gc' ./run ltl2tgba.py 'Fc & X(a | Xb) & GF(a | Xb) & Gc' + +./run ltl2tgba.py -f a +./run ltl2tgba.py -f 'a U b' +./run ltl2tgba.py -f 'X a' +./run ltl2tgba.py -f 'a & b & c' +./run ltl2tgba.py -f 'a | b | (c U (d & (g U (h ^ i))))' +./run ltl2tgba.py -f 'Xa & (b U !a) & (b U !a)' +./run ltl2tgba.py -f 'Fa & Xb & GFc & Gd' +./run ltl2tgba.py -f 'Fa & Xa & GFc & Gc' +./run ltl2tgba.py -f 'Fc & X(a | Xb) & GF(a | Xb) & Gc'