Commit 133896d5 authored by Philipp Schlehuber's avatar Philipp Schlehuber Committed by Alexandre Duret-Lutz
Browse files

game: reimplement parity game solving

* spot/misc/game.cc, spot/misc/game.hh: More efficient implementation
of Zielonka's algorithm to solve parity games.  Now supports SCC
decomposition and efficient handling of certain special cases.
* doc/org/concepts.org: Document "strategy" and "state-winner"
properties.
* bin/ltlsynt.cc, tests/python/paritygame.ipynb: Adjust.
* tests/core/ltlsynt.test: Add more tests.
parent f6ac69d0
Pipeline #21699 passed with stages
in 311 minutes and 2 seconds
......@@ -212,51 +212,101 @@ namespace
return dpa;
}
// Construct a smaller automaton, filtering out states that are not
// accessible. Also merge back pairs of p --(i)--> q --(o)--> r
// transitions to p --(i&o)--> r.
static spot::twa_graph_ptr
strat_to_aut(const spot::const_twa_graph_ptr& pg,
const spot::strategy_t& strat,
bdd all_outputs)
spot::twa_graph_ptr
apply_strategy(const spot::twa_graph_ptr& arena,
bdd all_outputs,
bool unsplit, bool keep_acc, bool leave_choice)
{
auto aut = spot::make_twa_graph(pg->get_dict());
aut->copy_ap_of(pg);
unsigned pg_init = pg->get_init_state_number();
std::vector<unsigned> todo{pg_init};
std::vector<int> pg2aut(pg->num_states(), -1);
spot::region_t* w_ptr =
arena->get_named_prop<spot::region_t>("state-winner");
spot::strategy_t* s_ptr =
arena->get_named_prop<spot::strategy_t>("strategy");
std::vector<bool>* sp_ptr =
arena->get_named_prop<std::vector<bool>>("state-player");
if (!w_ptr || !s_ptr || !sp_ptr)
throw std::runtime_error("Arena missing state-winner, strategy "
"or state-player");
if (!(w_ptr->at(arena->get_init_state_number())))
throw std::runtime_error("Player does not win initial state, strategy "
"is not applicable");
spot::region_t& w = *w_ptr;
spot::strategy_t& s = *s_ptr;
auto aut = spot::make_twa_graph(arena->get_dict());
aut->copy_ap_of(arena);
if (keep_acc)
aut->copy_acceptance_of(arena);
const unsigned unseen_mark = std::numeric_limits<unsigned>::max();
std::vector<unsigned> todo{arena->get_init_state_number()};
std::vector<unsigned> pg2aut(arena->num_states(), unseen_mark);
aut->set_init_state(aut->new_state());
pg2aut[pg_init] = aut->get_init_state_number();
pg2aut[arena->get_init_state_number()] = aut->get_init_state_number();
bdd out;
while (!todo.empty())
{
unsigned s = todo.back();
unsigned v = todo.back();
todo.pop_back();
for (auto& e1: pg->out(s))
// Env edge -> keep all
for (auto &e1: arena->out(v))
{
unsigned i = 0;
for (auto& e2: pg->out(e1.dst))
assert(w.at(e1.dst));
if (!unsplit)
{
bool self_loop = false;
if (e1.dst == s || e2.dst == e1.dst)
self_loop = true;
if (self_loop || strat.at(e1.dst) == i)
{
bdd out = bdd_satoneset(e2.cond, all_outputs, bddfalse);
if (pg2aut[e2.dst] == -1)
{
pg2aut[e2.dst] = aut->new_state();
todo.push_back(e2.dst);
}
aut->new_edge(pg2aut[s], pg2aut[e2.dst],
(e1.cond & out), {});
break;
}
++i;
if (pg2aut[e1.dst] == unseen_mark)
pg2aut[e1.dst] = aut->new_state();
aut->new_edge(pg2aut[v], pg2aut[e1.dst], e1.cond,
keep_acc ? e1.acc : spot::acc_cond::mark_t({}));
}
// Player strat
auto &e2 = arena->edge_storage(s[e1.dst]);
if (pg2aut[e2.dst] == unseen_mark)
{
pg2aut[e2.dst] = aut->new_state();
todo.push_back(e2.dst);
}
if (leave_choice)
// Leave the choice
out = e2.cond;
else
// Save only one letter
out = bdd_satoneset(e2.cond, all_outputs, bddfalse);
aut->new_edge(unsplit ? pg2aut[v] : pg2aut[e1.dst],
pg2aut[e2.dst],
unsplit ? (e1.cond & out):out,
keep_acc ? e2.acc : spot::acc_cond::mark_t({}));
}
}
aut->purge_dead_states();
aut->set_named_prop("synthesis-outputs", new bdd(all_outputs));
// If no unsplitting is demanded, it remains a two-player arena
// We do not need to track winner as this is a
// strategy automaton
if (!unsplit)
{
std::vector<bool>& sp_pg = * sp_ptr;
std::vector<bool> sp_aut(aut->num_states());
spot::strategy_t str_aut(aut->num_states());
for (unsigned i = 0; i < arena->num_states(); ++i)
{
if (pg2aut[i] == unseen_mark)
// Does not appear in strategy
continue;
sp_aut[pg2aut[i]] = sp_pg[i];
str_aut[pg2aut[i]] = s[i];
}
aut->set_named_prop("state-player",
new std::vector<bool>(std::move(sp_aut)));
aut->set_named_prop("state-winner",
new spot::region_t(aut->num_states(), true));
aut->set_named_prop("strategy",
new spot::strategy_t(std::move(str_aut)));
}
return aut;
}
......@@ -523,22 +573,22 @@ namespace
if (want_time)
sw.start();
auto solution = parity_game_solve(dpa);
bool player1winning = solve_parity_game(dpa);
if (want_time)
solve_time = sw.stop();
if (verbose)
std::cerr << "parity game solved in " << solve_time << " seconds\n";
nb_states_parity_game = dpa->num_states();
timer.stop();
if (solution.player_winning_at(1, dpa->get_init_state_number()))
if (player1winning)
{
std::cout << "REALIZABLE\n";
if (!opt_real)
{
if (want_time)
sw.start();
auto strat_aut =
strat_to_aut(dpa, solution.winning_strategy[1], all_outputs);
auto strat_aut = apply_strategy(dpa, all_outputs,
true, false, true);
if (want_time)
strat2aut_time = sw.stop();
......
......@@ -1144,7 +1144,9 @@ Here is a list of named properties currently used inside Spot:
| ~rejected-word~ | ~std::string~ | a word rejected by the automaton |
| ~simulated-states~ | ~std::vector<unsigned>~ | map states of the original automaton to states if the current automaton in the result of simulation-based reductions |
| ~state-names~ | ~std::vector<std::string>~ | vector naming each state of the automaton, for display purpose |
| ~state-player~ | ~std::vector<bool>~ | the automaton represents a two-player game, and the vector gives the player (0 or 1) associated to each state |
| ~state-player~ | ~std::vector<bool>~ | the automaton represents a two-player game, and the vector gives the player (0 or 1) associated to each state |
| ~state-winner~ | ~std::vector<bool>~ | vector indicating the player (0 or 1) winning from this state |
| ~strategy~ | ~std::vector<unsigned>~ | vector representing the memoryless strategy of the players in a parity game. The value corrsponds to the edge number of the transition to take. |
| ~synthesis-outputs~ | ~bdd~ | conjunction of controllable atomic propositions (used by ~print_aiger()~ to determine which propositions should be encoded as outputs of the circuit) |
Objects referenced via named properties are automatically destroyed
......
This diff is collapsed.
......@@ -47,27 +47,12 @@ namespace spot
bool complete0 = true);
typedef std::unordered_set<unsigned> region_t;
typedef std::unordered_map<unsigned, unsigned> strategy_t;
// false -> env, true -> player
typedef std::vector<bool> region_t;
// state idx -> global edge number
typedef std::vector<unsigned> strategy_t;
struct SPOT_API solved_game
{
const_twa_graph_ptr arena;
region_t winning_region[2];
strategy_t winning_strategy[2];
/// \brief Highlight the edges of a strategy on the automaton.
twa_graph_ptr highlight_strategy(unsigned player, unsigned color);
bool player_winning_at(unsigned player, unsigned state)
{
auto& w = winning_region[player];
return w.find(state) != w.end();
}
};
/// \brief solve a parity-game
///
/// The arena is a deterministic max odd parity automaton with a
......@@ -76,8 +61,11 @@ namespace spot
/// This computes the winning strategy and winning region of this
/// game for player 1 using Zielonka's recursive algorithm.
/// \cite zielonka.98.tcs
///
/// Return the player winning in the initial state, and set
/// the state-winner and strategy named properties.
SPOT_API
solved_game parity_game_solve(const const_twa_graph_ptr& arena);
bool solve_parity_game(const twa_graph_ptr& arena);
/// \brief Print a max odd parity game using PG-solver syntax
SPOT_API
......@@ -85,10 +73,12 @@ namespace spot
/// \brief Highlight the edges of a strategy on an automaton.
///
/// Pass a negative color to not display the corresponding strategy.
SPOT_API
twa_graph_ptr highlight_strategy(twa_graph_ptr& arena,
const strategy_t& s,
unsigned color);
int player0_color = 5,
int player1_color = 4);
/// \brief Set the owner for all the states.
SPOT_API
......
......@@ -23,6 +23,27 @@
set -e
cat >exp <<EOF
parity 19;
0 1 0 8,9 "INIT";
9 3 1 1,2;
2 1 0 12,13;
13 5 1 1,4;
4 1 0 8,15;
15 3 1 5,6;
6 1 0 8,15;
8 3 1 2;
5 1 0 16,17;
17 4 1 5,6;
16 4 1 2;
1 1 0 10,11;
11 4 1 5,7;
7 1 0 18,19;
19 3 1 5,7;
18 3 1 2,3;
3 1 0 10,14;
14 4 1 1,4;
10 4 1 2,3;
12 5 1 2,3;
parity 16;
0 1 0 1,2 "INIT";
2 1 1 3;
......@@ -41,8 +62,18 @@ parity 16;
14 1 1 10,16;
16 1 0 14,15;
1 1 1 3,6;
parity 4;
3 2 0 1,4 "INIT";
4 3 1 0,3;
0 1 0 1,2;
2 1 1 0,0;
1 2 1 3,3;
EOF
ltlsynt --ins=a --outs=b -f 'GFa <-> GFb' --print-pg >out
: > out
for algo in ds sd lar; do
ltlsynt --ins=a --outs=b -f 'GFa <-> GFb' --algo=$algo --print-pg >>out
done
diff out exp
cat >exp <<EOF
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment