spot/spot/mc/utils.hh
Etienne Renault 17579ad9ad 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.
2020-06-03 12:22:41 +02:00

372 lines
11 KiB
C++

// -*- coding: utf-8 -*-
// Copyright (C) 2016, 2020 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 <http://www.gnu.org/licenses/>.
#pragma once
#include <spot/mc/intersect.hh>
#include <spot/twa/twa.hh>
#include <spot/twacube_algos/convert.hh>
namespace spot
{
/// \brief convert a (cube) model into a twa.
/// Note that this algorithm cannot be run in parallel but could.
template<typename State, typename SuccIterator,
typename StateHash, typename StateEqual>
class SPOT_API kripkecube_to_twa
{
public:
kripkecube_to_twa(kripkecube<State, SuccIterator>& sys, bdd_dict_ptr dict):
sys_(sys), dict_(dict)
{
static_assert(spot::is_a_kripkecube_ptr<decltype(&sys),
State, SuccIterator>::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()
{
auto d = spot::make_bdd_dict();
res_ = make_twa_graph(d);
names_ = new std::vector<std::string>();
int i = 0;
for (auto ap : sys_.ap())
{
auto idx = res_->register_ap(ap);
reverse_binder_[i++] = idx;
}
}
bool push(State s, unsigned i)
{
unsigned st = res_->new_state();
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(sys_.ap().size());
bdd cond = cube_to_bdd(todo_.back().it->condition(),
cs, reverse_binder_);
res_->new_edge(src, dst, cond);
}
void finalize()
{
res_->purge_unreachable_states();
res_->set_named_prop<std::vector<std::string>>("state-names", names_);
}
twa_graph_ptr twa()
{
return res_;
}
protected:
struct todo__element
{
State s;
SuccIterator* it;
};
typedef std::unordered_map<const State, int,
StateHash, StateEqual> visited__map;
kripkecube<State, SuccIterator>& sys_;
std::vector<todo__element> todo_;
visited__map visited_;
unsigned int dfs_number_ = 0;
unsigned int transitions_ = 0;
spot::twa_graph_ptr res_;
std::vector<std::string>* names_;
bdd_dict_ptr dict_;
std::unordered_map<int, int> reverse_binder_;
};
/// \brief convert a (cube) product automaton into a twa
/// Note that this algorithm cannot be run in parallel.
template<typename State, typename SuccIterator,
typename StateHash, typename StateEqual>
class SPOT_API product_to_twa
{
struct product_state
{
State st_kripke;
unsigned st_prop;
};
struct product_state_equal
{
bool
operator()(const product_state lhs,
const product_state rhs) const
{
StateEqual equal;
return (lhs.st_prop == rhs.st_prop) &&
equal(lhs.st_kripke, rhs.st_kripke);
}
};
struct product_state_hash
{
size_t
operator()(const product_state lhs) const noexcept
{
StateHash hash;
unsigned u = hash(lhs.st_kripke) % (1<<30);
u = wang32_hash(lhs.st_prop) ^ u;
u = u % (1<<30);
return u;
}
};
public:
product_to_twa(kripkecube<State, SuccIterator>& sys,
twacube_ptr twa):
sys_(sys), twa_(twa)
{
static_assert(spot::is_a_kripkecube_ptr<decltype(&sys),
State, SuccIterator>::value,
"error: does not match the kripkecube requirements");
}
virtual ~product_to_twa()
{
map.clear();
}
bool run()
{
setup();
product_state initial = {sys_.initial(0), twa_->get_initial()};
if (SPOT_LIKELY(push_state(initial, dfs_number_+1, {})))
{
todo_.push_back({initial, sys_.succ(initial.st_kripke, 0),
twa_->succ(initial.st_prop)});
// Not going further! It's a product with a single state.
if (todo_.back().it_prop->done())
return false;
forward_iterators(sys_, twa_, todo_.back().it_kripke,
todo_.back().it_prop, true, 0);
map[initial] = ++dfs_number_;
}
while (!todo_.empty())
{
// Check the kripke is enough since it's the outer loop. More
// details in forward_iterators.
if (todo_.back().it_kripke->done())
{
bool is_init = todo_.size() == 1;
auto newtop = is_init? todo_.back().st: todo_[todo_.size() -2].st;
if (SPOT_LIKELY(pop_state(todo_.back().st,
map[todo_.back().st],
is_init,
newtop,
map[newtop])))
{
sys_.recycle(todo_.back().it_kripke, 0);
todo_.pop_back();
}
}
else
{
++transitions_;
product_state dst = {
todo_.back().it_kripke->state(),
twa_->trans_storage(todo_.back().it_prop, 0).dst
};
auto acc = twa_->trans_data(todo_.back().it_prop, 0).acc_;
forward_iterators(sys_, twa_, todo_.back().it_kripke,
todo_.back().it_prop, false, 0);
auto it = map.find(dst);
if (it == map.end())
{
if (SPOT_LIKELY(push_state(dst, dfs_number_+1, acc)))
{
map[dst] = ++dfs_number_;
todo_.push_back({dst, sys_.succ(dst.st_kripke, 0),
twa_->succ(dst.st_prop)});
forward_iterators(sys_, twa_, todo_.back().it_kripke,
todo_.back().it_prop, true, 0);
}
}
else if (SPOT_UNLIKELY(update(todo_.back().st, dfs_number_,
dst, map[dst], acc)))
return true;
}
}
return false;
}
twa_graph_ptr twa()
{
res_->purge_unreachable_states();
res_->set_named_prop<std::vector<std::string>>("state-names", names_);
return res_;
}
void setup()
{
auto d = spot::make_bdd_dict();
res_ = make_twa_graph(d);
names_ = new std::vector<std::string>();
int i = 0;
for (auto ap : sys_.ap())
{
auto idx = res_->register_ap(ap);
reverse_binder_[i++] = idx;
}
}
bool push_state(product_state s, unsigned i, acc_cond::mark_t)
{
unsigned st = res_->new_state();
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_);
twa_->get_cubeset().release(c);
res_->new_edge(map[todo_.back().st]-1, st, x,
twa_->trans_data
(todo_.back().it_prop).acc_);
}
names_->push_back(sys_.to_string(s.st_kripke) +
('*' + std::to_string(s.st_prop)));
SPOT_ASSERT(st+1 == i);
return true;
}
bool update(product_state, unsigned src,
product_state, unsigned dst,
acc_cond::mark_t cond)
{
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_);
twa_->get_cubeset().release(c);
res_->new_edge(src-1, dst-1, x, cond);
return false;
}
bool pop_state(product_state, unsigned, bool, product_state, unsigned)
{
return true;
}
private:
struct todo__element
{
product_state st;
SuccIterator* it_kripke;
std::shared_ptr<trans_index> it_prop;
};
typedef std::unordered_map<const product_state, int,
product_state_hash,
product_state_equal> visited_map;
kripkecube<State, SuccIterator>& sys_;
twacube_ptr twa_;
std::vector<todo__element> todo_;
visited_map map;
unsigned int dfs_number_ = 0;
unsigned int transitions_ = 0;
spot::twa_graph_ptr res_;
std::vector<std::string>* names_;
std::unordered_map<int, int> reverse_binder_;
};
}