introduce a zielonka_tree class

* spot/twaalgos/zlktree.cc, spot/twaalgos/zlktree.hh: New files.
* spot/twaalgos/Makefile.am: Add them.
* tests/python/_zlktree.ipynb: New file.
* tests/Makefile.am: Add it.
* python/spot/__init__.py, python/spot/impl.i: Add bindings for it.
* doc/spot.bib (casares.21.icalp): New entry.
* NEWS: Mention this.
This commit is contained in:
Alexandre Duret-Lutz 2021-07-29 17:41:04 +02:00
parent 803f647dde
commit af511707c0
9 changed files with 1948 additions and 10 deletions

5
NEWS
View file

@ -231,6 +231,11 @@ New in spot 2.9.7.dev (not yet released)
Additionally, this function now returns the number of states that Additionally, this function now returns the number of states that
have been merged (and therefore removed from the automaton). have been merged (and therefore removed from the automaton).
- spot::zielonka_tree is a new class that can be constructed from
any acceptance condition to help paritizing it. This is based on
a paper by Casares et al. (ICALP'21). Its python binding will
display the tree graphically.
Python: Python:
- Bindings for functions related to games. - Bindings for functions related to games.

View file

@ -1,4 +1,3 @@
@InProceedings{ babiak.12.tacas, @InProceedings{ babiak.12.tacas,
author = {Tom{\'a}{\v{s}} Babiak and Mojm{\'i}r author = {Tom{\'a}{\v{s}} Babiak and Mojm{\'i}r
K{\v{r}}et{\'i}nsk{\'y} and Vojt{\v{e}}ch {\v{R}}eh{\'a}k K{\v{r}}et{\'i}nsk{\'y} and Vojt{\v{e}}ch {\v{R}}eh{\'a}k
@ -114,6 +113,24 @@
url = {http://www.numdam.org/item/ITA_1999__33_6_495_0/} url = {http://www.numdam.org/item/ITA_1999__33_6_495_0/}
} }
@InProceedings{ casares.21.icalp,
author = {Antonio Casares and Thomas Colcombet and Nathana\"{e}l
Fijalkow},
title = {Optimal Transformations of Games and Automata Using
{M}uller Conditions},
booktitle = {Proceedings of the 48th International Colloquium on
Automata, Languages, and Programming (ICALP'21)},
pages = {123:1--123:14},
series = {Leibniz International Proceedings in Informatics
(LIPIcs)},
year = {2021},
volume = {198},
editor = {Bansal, Nikhil and Merelli, Emanuela and Worrell, James},
publisher = {Schloss Dagstuhl -- Leibniz-Zentrum f{\"u}r Informatik},
address = {Dagstuhl, Germany},
doi = {10.4230/LIPIcs.ICALP.2021.123}
}
@InProceedings{ cerna.03.mfcs, @InProceedings{ cerna.03.mfcs,
author = {Ivana {\v{C}}ern{\'a} and Radek Pel{\'a}nek}, author = {Ivana {\v{C}}ern{\'a} and Radek Pel{\'a}nek},
title = {Relating Hierarchy of Temporal Properties to Model title = {Relating Hierarchy of Temporal Properties to Model
@ -169,13 +186,13 @@
doi = {10.1109/TCAD.2008.2003303} doi = {10.1109/TCAD.2008.2003303}
} }
@article{ clemente.2.17.corr, @Article{ clemente.2.17.corr,
author = {Lorenzo Clemente and Richard Mayr}, author = {Lorenzo Clemente and Richard Mayr},
title = {Efficient reduction of nondeterministic automata with application title = {Efficient reduction of nondeterministic automata with
to language inclusion testing}, application to language inclusion testing},
journal = {CoRR}, journal = {CoRR},
volume = {abs/1711.09946}, volume = {abs/1711.09946},
year = {2017}, year = {2017}
} }
@Article{ courcoubetis.92.fmsd, @Article{ courcoubetis.92.fmsd,

View file

@ -428,6 +428,15 @@ class atomic_prop_set:
return res return res
@_extend(zielonka_tree)
class zielonka_tree:
def _repr_svg_(self):
"""Output the Zielonka tree as SVG"""
ostr = ostringstream()
self.dot(ostr)
return _ostream_to_svg(ostr)
def automata(*sources, timeout=None, ignore_abort=True, def automata(*sources, timeout=None, ignore_abort=True,
trust_hoa=True, no_sid=False, debug=False, trust_hoa=True, no_sid=False, debug=False,
want_kripke=False): want_kripke=False):

View file

@ -164,6 +164,7 @@
#include <spot/twaalgos/word.hh> #include <spot/twaalgos/word.hh>
#include <spot/twaalgos/are_isomorphic.hh> #include <spot/twaalgos/are_isomorphic.hh>
#include <spot/twaalgos/toparity.hh> #include <spot/twaalgos/toparity.hh>
#include <spot/twaalgos/zlktree.hh>
#include <spot/parseaut/public.hh> #include <spot/parseaut/public.hh>
@ -690,6 +691,7 @@ def state_is_accepting(self, src) -> "bool":
%template(list_bdd) std::list<bdd>; %template(list_bdd) std::list<bdd>;
%include <spot/twaalgos/are_isomorphic.hh> %include <spot/twaalgos/are_isomorphic.hh>
%include <spot/twaalgos/toparity.hh> %include <spot/twaalgos/toparity.hh>
%include <spot/twaalgos/zlktree.hh>
%pythonprepend spot::twa::dtwa_complement %{ %pythonprepend spot::twa::dtwa_complement %{
from warnings import warn from warnings import warn

View file

@ -98,7 +98,8 @@ twaalgos_HEADERS = \
totgba.hh \ totgba.hh \
toweak.hh \ toweak.hh \
translate.hh \ translate.hh \
word.hh word.hh \
zlktree.hh
noinst_LTLIBRARIES = libtwaalgos.la noinst_LTLIBRARIES = libtwaalgos.la
libtwaalgos_la_SOURCES = \ libtwaalgos_la_SOURCES = \
@ -170,6 +171,7 @@ libtwaalgos_la_SOURCES = \
totgba.cc \ totgba.cc \
toweak.cc \ toweak.cc \
translate.cc \ translate.cc \
word.cc word.cc \
zlktree.cc
libtwaalgos_la_LIBADD = gtec/libgtec.la libtwaalgos_la_LIBADD = gtec/libgtec.la

228
spot/twaalgos/zlktree.cc Normal file
View file

@ -0,0 +1,228 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
#include "config.h"
#include <iostream>
#include <spot/twaalgos/zlktree.hh>
#include "spot/priv/bddalloc.hh"
namespace spot
{
zielonka_tree::zielonka_tree(acc_cond& cond)
{
acc_cond::acc_code& code = cond.get_acceptance();
auto used = code.used_sets();
unsigned c = used.count();
unsigned max = used.max_set();
bdd_allocator ba;
int base = ba.allocate_variables(c);
assert(base == 0);
std::vector<bdd> col_to_bdd(max ? max : 1, bddfalse);
std::vector<unsigned> bdd_to_col(c);
bdd all_neg = bddtrue;
for (unsigned i = 0; i < max; ++i)
if (used.has(i))
{
bdd_to_col[base] = i;
all_neg &= bdd_nithvar(base);
col_to_bdd[i] = bdd_ithvar(base++);
}
bdd poscond = code.to_bdd(col_to_bdd.data());
bdd negcond = !poscond;
nodes_.emplace_back();
nodes_[0].parent = 0;
nodes_[0].colors = used;
nodes_[0].level = 0;
// Or goal is the find the list of maximal models for a formula named
// cond and defined later for each node.
//
// For instance if cond is satisfied by {1}, {3}, {1,2}, {1,2,3},
// {0,3}, and {0,1,3}, then the maximal models are {1,2,3} and
// {0,1,3}.
//
// To do that we build a list of models ordered by decreasing
// size. When inserting a model, we will compare it to all
// model of larger size first, and abort the insertion if
// needed. Otherwise we insert it, and then compare it to
// smaller models to possibly remove them.
//
// "models" is the variable where we store those ordered models.
// This list is local to each node, but we reuse the vector
// between each iteration to avoid unnecessary allocations.
struct size_model
{
unsigned size;
acc_cond::mark_t model;
};
std::vector<size_model> models;
// This loop is a BFS over the increasing set of nodes.
for (unsigned node = 0; node < nodes_.size(); ++node)
{
acc_cond::mark_t colors = nodes_[node].colors;
bool is_accepting = code.accepting(colors);
if (node == 0)
is_even_ = is_accepting;
// colors that do not appear in this node should
// be set to false in the acceptance.
bdd to_remove = bddtrue;
for (unsigned c: (colors ^ used).sets())
to_remove &= !col_to_bdd[c];
bdd cond = bdd_restrict(is_accepting ? negcond : poscond, to_remove);
// These is where we will store the ordered list of models, as
// explained in the declation of that vector.
models.clear();
while (cond != bddfalse)
{
// Find one model of cond. If it has some don't cares
// variable, we interpret them as true, so in effect, we can
// start from a model where all colors are sets, and only
// unset those that are negative in the output of
// bdd_satone.
bdd one = bdd_satone(cond);
cond -= one;
acc_cond::mark_t curmodel = colors;
while (one != bddtrue)
{
unsigned v = bdd_to_col[bdd_var(one)];
if (bdd_high(one) == bddfalse)
{
curmodel.clear(v);
one = bdd_low(one);
}
else
{
one = bdd_high(one);
}
}
//
unsigned sz = curmodel.count();
if (sz == 0)
// ignore the empty set
continue;
auto iter = models.begin();
while (iter != models.end())
{
if (iter->size < sz)
// We have checked all larger models.
break;
if (curmodel.subset(iter->model))
// curmodel is covered by iter->model.
goto donotinsert;
++iter;
}
// insert the model
iter = models.insert(iter, {sz, curmodel});
++iter;
// erase all models it contains
models.erase(std::remove_if(iter, models.end(),
[&](auto& mod) {
return mod.model.subset(curmodel);
}), models.end());
donotinsert:;
}
if (models.empty()) // This is a leaf of the tree.
{
if (num_branches_++ == 0)
one_branch_ = node;
continue;
}
unsigned first = nodes_.size();
nodes_[node].first_child = first;
unsigned num_children = models.size();
nodes_.reserve(first + num_children);
for (auto& m: models)
nodes_.push_back({node, static_cast<unsigned>(nodes_.size() + 1),
0, nodes_[node].level + 1, m.model});
nodes_.back().next_sibling = first;
if (num_children > 1)
{
if (is_accepting)
has_rabin_shape_ = false;
else
has_streett_shape_ = false;
}
};
}
void zielonka_tree::dot(std::ostream& os) const
{
os << "digraph zielonka_tree {\n";
unsigned ns = nodes_.size();
for (unsigned n = 0; n < ns; ++n)
{
os << " " << n << " [label=\"" << nodes_[n].colors;
unsigned first_child = nodes_[n].first_child;
if (!first_child)
os << "\n<" << n << '>';
os << "\", shape="
<< (((nodes_[n].level & 1) ^ is_even_) ? "ellipse" : "box")
<< "]\n";
if (first_child)
{
unsigned child = first_child;
do
{
os << " " << n << " -> " << child << '\n';
child = nodes_[child].next_sibling;
}
while (child != first_child);
}
}
os << "}\n";
}
std::pair<unsigned, unsigned>
zielonka_tree::step(unsigned branch, acc_cond::mark_t colors) const
{
if (SPOT_UNLIKELY(nodes_.size() < branch || nodes_[branch].first_child))
throw std::runtime_error
("zielonka_tree::next_branch(): incorrect branch number");
if (!colors)
return {branch, nodes_[branch].level + 1};
unsigned child = 0;
for (;;)
{
colors -= nodes_[branch].colors;
if (!colors)
break;
child = branch;
branch = nodes_[branch].parent;
}
unsigned lvl = nodes_[branch].level;
if (child != 0)
{
// The following computation could be precomputed.
branch = nodes_[child].next_sibling;
while (nodes_[branch].first_child)
branch = nodes_[branch].first_child;
}
return {branch, lvl};
}
}

132
spot/twaalgos/zlktree.hh Normal file
View file

@ -0,0 +1,132 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
#pragma once
#include <iosfwd>
#include <spot/twa/twagraph.hh>
namespace spot
{
/// \ingroup twa_acc_transform
/// \brief Zielonka Tree implementation
///
/// This class implements a Zielonka Tree, using
/// conventions similar to those in \cite casares.21.icalp
///
/// The differences is that this tree is built from Emerson-Lei
/// acceptance conditions, and can be "walked through" with multiple
/// colors at once.
class SPOT_API zielonka_tree
{
public:
/// \brief Build a Zielonka tree from the acceptance condition.
zielonka_tree(acc_cond& cond);
/// \brief The number of branches in the Zielonka tree.
///
/// Branch are designated by the node number of their
/// leaves.
unsigned num_branches() const
{
return num_branches_;
}
/// \brief The number of one branch in the tree.
///
/// This returns the branch whose leave is the smallest one.
unsigned first_branch() const
{
return one_branch_;
}
/// \brief Walk through the Zielonka tree.
///
/// Given a \a branch number, and a set of \a colors, this returns
/// a pair (new branch, level), as needed in definition 3.7 of
/// \cite casares.21.icalp
///
/// The level correspond to the priority of a minimum parity acceptance
/// condition, with the parity odd/even as specified by is_even().
///
/// This implementation is slightly different from the original
/// definition since it allows a set of colors to be processed,
/// and not exactly one color. When multiple colors are given,
/// the minimum corresponding level is returned. When no color is
/// given, the branch is not changed and the level returned is one
/// more than the depth of that branch (this is as if the tree add
/// another layer of leaves labeled by the empty sets, that do not
/// store for simplicity).
std::pair<unsigned, unsigned>
step(unsigned branch, acc_cond::mark_t colors) const;
/// \brief Whether the tree corresponds to a min even or min odd
/// parity acceptance.
bool is_even() const
{
return is_even_;
}
/// \brief Whether the Zielonka tree has Rabin shape.
///
/// The tree has Rabin shape of all accepting (round) nodes have
/// at most one child.
bool has_rabin_shape() const
{
return has_rabin_shape_;
}
/// \brief Whether the Zielonka tree has Streett shape.
///
/// The tree has Streett shape of all rejecting (square) nodes have
/// at most one child.
bool has_streett_shape() const
{
return has_streett_shape_;
}
/// \brief Whether the Zielonka tree has parity shape.
///
/// The tree has parity shape of all nodes have at most one child.
bool has_parity_shape() const
{
return has_streett_shape_ && has_rabin_shape_;
}
/// \brief Render the tree as in GraphViz format.
void dot(std::ostream&) const;
private:
struct zielonka_node
{
unsigned parent;
unsigned next_sibling = 0;
unsigned first_child = 0;
unsigned level;
acc_cond::mark_t colors;
};
std::vector<zielonka_node> nodes_;
unsigned one_branch_ = 0;
unsigned num_branches_ = 0;
bool is_even_;
bool has_rabin_shape_ = true;
bool has_streett_shape_ = true;
};
}

View file

@ -449,6 +449,7 @@ TESTS_python = \
python/twagraph.py \ python/twagraph.py \
python/toweak.py \ python/toweak.py \
python/_word.ipynb \ python/_word.ipynb \
python/_zlktree.ipynb \
$(TESTS_ipython) $(TESTS_ipython)
endif endif

1542
tests/python/_zlktree.ipynb Normal file

File diff suppressed because it is too large Load diff