parity game: add Zielonka's recursive algorithm

* spot/misc/game.cc, spot/misc/game.hh: Implement it.
* bin/ltlsynt.cc: Use it.
* doc/org/ltlsynt.org: Document it.
This commit is contained in:
Thibaud Michaud 2017-09-10 22:54:24 +02:00
parent 0821c97eb8
commit f414e9f5f2
4 changed files with 213 additions and 27 deletions

View file

@ -54,11 +54,111 @@ void parity_game::print(std::ostream& os)
}
}
bool parity_game::winner() const
{
std::unordered_set<unsigned> states_;
for (unsigned i = 0; i < num_states(); ++i)
states_.insert(i);
unsigned m = max_parity();
auto w1 = winning_region(states_, m);
return w1.find(get_init_state_number()) != w1.end();
}
bool parity_game::solve_qp() const
{
return reachability_game(*this).is_reachable();
}
void parity_game::attractor(const std::unordered_set<unsigned>& subgame,
std::unordered_set<unsigned>& set,
unsigned max_parity, bool odd,
bool attr_max) const
{
unsigned size;
do
{
size = set.size();
for (unsigned s: subgame)
{
bool any = false;
bool all = true;
for (auto& e: out(s))
{
if (e.acc.max_set() - 1 <= max_parity
&& subgame.find(e.dst) != subgame.end())
{
if (set.find(e.dst) != set.end()
|| (attr_max && e.acc.max_set() - 1 == max_parity))
any = true;
else
all = false;
}
}
if ((owner_[s] == odd && any) || (owner_[s] != odd && all))
set.insert(s);
}
} while (set.size() != size);
}
std::unordered_set<unsigned>
parity_game::winning_region(std::unordered_set<unsigned>& subgame,
unsigned max_parity) const
{
// The algorithm works recursively on subgames. To avoid useless copies of
// the game at each call, subgame and max_parity are used to filter states
// and transitions.
if (max_parity == 0 || subgame.empty())
return std::unordered_set<unsigned>();
bool odd = max_parity % 2 == 1;
std::unordered_set<unsigned> w1;
std::unordered_set<unsigned> removed;
while (!subgame.empty())
{
// Recursion on max_parity.
std::unordered_set<unsigned> u;
attractor(subgame, u, max_parity, odd, true);
for (unsigned s: u)
subgame.erase(s);
auto w1_ = winning_region(subgame, max_parity - 1);
std::unordered_set<unsigned> w0_;
if (odd && w1_.size() != subgame.size())
std::set_difference(subgame.begin(), subgame.end(),
w1_.begin(), w1_.end(),
std::inserter(w0_, w0_.begin()));
// if !odd, w0_ is not used.
for (unsigned s: u)
subgame.insert(s);
if (odd && w1_.size() + u.size() == subgame.size())
{
for (unsigned s: subgame)
w1.insert(s);
break;
}
else if (!odd && w1_.empty())
break;
// Unrolled tail-recursion on game size.
auto& wni = odd ? w0_ : w1_;
attractor(subgame, wni, max_parity, !odd);
for (unsigned s: wni)
{
subgame.erase(s);
removed.insert(s);
}
if (!odd)
for (unsigned s: wni)
w1.insert(s);
}
for (unsigned s: removed)
subgame.insert(s);
return w1;
}
int reachability_state::compare(const state* other) const
{
auto o = down_cast<const reachability_state*>(other);

View file

@ -82,33 +82,74 @@ public:
return owner_[src];
}
unsigned max_parity() const
{
unsigned max_parity = 0;
for (auto& e: dpa_->edges())
max_parity = std::max(max_parity, e.acc.max_set());
SPOT_ASSERT(max_parity);
return max_parity - 1;
}
/// Print the parity game in PGSolver's format.
void print(std::ostream& os);
// Compute the winner of this game using Zielonka's recursive algorithm.
// False is Even and True is Odd.
/** \verbatim
@article{ zielonka.98.tcs
title = "Infinite games on finitely coloured graphs with applications to
automata on infinite trees",
journal = "Theoretical Computer Science",
volume = "200",
number = "1",
pages = "135 - 183",
year = "1998",
author = "Wieslaw Zielonka",
}
\endverbatim */
bool winner() const;
/// Whether player 1 has a winning strategy from the initial state.
/// Implements Calude et al.'s quasipolynomial time algorithm.
/** \verbatim
@inproceedings{calude.17.stoc,
author = {Calude, Cristian S. and Jain, Sanjay and Khoussainov,
Bakhadyr and Li, Wei and Stephan, Frank},
title = {Deciding Parity Games in Quasipolynomial Time},
booktitle = {Proceedings of the 49th Annual ACM SIGACT Symposium on
Theory of Computing},
series = {STOC 2017},
year = {2017},
isbn = {978-1-4503-4528-6},
location = {Montreal, Canada},
pages = {252--263},
numpages = {12},
url = {http://doi.acm.org/10.1145/3055399.3055409},
doi = {10.1145/3055399.3055409},
acmid = {3055409},
publisher = {ACM},
address = {New York, NY, USA},
keywords = {Muller Games, Parity Games, Quasipolynomial Time Algorithm},
author = {Calude, Cristian S. and Jain, Sanjay and Khoussainov,
Bakhadyr and Li, Wei and Stephan, Frank},
title = {Deciding Parity Games in Quasipolynomial Time},
booktitle = {Proceedings of the 49th Annual ACM SIGACT Symposium on
Theory of Computing},
series = {STOC 2017},
year = {2017},
isbn = {978-1-4503-4528-6},
location = {Montreal, Canada},
pages = {252--263},
numpages = {12},
url = {http://doi.acm.org/10.1145/3055399.3055409},
doi = {10.1145/3055399.3055409},
acmid = {3055409},
publisher = {ACM},
address = {New York, NY, USA},
keywords = {Muller Games, Parity Games, Quasipolynomial Time Algorithm},
}
\endverbatim */
bool solve_qp() const;
private:
typedef twa_graph::graph_t::edge_storage_t edge_t;
// Compute (in place) a set of states from which player can force a visit
// through set.
// if attr_max is true, states that can force a visit through an edge with
// max parity are also counted in.
void attractor(const std::unordered_set<unsigned>& subgame,
std::unordered_set<unsigned>& set, unsigned max_parity,
bool player, bool attr_max = false) const;
// Compute the winning region for player Odd.
std::unordered_set<unsigned>
winning_region(std::unordered_set<unsigned>& subgame,
unsigned max_parity) const;
};
@ -246,8 +287,7 @@ private:
public:
reachability_game(const parity_game& pg)
: twa(std::make_shared<bdd_dict>()),
pg_(pg)
: twa(std::make_shared<bdd_dict>()), pg_(pg)
{
init_state_ = std::shared_ptr<const reachability_state>(get_init_state());
}