zlktree: use a cache in the construction of zielonka_tree
This largely speeds up the computation for conditions like "Rabin n" sharing a lot of subtrees. Also implement options to stop the construction if the shape is wrong. * spot/twaalgos/zlktree.cc, spot/twaalgos/zlktree.hh: Implement the cache and the options. * tests/python/zlktree.ipynb, tests/python/zlktree.py: New tests.
This commit is contained in:
parent
f784e40548
commit
b11208440b
5 changed files with 517 additions and 151 deletions
5
NEWS
5
NEWS
|
|
@ -85,6 +85,11 @@ New in spot 2.10.6.dev (not yet released)
|
||||||
- complement() used to always turn tautological acceptance conditions
|
- complement() used to always turn tautological acceptance conditions
|
||||||
into Büchi. It now only does that if the automaton is modified.
|
into Büchi. It now only does that if the automaton is modified.
|
||||||
|
|
||||||
|
- The zielonka_tree construction was optimized using the same
|
||||||
|
memoization trick that is used in ACD. Additionally it can now be
|
||||||
|
run with additional option to abort when the tree as an unwanted
|
||||||
|
shape, or to turn the tree into a DAG.
|
||||||
|
|
||||||
New in spot 2.10.6 (2022-05-18)
|
New in spot 2.10.6 (2022-05-18)
|
||||||
|
|
||||||
Bugs fixed:
|
Bugs fixed:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// -*- coding: utf-8 -*-
|
// -*- coding: utf-8 -*-
|
||||||
// Copyright (C) 2021 Laboratoire de Recherche et Developpement de
|
// Copyright (C) 2021, 2022 Laboratoire de Recherche et Developpement de
|
||||||
// l'Epita (LRDE).
|
// l'Epita (LRDE).
|
||||||
//
|
//
|
||||||
// This file is part of Spot, a model checking library.
|
// This file is part of Spot, a model checking library.
|
||||||
|
|
@ -109,7 +109,8 @@ namespace spot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zielonka_tree::zielonka_tree(const acc_cond& cond)
|
zielonka_tree::zielonka_tree(const acc_cond& cond,
|
||||||
|
zielonka_tree_options opt)
|
||||||
{
|
{
|
||||||
const acc_cond::acc_code& code = cond.get_acceptance();
|
const acc_cond::acc_code& code = cond.get_acceptance();
|
||||||
auto all = cond.all_sets();
|
auto all = cond.all_sets();
|
||||||
|
|
@ -120,11 +121,47 @@ namespace spot
|
||||||
nodes_[0].colors = all;
|
nodes_[0].colors = all;
|
||||||
nodes_[0].level = 0;
|
nodes_[0].level = 0;
|
||||||
|
|
||||||
|
robin_hood::unordered_node_map<acc_cond::mark_t, unsigned> nmap;
|
||||||
|
|
||||||
std::vector<size_model> models;
|
std::vector<size_model> models;
|
||||||
// This loop is a BFS over the increasing set of nodes.
|
// This loop is a BFS over the increasing set of nodes.
|
||||||
for (unsigned node = 0; node < nodes_.size(); ++node)
|
for (unsigned node = 0; node < nodes_.size(); ++node)
|
||||||
{
|
{
|
||||||
acc_cond::mark_t colors = nodes_[node].colors;
|
acc_cond::mark_t colors = nodes_[node].colors;
|
||||||
|
unsigned nextlvl = nodes_[node].level + 1;
|
||||||
|
|
||||||
|
// Have we already seen this combination of colors previously?
|
||||||
|
// If yes, simply copy the children.
|
||||||
|
if (auto p = nmap.emplace(colors, node); !p.second)
|
||||||
|
{
|
||||||
|
unsigned fc = nodes_[p.first->second].first_child;
|
||||||
|
if (!fc) // this is a leaf
|
||||||
|
{
|
||||||
|
++num_branches_;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!!(opt & zielonka_tree_options::MERGE_SUBTREES))
|
||||||
|
{
|
||||||
|
nodes_[node].first_child = fc;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
unsigned child = fc;
|
||||||
|
unsigned first = nodes_.size();
|
||||||
|
nodes_[node].first_child = first;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto& c = nodes_[child];
|
||||||
|
child = c.next_sibling;
|
||||||
|
nodes_.push_back({node, static_cast<unsigned>(nodes_.size() + 1),
|
||||||
|
0, nextlvl, c.colors});
|
||||||
|
}
|
||||||
|
while (child != fc);
|
||||||
|
nodes_.back().next_sibling = first;
|
||||||
|
// We do not have to test the shape since this is the second time
|
||||||
|
// we see these colors;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
bool is_accepting = code.accepting(colors);
|
bool is_accepting = code.accepting(colors);
|
||||||
if (node == 0)
|
if (node == 0)
|
||||||
is_even_ = is_accepting;
|
is_even_ = is_accepting;
|
||||||
|
|
@ -145,15 +182,32 @@ namespace spot
|
||||||
nodes_.reserve(first + num_children);
|
nodes_.reserve(first + num_children);
|
||||||
for (auto& m: models)
|
for (auto& m: models)
|
||||||
nodes_.push_back({node, static_cast<unsigned>(nodes_.size() + 1),
|
nodes_.push_back({node, static_cast<unsigned>(nodes_.size() + 1),
|
||||||
0, nodes_[node].level + 1, m.model});
|
0, nextlvl, m.model});
|
||||||
nodes_.back().next_sibling = first;
|
nodes_.back().next_sibling = first;
|
||||||
|
|
||||||
if (num_children > 1)
|
if (num_children > 1)
|
||||||
{
|
{
|
||||||
|
bool abort = false;
|
||||||
if (is_accepting)
|
if (is_accepting)
|
||||||
has_rabin_shape_ = false;
|
{
|
||||||
|
has_rabin_shape_ = false;
|
||||||
|
if (!!(opt & zielonka_tree_options::ABORT_WRONG_SHAPE)
|
||||||
|
&& !!(opt & zielonka_tree_options::CHECK_RABIN))
|
||||||
|
abort = true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
has_streett_shape_ = false;
|
{
|
||||||
|
has_streett_shape_ = false;
|
||||||
|
if (!!(opt & zielonka_tree_options::ABORT_WRONG_SHAPE)
|
||||||
|
&& !!(opt & zielonka_tree_options::CHECK_STREETT))
|
||||||
|
abort = true;
|
||||||
|
}
|
||||||
|
if (abort)
|
||||||
|
{
|
||||||
|
nodes_.clear();
|
||||||
|
num_branches_ = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,14 +577,18 @@ namespace spot
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
auto& c = nodes_[child];
|
auto& c = nodes_[child];
|
||||||
|
// We have to read anything we need from C
|
||||||
|
// before emplace_back, which may reallocate.
|
||||||
|
acc_cond::mark_t colors = c.colors;
|
||||||
|
unsigned minstate = c.minstate;
|
||||||
|
child = c.next_sibling;
|
||||||
nodes_.emplace_back(c.edges, c.states);
|
nodes_.emplace_back(c.edges, c.states);
|
||||||
auto& n = nodes_.back();
|
auto& n = nodes_.back();
|
||||||
n.parent = node;
|
n.parent = node;
|
||||||
n.level = lvl + 1;
|
n.level = lvl + 1;
|
||||||
n.scc = ref.scc;
|
n.scc = ref.scc;
|
||||||
n.colors = c.colors;
|
n.colors = colors;
|
||||||
n.minstate = c.minstate;
|
n.minstate = minstate;
|
||||||
child = c.next_sibling;
|
|
||||||
}
|
}
|
||||||
while (child != fc);
|
while (child != fc);
|
||||||
chain_children(node, before, nodes_.size());
|
chain_children(node, before, nodes_.size());
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// -*- coding: utf-8 -*-
|
// -*- coding: utf-8 -*-
|
||||||
// Copyright (C) 2021 Laboratoire de Recherche et Developpement de
|
// Copyright (C) 2021, 2022 Laboratoire de Recherche et Developpement de
|
||||||
// l'Epita (LRDE).
|
// l'Epita (LRDE).
|
||||||
//
|
//
|
||||||
// This file is part of Spot, a model checking library.
|
// This file is part of Spot, a model checking library.
|
||||||
|
|
@ -28,6 +28,68 @@
|
||||||
|
|
||||||
namespace spot
|
namespace spot
|
||||||
{
|
{
|
||||||
|
/// \ingroup twa_acc_transform
|
||||||
|
/// \brief Options to alter the behavior of acd
|
||||||
|
enum class zielonka_tree_options
|
||||||
|
{
|
||||||
|
/// Build the ZlkTree, without checking its shape.
|
||||||
|
NONE = 0,
|
||||||
|
/// Check if the ZlkTree has Rabin shape.
|
||||||
|
/// This actually has no effect unless ABORT_WRONG_SHAPE is set,
|
||||||
|
/// because zielonka_tree always check the shape.
|
||||||
|
CHECK_RABIN = 1,
|
||||||
|
/// Check if the ZlkTree has Streett shape.
|
||||||
|
/// This actually has no effect unless ABORT_WRONG_SHAPE is set,
|
||||||
|
/// because zielonka_tree always check the shape.
|
||||||
|
CHECK_STREETT = 2,
|
||||||
|
/// Check if the ZlkTree has Parity shape
|
||||||
|
/// This actually has no effect unless ABORT_WRONG_SHAPE is set,
|
||||||
|
/// because zielonka_tree always check the shape.
|
||||||
|
CHECK_PARITY = CHECK_RABIN | CHECK_STREETT,
|
||||||
|
/// Abort the construction of the ZlkTree if it does not have the
|
||||||
|
/// shape that is tested. When that happens, num_branches() is set
|
||||||
|
/// to 0.
|
||||||
|
ABORT_WRONG_SHAPE = 4,
|
||||||
|
/// Fuse identical substree. This cannot be used with
|
||||||
|
/// zielonka_tree_transform(). However it saves memory if the
|
||||||
|
/// only use of the zielonka_tree to check the shape.
|
||||||
|
MERGE_SUBTREES = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef SWIG
|
||||||
|
inline
|
||||||
|
bool operator!(zielonka_tree_options me)
|
||||||
|
{
|
||||||
|
return me == zielonka_tree_options::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
zielonka_tree_options operator&(zielonka_tree_options left,
|
||||||
|
zielonka_tree_options right)
|
||||||
|
{
|
||||||
|
typedef std::underlying_type_t<zielonka_tree_options> ut;
|
||||||
|
return static_cast<zielonka_tree_options>(static_cast<ut>(left)
|
||||||
|
& static_cast<ut>(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
zielonka_tree_options operator|(zielonka_tree_options left,
|
||||||
|
zielonka_tree_options right)
|
||||||
|
{
|
||||||
|
typedef std::underlying_type_t<zielonka_tree_options> ut;
|
||||||
|
return static_cast<zielonka_tree_options>(static_cast<ut>(left)
|
||||||
|
| static_cast<ut>(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
zielonka_tree_options operator-(zielonka_tree_options left,
|
||||||
|
zielonka_tree_options right)
|
||||||
|
{
|
||||||
|
typedef std::underlying_type_t<zielonka_tree_options> ut;
|
||||||
|
return static_cast<zielonka_tree_options>(static_cast<ut>(left)
|
||||||
|
& ~static_cast<ut>(right));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
/// \ingroup twa_acc_transform
|
/// \ingroup twa_acc_transform
|
||||||
/// \brief Zielonka Tree implementation
|
/// \brief Zielonka Tree implementation
|
||||||
///
|
///
|
||||||
|
|
@ -41,7 +103,8 @@ namespace spot
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// \brief Build a Zielonka tree from the acceptance condition.
|
/// \brief Build a Zielonka tree from the acceptance condition.
|
||||||
zielonka_tree(const acc_cond& cond);
|
zielonka_tree(const acc_cond& cond,
|
||||||
|
zielonka_tree_options opt = zielonka_tree_options::NONE);
|
||||||
|
|
||||||
/// \brief The number of branches in the Zielonka tree.
|
/// \brief The number of branches in the Zielonka tree.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -152,3 +152,12 @@ tc.assertTrue(a.equivalent_to(b))
|
||||||
b = spot.acd_transform_sbacc(a, False)
|
b = spot.acd_transform_sbacc(a, False)
|
||||||
tc.assertEqual(str(b.acc()), '(2, Fin(0) & Inf(1))')
|
tc.assertEqual(str(b.acc()), '(2, Fin(0) & Inf(1))')
|
||||||
tc.assertTrue(a.equivalent_to(b))
|
tc.assertTrue(a.equivalent_to(b))
|
||||||
|
|
||||||
|
|
||||||
|
# This used to be very slow.
|
||||||
|
c = spot.acc_cond("Rabin 9")
|
||||||
|
n = spot.zielonka_tree(c).num_branches()
|
||||||
|
tc.assertEqual(n, 362880)
|
||||||
|
opt = spot.zielonka_tree_options_MERGE_SUBTREES;
|
||||||
|
n = spot.zielonka_tree(c, opt).num_branches()
|
||||||
|
tc.assertEqual(n, 9)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue