From 17579ad9adb24fb60324b60fc16646b13e1add68 Mon Sep 17 00:00:00 2001 From: Etienne Renault Date: Thu, 14 May 2020 13:22:23 +0200 Subject: [PATCH] mc: rework and test conversion into twa * spot/mc/Makefile.am, spot/mc/reachability.hh, spot/mc/utils.hh, tests/Makefile.am, tests/ltsmin/.gitignore, tests/ltsmin/testconvert.cc, tests/ltsmin/testconvert.test: Here. --- spot/mc/Makefile.am | 4 +- spot/mc/reachability.hh | 126 ---------------------- spot/mc/utils.hh | 190 +++++++++++++++++++++++----------- tests/Makefile.am | 25 +++-- tests/ltsmin/.gitignore | 1 + tests/ltsmin/testconvert.cc | 133 ++++++++++++++++++++++++ tests/ltsmin/testconvert.test | 54 ++++++++++ 7 files changed, 337 insertions(+), 196 deletions(-) delete mode 100644 spot/mc/reachability.hh create mode 100644 tests/ltsmin/testconvert.cc create mode 100644 tests/ltsmin/testconvert.test diff --git a/spot/mc/Makefile.am b/spot/mc/Makefile.am index d0eb7fe68..e84815433 100644 --- a/spot/mc/Makefile.am +++ b/spot/mc/Makefile.am @@ -21,8 +21,8 @@ AM_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) $(BUDDY_CPPFLAGS) AM_CXXFLAGS = $(WARNING_CXXFLAGS) mcdir = $(pkgincludedir)/mc -mc_HEADERS = reachability.hh intersect.hh lpar13.hh unionfind.hh utils.hh\ - mc.hh mc_instanciator.hh deadlock.hh bloemen.hh bloemen_ec.hh cndfs.hh +mc_HEADERS = intersect.hh lpar13.hh unionfind.hh utils.hh mc.hh\ + mc_instanciator.hh deadlock.hh bloemen.hh bloemen_ec.hh cndfs.hh noinst_LTLIBRARIES = libmc.la diff --git a/spot/mc/reachability.hh b/spot/mc/reachability.hh deleted file mode 100644 index 53d82e728..000000000 --- a/spot/mc/reachability.hh +++ /dev/null @@ -1,126 +0,0 @@ -// -*- coding: utf-8 -*- -// Copyright (C) 2015, 2016, 2019 Laboratoire de Recherche et -// Developpement de l'Epita -// -// 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 . - -#pragma once - -#include - -namespace spot -{ - /// \brief This template class provide a sequential reachability - /// of a kripkecube. The algorithm uses a single DFS since it - /// is the most efficient in a sequential setting - template - class SPOT_API seq_reach_kripke - { - public: - seq_reach_kripke(kripkecube& sys, unsigned tid): - sys_(sys), tid_(tid) - { - static_assert(spot::is_a_kripkecube_ptr::value, - "error: does not match the kripkecube requirements"); - visited.reserve(2000000); - todo.reserve(100000); - } - - ~seq_reach_kripke() - { - // States will be destroyed by the system, so just clear map - visited.clear(); - } - - Visitor& self() - { - return static_cast(*this); - } - - void run() - { - self().setup(); - State initial = sys_.initial(tid_); - if (SPOT_LIKELY(self().push(initial, dfs_number))) - { - todo.push_back({initial, sys_.succ(initial, tid_)}); - visited[initial] = ++dfs_number; - } - while (!todo.empty()) - { - if (todo.back().it->done()) - { - if (SPOT_LIKELY(self().pop(todo.back().s))) - { - sys_.recycle(todo.back().it, tid_); - todo.pop_back(); - } - } - else - { - ++transitions; - State dst = todo.back().it->state(); - auto it = visited.insert({dst, dfs_number+1}); - if (it.second) - { - ++dfs_number; - if (SPOT_LIKELY(self().push(dst, dfs_number))) - { - todo.back().it->next(); - todo.push_back({dst, sys_.succ(dst, tid_)}); - } - } - else - { - self().edge(visited[todo.back().s], visited[dst]); - todo.back().it->next(); - } - } - } - self().finalize(); - } - - unsigned int states() - { - return dfs_number; - } - - unsigned int trans() - { - return transitions; - } - - protected: - struct todo_element - { - State s; - SuccIterator* it; - }; - kripkecube& sys_; - std::vector todo; - // FIXME: The system already handle a set of visited states so - // this map is redundant: an we avoid this new map? - typedef std::unordered_map visited_map; - visited_map visited; - unsigned int dfs_number = 0; - unsigned int transitions = 0; - unsigned int tid_; - }; -} diff --git a/spot/mc/utils.hh b/spot/mc/utils.hh index 8ffdc6831..9f80c8764 100644 --- a/spot/mc/utils.hh +++ b/spot/mc/utils.hh @@ -19,86 +19,149 @@ #pragma once -#include #include #include #include namespace spot { + /// \brief convert a (cube) model into a twa. + /// Note that this algorithm cannot be run in parallel but could. template - class SPOT_API kripke_to_twa : - public seq_reach_kripke> + class SPOT_API kripkecube_to_twa { public: - kripke_to_twa(kripkecube& sys, bdd_dict_ptr dict) - : seq_reach_kripke>(sys), - dict_(dict) - {} - ~kripke_to_twa() - { - } + kripkecube_to_twa(kripkecube& sys, bdd_dict_ptr dict): + sys_(sys), dict_(dict) + { + static_assert(spot::is_a_kripkecube_ptr::value, + "error: does not match the kripkecube requirements"); + } + + ~kripkecube_to_twa() + { + visited_.clear(); + } + + void run() + { + setup(); + State initial = sys_.initial(0); + if (SPOT_LIKELY(push(initial, dfs_number_+1))) + { + visited_[initial] = dfs_number_++; + todo_.push_back({initial, sys_.succ(initial, 0)}); + } + while (!todo_.empty()) + { + if (todo_.back().it->done()) + { + if (SPOT_LIKELY(pop(todo_.back().s))) + { + sys_.recycle(todo_.back().it, 0); + todo_.pop_back(); + } + } + else + { + ++transitions_; + State dst = todo_.back().it->state(); + auto it = visited_.find(dst); + if (it == visited_.end()) + { + if (SPOT_LIKELY(push(dst, dfs_number_+1))) + { + visited_[dst] = dfs_number_++; + todo_.back().it->next(); + todo_.push_back({dst, sys_.succ(dst, 0)}); + } + } + else + { + edge(visited_[todo_.back().s], visited_[dst]); + todo_.back().it->next(); + } + } + } + finalize(); + } void setup() { - res_ = make_twa_graph(dict_); - names_ = new std::vector(); + auto d = spot::make_bdd_dict(); + res_ = make_twa_graph(d); + names_ = new std::vector(); - // padding to simplify computation. - res_->new_state(); - - // Compute the reverse binder. - auto aps = this->sys_.ap(); - for (unsigned i = 0; i < aps.size(); ++i) - { - auto k = res_->register_ap(aps[i]); - reverse_binder_.insert({i, k}); - } + int i = 0; + for (auto ap : sys_.ap()) + { + auto idx = res_->register_ap(ap); + reverse_binder_[i++] = idx; + } } - void push(State s, unsigned i) + bool push(State s, unsigned i) { + unsigned st = res_->new_state(); - names_->push_back(this->sys_.to_string(s)); - SPOT_ASSERT(st == i); + names_->push_back(sys_.to_string(s)); + if (!todo_.empty()) + { + edge(visited_[todo_.back().s], st); + } + + SPOT_ASSERT(st+1 == i); + return true; + } + + bool pop(State) + { + return true; } void edge(unsigned src, unsigned dst) { - cubeset cs(this->sys_.ap().size()); - bdd cond = cube_to_bdd(this->todo.back().it->condition(), - cs, reverse_binder_); - res_->new_edge(src, dst, cond); + cubeset cs(sys_.ap().size()); + bdd cond = cube_to_bdd(todo_.back().it->condition(), + cs, reverse_binder_); + res_->new_edge(src, dst, cond); } void finalize() { - res_->set_init_state(1); - res_->purge_unreachable_states(); - res_->set_named_prop>("state-names", names_); + res_->purge_unreachable_states(); + res_->set_named_prop>("state-names", names_); } twa_graph_ptr twa() { - return res_; + return res_; } - private: + protected: + struct todo__element + { + State s; + SuccIterator* it; + }; + + typedef std::unordered_map visited__map; + + kripkecube& sys_; + std::vector todo_; + visited__map visited_; + unsigned int dfs_number_ = 0; + unsigned int transitions_ = 0; spot::twa_graph_ptr res_; std::vector* names_; bdd_dict_ptr dict_; std::unordered_map reverse_binder_; }; - - /// \brief convert a (cube) product automaton into a twa /// Note that this algorithm cannot be run in parallel. templatepurge_unreachable_states(); res_->set_named_prop>("state-names", names_); return res_; } @@ -228,7 +293,7 @@ namespace spot names_ = new std::vector(); int i = 0; - for (auto ap : this->twa_->ap()) + for (auto ap : sys_.ap()) { auto idx = res_->register_ap(ap); reverse_binder_[i++] = idx; @@ -237,24 +302,25 @@ namespace spot bool push_state(product_state s, unsigned i, acc_cond::mark_t) { - // push also implies edge (when it's not the initial state) - if (this->todo_.size()) - { - auto c = this->twa_->get_cubeset() - .intersection(this->twa_->trans_data - (this->todo_.back().it_prop).cube_, - this->todo_.back().it_kripke->condition()); + unsigned st = res_->new_state(); - bdd x = spot::cube_to_bdd(c, this->twa_->get_cubeset(), + if (!todo_.empty()) + { + auto c = twa_->get_cubeset() + .intersection(twa_->trans_data + (todo_.back().it_prop).cube_, + todo_.back().it_kripke->condition()); + + bdd x = spot::cube_to_bdd(c, twa_->get_cubeset(), reverse_binder_); - this->twa_->get_cubeset().release(c); - res_->new_edge(this->map[this->todo_.back().st]-1, i-1, x, - this->twa_->trans_data - (this->todo_.back().it_prop).acc_); + twa_->get_cubeset().release(c); + res_->new_edge(map[todo_.back().st]-1, st, x, + twa_->trans_data + (todo_.back().it_prop).acc_); } - unsigned st = res_->new_state(); - names_->push_back(this->sys_.to_string(s.st_kripke) + + + names_->push_back(sys_.to_string(s.st_kripke) + ('*' + std::to_string(s.st_prop))); SPOT_ASSERT(st+1 == i); return true; @@ -264,14 +330,14 @@ namespace spot product_state, unsigned dst, acc_cond::mark_t cond) { - auto c = this->twa_->get_cubeset() - .intersection(this->twa_->trans_data - (this->todo_.back().it_prop).cube_, - this->todo_.back().it_kripke->condition()); + auto c = twa_->get_cubeset() + .intersection(twa_->trans_data + (todo_.back().it_prop).cube_, + todo_.back().it_kripke->condition()); - bdd x = spot::cube_to_bdd(c, this->twa_->get_cubeset(), + bdd x = spot::cube_to_bdd(c, twa_->get_cubeset(), reverse_binder_); - this->twa_->get_cubeset().release(c); + twa_->get_cubeset().release(c); res_->new_edge(src-1, dst-1, x, cond); return false; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 0d7c65816..00f8d42cd 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -458,6 +458,7 @@ EXTRA_DIST = \ if USE_LTSMIN check_PROGRAMS += ltsmin/modelcheck +check_PROGRAMS += ltsmin/testconvert ltsmin_modelcheck_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) \ $(BUDDY_CPPFLAGS) \-I$(top_builddir)/lib -I$(top_srcdir)/lib ltsmin_modelcheck_CXXFLAGS = $(CXXFLAGS) -pthread @@ -468,19 +469,31 @@ ltsmin_modelcheck_LDADD = \ $(top_builddir)/spot/libspot.la \ $(top_builddir)/spot/ltsmin/libspotltsmin.la +ltsmin_testconvert_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) \ + $(BUDDY_CPPFLAGS) \-I$(top_builddir)/lib -I$(top_srcdir)/lib +ltsmin_testconvert_CXXFLAGS = $(CXXFLAGS) -pthread +ltsmin_testconvert_SOURCES = ltsmin/testconvert.cc +ltsmin_testconvert_LDADD = \ + $(top_builddir)/bin/libcommon.a \ + $(top_builddir)/lib/libgnu.la \ + $(top_builddir)/spot/libspot.la \ + $(top_builddir)/spot/ltsmin/libspotltsmin.la + + check_SCRIPTS += ltsmin/defs ltsmin/defs: $(top_builddir)/config.status $(srcdir)/core/defs.in $(top_builddir)/config.status --file ltsmin/defs:core/defs.in -TESTS_ltsmin = \ - ltsmin/check.test \ - ltsmin/check2.test \ - ltsmin/check3.test \ - ltsmin/finite.test \ +TESTS_ltsmin = \ + ltsmin/check.test \ + ltsmin/check2.test \ + ltsmin/check3.test \ + ltsmin/finite.test \ ltsmin/finite2.test \ ltsmin/finite3.test \ - ltsmin/kripke.test + ltsmin/kripke.test \ + ltsmin/testconvert.test EXTRA_DIST += ltsmin/beem-peterson.4.dve ltsmin/beem-peterson.4.gal \ ltsmin/elevator2.1.pm ltsmin/finite.dve ltsmin/finite.pm ltsmin/finite.gal diff --git a/tests/ltsmin/.gitignore b/tests/ltsmin/.gitignore index b3518bd8b..7659746cc 100644 --- a/tests/ltsmin/.gitignore +++ b/tests/ltsmin/.gitignore @@ -1,2 +1,3 @@ defs modelcheck +testconvert diff --git a/tests/ltsmin/testconvert.cc b/tests/ltsmin/testconvert.cc new file mode 100644 index 000000000..cfd84333c --- /dev/null +++ b/tests/ltsmin/testconvert.cc @@ -0,0 +1,133 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2020 Laboratoire de Recherche et Développement de l'Epita. +// +// 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 "config.h" +#include +#include "bin/common_conv.hh" +#include "bin/common_setup.hh" +#include "bin/common_output.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// FIXME (1) model (2)formula +int main(int argc, char** argv) +{ + // Build the formula + char* formula = argv[2]; + spot::formula f = nullptr; + spot::default_environment& env = spot::default_environment::instance(); + spot::atomic_prop_set ap; + spot::const_twa_graph_ptr prop = nullptr; + auto dict = spot::make_bdd_dict(); + { + auto pf = spot::parse_infix_psl(formula, env, false); + f = pf.f; + spot::translator trans(dict); + prop = trans.run(&f); + } + + atomic_prop_collect(f, &ap); + + auto propcube = spot::twa_to_twacube(prop); + + // Load model + spot::ltsmin_kripkecube_ptr modelcube = nullptr; + try + { + modelcube = spot::ltsmin_model::load(argv[1]) + .kripkecube(propcube->ap(), spot::formula::tt(), 0, 1); + } + catch (const std::runtime_error& e) + { + std::cerr << e.what() << '\n'; + } + + // ------------------------------------------------------------------ + // check that cube and bdd approaches agree for the size of the model + // ------------------------------------------------------------------ + auto result0 = + spot::ec_instanciator + (spot::mc_algorithm::REACHABILITY, modelcube, propcube, false); + std::cout << "[CUBE] Model: " + << result0.states[0] << ',' + << result0.transitions[0] + << '\n'; + + + spot::kripkecube_to_twa ktt(*modelcube, dict); + ktt.run(); + auto twa_from_model = ktt.twa(); + std::cout << "[BDD] Model: " + << twa_from_model->num_states() << ',' + << twa_from_model->num_edges() << '\n'; + + + + // ------------------------------------------------------------------ + // check that cube and bdd approaches agree for the emptiness and the + // size of the synchronous product + // ------------------------------------------------------------------ + auto result1 = + spot::ec_instanciator + (spot::mc_algorithm::BLOEMEN_EC, modelcube, propcube, false); + auto result2 = + spot::ec_instanciator + (spot::mc_algorithm::REACHABILITY, modelcube, propcube, false); + + std::cout << "[CUBE] Product: " + << result1.value[0] << ',' + << result1.states[0] << ',' + << result1.transitions[0] + << '\n'; + + spot::product_to_twa ptt(*modelcube, propcube); + ptt.run(); + auto twa_from_product = ptt.twa(); + std::cout << "[BDD] Product: " + << (twa_from_product->is_empty() ? "empty" : "not_empty") + << ',' + << twa_from_product->num_states() << ',' + << twa_from_product->num_edges() + << '\n'; +} diff --git a/tests/ltsmin/testconvert.test b/tests/ltsmin/testconvert.test new file mode 100644 index 000000000..7e925782c --- /dev/null +++ b/tests/ltsmin/testconvert.test @@ -0,0 +1,54 @@ + #! /bin/sh +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Laboratoire de Recherche et Developpement +# de l'Epita (LRDE) +# +# 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 . + + + +. ./defs + +divine compile > output 2>&1 + +if grep -i 'ltsmin ' output; then + : +else + echo "divine not installed, or no ltsmin interface" + exit 77 +fi + +set -e + +run 0 ../testconvert $srcdir/finite.dve 'true' > output +cat << EOF > expected +[CUBE] Model: 15,24 +[BDD] Model: 15,24 +[CUBE] Product: not_empty,4,4 +[BDD] Product: not_empty,15,24 +EOF + +cmp output expected + +run 0 ../testconvert $srcdir/finite.dve 'G "P.a==5"' > output +cat << EOF > expected +[CUBE] Model: 15,24 +[BDD] Model: 15,24 +[CUBE] Product: empty,1,0 +[BDD] Product: empty,1,0 +EOF + +cmp output expected