parity game: various improvements
Zielonka algorithm has been fixed and optimized. It also now computes the strategy for both players. * bin/ltlsynt.cc: Update calls to parity_game::solve() * spot/misc/game.cc, spot/misc/game.hh: Implement the changes
This commit is contained in:
parent
0e29d30d1b
commit
9698363ef5
3 changed files with 131 additions and 94 deletions
|
|
@ -359,16 +359,16 @@ namespace
|
||||||
{
|
{
|
||||||
case REC:
|
case REC:
|
||||||
{
|
{
|
||||||
spot::parity_game::strategy_t strategy;
|
spot::parity_game::strategy_t strategy[2];
|
||||||
spot::parity_game::region_t winning_region;
|
spot::parity_game::region_t winning_region[2];
|
||||||
std::tie(winning_region, strategy) = pg.solve();
|
pg.solve(winning_region, strategy);
|
||||||
if (winning_region.count(pg.get_init_state_number()))
|
if (winning_region[1].count(pg.get_init_state_number()))
|
||||||
{
|
{
|
||||||
std::cout << "REALIZABLE\n";
|
std::cout << "REALIZABLE\n";
|
||||||
if (!opt_real)
|
if (!opt_real)
|
||||||
{
|
{
|
||||||
auto strat_aut =
|
auto strat_aut =
|
||||||
strat_to_aut(pg, strategy, dpa, all_outputs);
|
strat_to_aut(pg, strategy[1], dpa, all_outputs);
|
||||||
|
|
||||||
// output the winning strategy
|
// output the winning strategy
|
||||||
if (opt_print_aiger)
|
if (opt_print_aiger)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,23 @@
|
||||||
namespace spot
|
namespace spot
|
||||||
{
|
{
|
||||||
|
|
||||||
|
parity_game::parity_game(const twa_graph_ptr& arena,
|
||||||
|
const std::vector<bool>& owner)
|
||||||
|
: arena_(arena)
|
||||||
|
, owner_(owner)
|
||||||
|
{
|
||||||
|
bool max, odd;
|
||||||
|
arena_->acc().is_parity(max, odd, true);
|
||||||
|
if (!(max && odd))
|
||||||
|
throw std::runtime_error("arena must have max-odd acceptance condition");
|
||||||
|
|
||||||
|
for (const auto& e : arena_->edges())
|
||||||
|
if (e.acc.max_set() == 0)
|
||||||
|
throw std::runtime_error("arena must be colorized");
|
||||||
|
|
||||||
|
assert(owner_.size() == arena_->num_states());
|
||||||
|
}
|
||||||
|
|
||||||
void parity_game::print(std::ostream& os)
|
void parity_game::print(std::ostream& os)
|
||||||
{
|
{
|
||||||
os << "parity " << num_states() - 1 << ";\n";
|
os << "parity " << num_states() - 1 << ";\n";
|
||||||
|
|
@ -54,14 +71,13 @@ void parity_game::print(std::ostream& os)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<parity_game::region_t, parity_game::strategy_t>
|
void parity_game::solve(region_t (&w)[2], strategy_t (&s)[2]) const
|
||||||
parity_game::solve() const
|
|
||||||
{
|
{
|
||||||
region_t states_;
|
region_t states_;
|
||||||
for (unsigned i = 0; i < num_states(); ++i)
|
for (unsigned i = 0; i < num_states(); ++i)
|
||||||
states_.insert(i);
|
states_.insert(i);
|
||||||
unsigned m = max_parity();
|
unsigned m = max_parity();
|
||||||
return solve_rec(states_, m);
|
solve_rec(states_, m, w, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parity_game::solve_qp() const
|
bool parity_game::solve_qp() const
|
||||||
|
|
@ -71,110 +87,138 @@ bool parity_game::solve_qp() const
|
||||||
|
|
||||||
parity_game::strategy_t
|
parity_game::strategy_t
|
||||||
parity_game::attractor(const region_t& subgame, region_t& set,
|
parity_game::attractor(const region_t& subgame, region_t& set,
|
||||||
unsigned max_parity, bool odd, bool attr_max) const
|
unsigned max_parity, int p, bool attr_max) const
|
||||||
{
|
{
|
||||||
strategy_t strategy;
|
strategy_t strategy;
|
||||||
unsigned size;
|
|
||||||
std::unordered_set<unsigned> complement = subgame;
|
std::unordered_set<unsigned> complement = subgame;
|
||||||
std::unordered_set<unsigned> delta = set;
|
for (unsigned s: set)
|
||||||
|
complement.erase(s);
|
||||||
|
|
||||||
|
acc_cond::mark_t max_acc({});
|
||||||
|
for (unsigned i = 0; i <= max_parity; ++i)
|
||||||
|
max_acc.set(i);
|
||||||
|
|
||||||
|
bool once_more;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
size = set.size();
|
once_more = false;
|
||||||
for (unsigned s: delta)
|
for (auto it = complement.begin(); it != complement.end();)
|
||||||
complement.erase(s);
|
|
||||||
for (unsigned s: complement)
|
|
||||||
{
|
{
|
||||||
bool any = false;
|
unsigned s = *it;
|
||||||
bool all = true;
|
|
||||||
unsigned i = 0;
|
unsigned i = 0;
|
||||||
for (auto& e: out(s))
|
|
||||||
|
bool is_owned = owner_[s] == p;
|
||||||
|
bool wins = !is_owned;
|
||||||
|
|
||||||
|
for (const auto& e: out(s))
|
||||||
{
|
{
|
||||||
if (e.acc.max_set() - 1 <= max_parity && subgame.count(e.dst))
|
if ((e.acc & max_acc) && subgame.count(e.dst))
|
||||||
{
|
{
|
||||||
if (set.count(e.dst)
|
if (set.count(e.dst)
|
||||||
|| (attr_max && e.acc.max_set() - 1 == max_parity))
|
|| (attr_max && e.acc.max_set() - 1 == max_parity))
|
||||||
{
|
{
|
||||||
if (!any && owner_[s] && odd)
|
if (is_owned)
|
||||||
|
{
|
||||||
strategy[s] = i;
|
strategy[s] = i;
|
||||||
any = true;
|
wins = true;
|
||||||
|
break; // no need to check all edges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
all = false;
|
{
|
||||||
|
if (!is_owned)
|
||||||
|
{
|
||||||
|
wins = false;
|
||||||
|
break; // no need to check all edges
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
bool owner_is_odd = !!owner_[s] == odd;
|
|
||||||
if ((owner_is_odd && any) || (!owner_is_odd && all))
|
if (wins)
|
||||||
{
|
{
|
||||||
set.insert(s);
|
// FIXME C++17 extract/insert could be useful here
|
||||||
delta.insert(s);
|
set.emplace(s);
|
||||||
|
it = complement.erase(it);
|
||||||
|
once_more = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
}
|
}
|
||||||
} while (set.size() != size);
|
} while (once_more);
|
||||||
return strategy;
|
return strategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto parity_game::solve_rec(region_t& subgame, unsigned max_parity) const
|
void parity_game::solve_rec(region_t& subgame, unsigned max_parity,
|
||||||
-> std::pair<region_t, strategy_t>
|
region_t (&w)[2], strategy_t (&s)[2]) const
|
||||||
{
|
{
|
||||||
|
assert(w[0].empty());
|
||||||
|
assert(w[1].empty());
|
||||||
|
assert(s[0].empty());
|
||||||
|
assert(s[1].empty());
|
||||||
// The algorithm works recursively on subgames. To avoid useless copies of
|
// 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
|
// the game at each call, subgame and max_parity are used to filter states
|
||||||
// and transitions.
|
// and transitions.
|
||||||
if (max_parity == 0 || subgame.empty())
|
if (subgame.empty())
|
||||||
return {};
|
return;
|
||||||
bool odd = max_parity % 2 == 1;
|
int p = max_parity % 2;
|
||||||
region_t w1;
|
|
||||||
strategy_t strategy;
|
|
||||||
|
|
||||||
// Recursion on max_parity.
|
// Recursion on max_parity.
|
||||||
region_t u;
|
region_t u;
|
||||||
auto strat_u = attractor(subgame, u, max_parity, odd, true);
|
auto strat_u = attractor(subgame, u, max_parity, p, true);
|
||||||
|
|
||||||
|
if (max_parity == 0)
|
||||||
|
{
|
||||||
|
s[p] = std::move(strat_u);
|
||||||
|
w[p] = std::move(u);
|
||||||
|
// FIXME what about w[!p]?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned s: u)
|
for (unsigned s: u)
|
||||||
subgame.erase(s);
|
subgame.erase(s);
|
||||||
region_t w00; // Even's winning region in the first recursive call.
|
region_t w0[2]; // Player's winning region in the first recursive call.
|
||||||
region_t w10; // Odd's winning region in the first recursive call.
|
strategy_t s0[2]; // Player's winning strategy in the first recursive call.
|
||||||
strategy_t s10; // Odd's winning strategy in the first recursive call.
|
solve_rec(subgame, max_parity - 1, w0, s0);
|
||||||
std::tie(w10, s10) = solve_rec(subgame, max_parity - 1);
|
if (w0[0].size() + w0[1].size() != subgame.size())
|
||||||
if (odd && w10.size() != subgame.size())
|
throw std::runtime_error("size mismatch");
|
||||||
for (unsigned s: subgame)
|
//if (w0[p].size() != subgame.size())
|
||||||
if (w10.find(s) == w10.end())
|
// for (unsigned s: subgame)
|
||||||
w00.insert(s);
|
// if (w0[p].find(s) == w0[p].end())
|
||||||
// If !odd, w00 is not used, no need to compute it.
|
// w0[!p].insert(s);
|
||||||
subgame.insert(u.begin(), u.end());
|
subgame.insert(u.begin(), u.end());
|
||||||
|
|
||||||
if (odd && w10.size() + u.size() == subgame.size())
|
if (w0[p].size() + u.size() == subgame.size())
|
||||||
{
|
{
|
||||||
strategy.insert(s10.begin(), s10.end());
|
s[p] = std::move(strat_u);
|
||||||
strategy.insert(strat_u.begin(), strat_u.end());
|
s[p].insert(s0[p].begin(), s0[p].end());
|
||||||
w1.insert(subgame.begin(), subgame.end());
|
w[p].insert(subgame.begin(), subgame.end());
|
||||||
return {w1, strategy};
|
return;
|
||||||
}
|
}
|
||||||
else if (!odd && w10.empty())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
// Recursion on game size.
|
// Recursion on game size.
|
||||||
auto& wni = odd ? w00 : w10;
|
auto strat_wnp = attractor(subgame, w0[!p], max_parity, !p);
|
||||||
auto strat_wni = attractor(subgame, wni, max_parity, !odd);
|
|
||||||
if (!odd)
|
|
||||||
strat_wni.insert(s10.begin(), s10.end());
|
|
||||||
|
|
||||||
for (unsigned s: wni)
|
for (unsigned s: w0[!p])
|
||||||
subgame.erase(s);
|
subgame.erase(s);
|
||||||
|
|
||||||
region_t w11; // Odd's winning region in the second recursive call.
|
region_t w1[2]; // Odd's winning region in the second recursive call.
|
||||||
strategy_t s11; // Odd's winning strategy in the second recursive call.
|
strategy_t s1[2]; // Odd's winning strategy in the second recursive call.
|
||||||
std::tie(w11, s11) = solve_rec(subgame, max_parity);
|
solve_rec(subgame, max_parity, w1, s1);
|
||||||
|
if (w1[0].size() + w1[1].size() != subgame.size())
|
||||||
|
throw std::runtime_error("size mismatch");
|
||||||
|
|
||||||
w1.insert(w11.begin(), w11.end());
|
w[p] = std::move(w1[p]);
|
||||||
strategy.insert(s11.begin(), s11.end());
|
s[p] = std::move(s1[p]);
|
||||||
if (!odd)
|
|
||||||
{
|
w[!p] = std::move(w1[!p]);
|
||||||
strategy.insert(strat_wni.begin(), strat_wni.end());
|
w[!p].insert(w0[!p].begin(), w0[!p].end());
|
||||||
w1.insert(wni.begin(), wni.end());
|
s[!p] = std::move(strat_wnp);
|
||||||
}
|
s[!p].insert(s0[!p].begin(), s0[!p].end());
|
||||||
subgame.insert(wni.begin(), wni.end());
|
s[!p].insert(s1[!p].begin(), s1[!p].end());
|
||||||
return {w1, strategy};
|
|
||||||
|
subgame.insert(w0[!p].begin(), w0[!p].end());
|
||||||
}
|
}
|
||||||
|
|
||||||
int reachability_state::compare(const state* other) const
|
int reachability_state::compare(const state* other) const
|
||||||
|
|
|
||||||
|
|
@ -35,47 +35,40 @@ namespace spot
|
||||||
class SPOT_API parity_game
|
class SPOT_API parity_game
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
const const_twa_graph_ptr dpa_;
|
const const_twa_graph_ptr arena_;
|
||||||
const std::vector<bool> owner_;
|
const std::vector<bool> owner_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// \a parity_game provides an interface to manipulate a deterministic parity
|
/// \a parity_game provides an interface to manipulate a colorized parity
|
||||||
/// automaton as a parity game, including methods to solve the game.
|
/// automaton as a parity game, including methods to solve the game.
|
||||||
|
/// The input automaton (arena) should be colorized and have a max-odd parity
|
||||||
|
/// acceptance condition.
|
||||||
///
|
///
|
||||||
/// \param dpa the underlying deterministic parity automaton
|
/// \param arena the underlying parity automaton
|
||||||
/// \param owner a vector of Booleans indicating the owner of each state,
|
/// \param owner a vector of Booleans indicating the owner of each state:
|
||||||
/// with the convention that true represents player 1 and false represents
|
/// true stands for Player 1, false stands for Player 0.
|
||||||
/// player 0.
|
parity_game(const twa_graph_ptr& arena, const std::vector<bool>& owner);
|
||||||
parity_game(const twa_graph_ptr dpa, std::vector<bool> owner)
|
|
||||||
: dpa_(dpa), owner_(owner)
|
|
||||||
{
|
|
||||||
bool max;
|
|
||||||
bool odd;
|
|
||||||
dpa_->acc().is_parity(max, odd, true);
|
|
||||||
SPOT_ASSERT(max && odd);
|
|
||||||
SPOT_ASSERT(owner_.size() == dpa_->num_states());
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned num_states() const
|
unsigned num_states() const
|
||||||
{
|
{
|
||||||
return dpa_->num_states();
|
return arena_->num_states();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned get_init_state_number() const
|
unsigned get_init_state_number() const
|
||||||
{
|
{
|
||||||
return dpa_->get_init_state_number();
|
return arena_->get_init_state_number();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal::state_out<const twa_graph::graph_t>
|
internal::state_out<const twa_graph::graph_t>
|
||||||
out(unsigned src) const
|
out(unsigned src) const
|
||||||
{
|
{
|
||||||
return dpa_->out(src);
|
return arena_->out(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal::state_out<const twa_graph::graph_t>
|
internal::state_out<const twa_graph::graph_t>
|
||||||
out(unsigned src)
|
out(unsigned src)
|
||||||
{
|
{
|
||||||
return dpa_->out(src);
|
return arena_->out(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool owner(unsigned src) const
|
bool owner(unsigned src) const
|
||||||
|
|
@ -86,7 +79,7 @@ public:
|
||||||
unsigned max_parity() const
|
unsigned max_parity() const
|
||||||
{
|
{
|
||||||
unsigned max_parity = 0;
|
unsigned max_parity = 0;
|
||||||
for (auto& e: dpa_->edges())
|
for (const auto& e: arena_->edges())
|
||||||
max_parity = std::max(max_parity, e.acc.max_set());
|
max_parity = std::max(max_parity, e.acc.max_set());
|
||||||
SPOT_ASSERT(max_parity);
|
SPOT_ASSERT(max_parity);
|
||||||
return max_parity - 1;
|
return max_parity - 1;
|
||||||
|
|
@ -113,7 +106,7 @@ public:
|
||||||
author = "Wieslaw Zielonka",
|
author = "Wieslaw Zielonka",
|
||||||
}
|
}
|
||||||
\endverbatim */
|
\endverbatim */
|
||||||
std::pair<region_t, strategy_t> solve() const;
|
void solve(region_t (&w)[2], strategy_t (&s)[2]) const;
|
||||||
|
|
||||||
/// Whether player 1 has a winning strategy from the initial state.
|
/// Whether player 1 has a winning strategy from the initial state.
|
||||||
/// Implements Calude et al.'s quasipolynomial time algorithm.
|
/// Implements Calude et al.'s quasipolynomial time algorithm.
|
||||||
|
|
@ -148,12 +141,12 @@ private:
|
||||||
// if attr_max is true, states that can force a visit through an edge with
|
// if attr_max is true, states that can force a visit through an edge with
|
||||||
// max parity are also counted in.
|
// max parity are also counted in.
|
||||||
strategy_t attractor(const region_t& subgame, region_t& set,
|
strategy_t attractor(const region_t& subgame, region_t& set,
|
||||||
unsigned max_parity, bool odd,
|
unsigned max_parity, int odd,
|
||||||
bool attr_max = false) const;
|
bool attr_max = false) const;
|
||||||
|
|
||||||
// Compute the winning strategy and winning region for player 1.
|
// Compute the winning strategy and winning region for both players.
|
||||||
std::pair<region_t, strategy_t>
|
void solve_rec(region_t& subgame, unsigned max_parity,
|
||||||
solve_rec(region_t& subgame, unsigned max_parity) const;
|
region_t (&w)[2], strategy_t (&s)[2]) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue