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.
This commit is contained in:
parent
f6ac69d0d2
commit
133896d584
6 changed files with 870 additions and 528 deletions
126
bin/ltlsynt.cc
126
bin/ltlsynt.cc
|
|
@ -212,51 +212,101 @@ namespace
|
||||||
return dpa;
|
return dpa;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a smaller automaton, filtering out states that are not
|
|
||||||
// accessible. Also merge back pairs of p --(i)--> q --(o)--> r
|
spot::twa_graph_ptr
|
||||||
// transitions to p --(i&o)--> r.
|
apply_strategy(const spot::twa_graph_ptr& arena,
|
||||||
static spot::twa_graph_ptr
|
bdd all_outputs,
|
||||||
strat_to_aut(const spot::const_twa_graph_ptr& pg,
|
bool unsplit, bool keep_acc, bool leave_choice)
|
||||||
const spot::strategy_t& strat,
|
|
||||||
bdd all_outputs)
|
|
||||||
{
|
{
|
||||||
auto aut = spot::make_twa_graph(pg->get_dict());
|
spot::region_t* w_ptr =
|
||||||
aut->copy_ap_of(pg);
|
arena->get_named_prop<spot::region_t>("state-winner");
|
||||||
unsigned pg_init = pg->get_init_state_number();
|
spot::strategy_t* s_ptr =
|
||||||
std::vector<unsigned> todo{pg_init};
|
arena->get_named_prop<spot::strategy_t>("strategy");
|
||||||
std::vector<int> pg2aut(pg->num_states(), -1);
|
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());
|
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())
|
while (!todo.empty())
|
||||||
{
|
{
|
||||||
unsigned s = todo.back();
|
unsigned v = todo.back();
|
||||||
todo.pop_back();
|
todo.pop_back();
|
||||||
for (auto& e1: pg->out(s))
|
// Env edge -> keep all
|
||||||
|
for (auto &e1: arena->out(v))
|
||||||
{
|
{
|
||||||
unsigned i = 0;
|
assert(w.at(e1.dst));
|
||||||
for (auto& e2: pg->out(e1.dst))
|
if (!unsplit)
|
||||||
{
|
{
|
||||||
bool self_loop = false;
|
if (pg2aut[e1.dst] == unseen_mark)
|
||||||
if (e1.dst == s || e2.dst == e1.dst)
|
pg2aut[e1.dst] = aut->new_state();
|
||||||
self_loop = true;
|
aut->new_edge(pg2aut[v], pg2aut[e1.dst], e1.cond,
|
||||||
if (self_loop || strat.at(e1.dst) == i)
|
keep_acc ? e1.acc : spot::acc_cond::mark_t({}));
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
// 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));
|
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;
|
return aut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,22 +573,22 @@ namespace
|
||||||
|
|
||||||
if (want_time)
|
if (want_time)
|
||||||
sw.start();
|
sw.start();
|
||||||
auto solution = parity_game_solve(dpa);
|
bool player1winning = solve_parity_game(dpa);
|
||||||
if (want_time)
|
if (want_time)
|
||||||
solve_time = sw.stop();
|
solve_time = sw.stop();
|
||||||
if (verbose)
|
if (verbose)
|
||||||
std::cerr << "parity game solved in " << solve_time << " seconds\n";
|
std::cerr << "parity game solved in " << solve_time << " seconds\n";
|
||||||
nb_states_parity_game = dpa->num_states();
|
nb_states_parity_game = dpa->num_states();
|
||||||
timer.stop();
|
timer.stop();
|
||||||
if (solution.player_winning_at(1, dpa->get_init_state_number()))
|
if (player1winning)
|
||||||
{
|
{
|
||||||
std::cout << "REALIZABLE\n";
|
std::cout << "REALIZABLE\n";
|
||||||
if (!opt_real)
|
if (!opt_real)
|
||||||
{
|
{
|
||||||
if (want_time)
|
if (want_time)
|
||||||
sw.start();
|
sw.start();
|
||||||
auto strat_aut =
|
auto strat_aut = apply_strategy(dpa, all_outputs,
|
||||||
strat_to_aut(dpa, solution.winning_strategy[1], all_outputs);
|
true, false, true);
|
||||||
if (want_time)
|
if (want_time)
|
||||||
strat2aut_time = sw.stop();
|
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 |
|
| ~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 |
|
| ~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-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) |
|
| ~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
|
Objects referenced via named properties are automatically destroyed
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <spot/misc/game.hh>
|
#include <spot/misc/game.hh>
|
||||||
|
#include <spot/misc/bddlt.hh>
|
||||||
|
#include <spot/twaalgos/sccinfo.hh>
|
||||||
|
|
||||||
namespace spot
|
namespace spot
|
||||||
{
|
{
|
||||||
|
|
@ -47,154 +49,728 @@ namespace spot
|
||||||
throw std::runtime_error
|
throw std::runtime_error
|
||||||
(std::string(fnname) + ": automaton should define \"state-player\"");
|
(std::string(fnname) + ": automaton should define \"state-player\"");
|
||||||
|
|
||||||
|
if (owner->size() != arena->num_states())
|
||||||
|
throw
|
||||||
|
(std::string(fnname) + ": \"state-player\" should have "
|
||||||
|
"as many states as the automaton");
|
||||||
|
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
strategy_t attractor(const const_twa_graph_ptr& arena,
|
// Internal structs
|
||||||
const std::vector<bool>* owner,
|
// winning regions for env and player
|
||||||
const region_t& subgame, region_t& set,
|
struct winner_t
|
||||||
unsigned max_parity, int p,
|
|
||||||
bool attr_max)
|
|
||||||
{
|
{
|
||||||
strategy_t strategy;
|
std::vector<bool> has_winner_;
|
||||||
std::set<unsigned> complement(subgame.begin(), subgame.end());
|
std::vector<bool> winner_;
|
||||||
for (unsigned s: set)
|
|
||||||
complement.erase(s);
|
|
||||||
|
|
||||||
acc_cond::mark_t max_acc({});
|
inline bool operator()(unsigned v, bool p)
|
||||||
for (unsigned i = 0; i <= max_parity; ++i)
|
{
|
||||||
max_acc.set(i);
|
// returns true if player p wins v
|
||||||
|
// false otherwise
|
||||||
|
if (!has_winner_[v])
|
||||||
|
return false;
|
||||||
|
|
||||||
bool once_more;
|
return winner_[v] == p;
|
||||||
do
|
}
|
||||||
{
|
|
||||||
once_more = false;
|
|
||||||
for (auto it = complement.begin(); it != complement.end();)
|
|
||||||
{
|
|
||||||
unsigned s = *it;
|
|
||||||
unsigned i = 0;
|
|
||||||
|
|
||||||
bool is_owned = (*owner)[s] == p;
|
inline void set(unsigned v, bool p)
|
||||||
bool wins = !is_owned;
|
{
|
||||||
|
has_winner_[v] = true;
|
||||||
|
winner_[v] = p;
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& e: arena->out(s))
|
inline void unset(unsigned v)
|
||||||
|
{
|
||||||
|
has_winner_[v] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool winner(unsigned v)
|
||||||
|
{
|
||||||
|
assert(has_winner_.at(v));
|
||||||
|
return winner_[v];
|
||||||
|
}
|
||||||
|
}; // winner_t
|
||||||
|
|
||||||
|
// When using scc decomposition we need to track the
|
||||||
|
// changes made to the graph
|
||||||
|
struct edge_stash_t
|
||||||
|
{
|
||||||
|
edge_stash_t(unsigned num, unsigned dst, acc_cond::mark_t acc) noexcept
|
||||||
|
: e_num(num),
|
||||||
|
e_dst(dst),
|
||||||
|
e_acc(acc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
const unsigned e_num, e_dst;
|
||||||
|
const acc_cond::mark_t e_acc;
|
||||||
|
}; // edge_stash_t
|
||||||
|
|
||||||
|
// Internal structs used by parity_game
|
||||||
|
// Struct to change recursive calls to stack
|
||||||
|
struct work_t
|
||||||
|
{
|
||||||
|
work_t(unsigned wstep_, unsigned rd_, unsigned min_par_,
|
||||||
|
unsigned max_par_) noexcept
|
||||||
|
: wstep(wstep_),
|
||||||
|
rd(rd_),
|
||||||
|
min_par(min_par_),
|
||||||
|
max_par(max_par_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
const unsigned wstep, rd, min_par, max_par;
|
||||||
|
}; // work_t
|
||||||
|
|
||||||
|
// Collects information about an scc
|
||||||
|
// Used to detect special cases
|
||||||
|
struct subgame_info_t
|
||||||
|
{
|
||||||
|
typedef std::set<unsigned, std::greater<unsigned>> all_parities_t;
|
||||||
|
|
||||||
|
subgame_info_t() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
subgame_info_t(bool empty, bool one_parity, bool one_player0,
|
||||||
|
bool one_player1, all_parities_t parities) noexcept
|
||||||
|
: is_empty(empty),
|
||||||
|
is_one_parity(one_parity),
|
||||||
|
is_one_player0(one_player0),
|
||||||
|
is_one_player1(one_player1),
|
||||||
|
all_parities(parities)
|
||||||
|
{};
|
||||||
|
bool is_empty; // empty subgame
|
||||||
|
bool is_one_parity; // only one parity appears in the subgame
|
||||||
|
// todo : Not used yet
|
||||||
|
bool is_one_player0; // one player subgame for player0 <-> p==false
|
||||||
|
bool is_one_player1; // one player subgame for player1 <-> p==true
|
||||||
|
all_parities_t all_parities;
|
||||||
|
}; // subgame_info_t
|
||||||
|
|
||||||
|
|
||||||
|
// A class to solve parity games
|
||||||
|
// The current implementation is inspired by
|
||||||
|
// by oink however without multicore and adapted to transition based
|
||||||
|
// acceptance
|
||||||
|
class parity_game
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool solve(const twa_graph_ptr &arena)
|
||||||
|
{
|
||||||
|
ensure_parity_game(arena, "solve_parity_game()");
|
||||||
|
|
||||||
|
// todo check if reordering states according to scc is worth it
|
||||||
|
set_up(arena);
|
||||||
|
// Start recursive zielonka in a bottom-up fashion on each scc
|
||||||
|
subgame_info_t subgame_info;
|
||||||
|
for (c_scc_idx_ = 0; c_scc_idx_ < info_->scc_count(); ++c_scc_idx_)
|
||||||
|
{
|
||||||
|
// Useless SCCs are winning for player 0.
|
||||||
|
if (!info_->is_useful_scc(c_scc_idx_))
|
||||||
|
{
|
||||||
|
for (unsigned v: c_states())
|
||||||
|
w_.set(v, false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Convert transitions leaving edges to self-loops
|
||||||
|
// and check if trivially solvable
|
||||||
|
subgame_info = fix_scc();
|
||||||
|
// If empty, the scc was trivially solved
|
||||||
|
if (!subgame_info.is_empty)
|
||||||
|
{
|
||||||
|
// Check for special cases
|
||||||
|
if (subgame_info.is_one_parity)
|
||||||
|
one_par_subgame_solver(subgame_info, max_abs_par_);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// "Regular" solver
|
||||||
|
max_abs_par_ = *subgame_info.all_parities.begin();
|
||||||
|
w_stack_.emplace_back(0, 0, 0, max_abs_par_);
|
||||||
|
zielonka();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All done -> restore graph, i.e. undo self-looping
|
||||||
|
restore();
|
||||||
|
|
||||||
|
if (!std::all_of(w_.has_winner_.cbegin(), w_.has_winner_.cend(),
|
||||||
|
[](bool b)
|
||||||
|
{ return b; }))
|
||||||
|
{
|
||||||
|
for (unsigned n = 0; n < w_.has_winner_.size(); ++n)
|
||||||
|
std::cerr << "hw[" << n << "]=" << w_.has_winner_[n] << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(std::all_of(w_.has_winner_.cbegin(), w_.has_winner_.cend(),
|
||||||
|
[](bool b)
|
||||||
|
{ return b; }));
|
||||||
|
assert(std::all_of(s_.cbegin(), s_.cend(),
|
||||||
|
[](unsigned e_idx)
|
||||||
|
{ return e_idx > 0; }));
|
||||||
|
|
||||||
|
// Put the solution as named property
|
||||||
|
region_t &w = *arena->get_or_set_named_prop<region_t>("state-winner");
|
||||||
|
strategy_t &s = *arena->get_or_set_named_prop<strategy_t>("strategy");
|
||||||
|
w.swap(w_.winner_);
|
||||||
|
s.resize(s_.size());
|
||||||
|
std::copy(s_.begin(), s_.end(), s.begin());
|
||||||
|
|
||||||
|
clean_up();
|
||||||
|
return w[arena->get_init_state_number()];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Returns the vector of states currently considered
|
||||||
|
// i.e. the states of the current scc
|
||||||
|
// c_scc_idx_ is set in the 'main' loop
|
||||||
|
inline const std::vector<unsigned> &c_states()
|
||||||
|
{
|
||||||
|
assert(info_);
|
||||||
|
return info_->states_of(c_scc_idx_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_up(const twa_graph_ptr &arena)
|
||||||
|
{
|
||||||
|
owner_ptr_ = arena->get_named_prop<std::vector<bool>>("state-player");
|
||||||
|
arena_ = arena;
|
||||||
|
unsigned n_states = arena_->num_states();
|
||||||
|
// Resize data structures
|
||||||
|
subgame_.clear();
|
||||||
|
subgame_.resize(n_states, unseen_mark);
|
||||||
|
w_.has_winner_.clear();
|
||||||
|
w_.has_winner_.resize(n_states, 0);
|
||||||
|
w_.winner_.clear();
|
||||||
|
w_.winner_.resize(n_states, 0);
|
||||||
|
s_.clear();
|
||||||
|
s_.resize(n_states, -1);
|
||||||
|
// Init
|
||||||
|
rd_ = 0;
|
||||||
|
max_abs_par_ = arena_->get_acceptance().used_sets().max_set() - 1;
|
||||||
|
info_ = std::make_unique<scc_info>(arena_);
|
||||||
|
// Every edge leaving an scc needs to be "fixed"
|
||||||
|
// at some point.
|
||||||
|
// We store: number of edge fixed, original dst, original acc
|
||||||
|
change_stash_.clear();
|
||||||
|
change_stash_.reserve(info_->scc_count() * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if an scc is empty and reports the occurring parities
|
||||||
|
// or special cases
|
||||||
|
inline subgame_info_t
|
||||||
|
inspect_scc(unsigned max_par)
|
||||||
|
{
|
||||||
|
subgame_info_t info;
|
||||||
|
info.is_empty = true;
|
||||||
|
info.is_one_player0 = true;
|
||||||
|
info.is_one_player1 = true;
|
||||||
|
for (unsigned v : c_states())
|
||||||
|
{
|
||||||
|
if (subgame_[v] != unseen_mark)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool multi_edge = false;
|
||||||
|
for (const auto &e : arena_->out(v))
|
||||||
|
if (subgame_[e.dst] == unseen_mark)
|
||||||
{
|
{
|
||||||
if ((e.acc & max_acc) && subgame.count(e.dst))
|
info.is_empty = false;
|
||||||
|
unsigned this_par = e.acc.max_set() - 1;
|
||||||
|
if (this_par <= max_par)
|
||||||
{
|
{
|
||||||
if (set.count(e.dst)
|
info.all_parities.insert(this_par);
|
||||||
|| (attr_max && e.acc.max_set() - 1 == max_parity))
|
multi_edge = true;
|
||||||
{
|
|
||||||
if (is_owned)
|
|
||||||
{
|
|
||||||
strategy[s] = i;
|
|
||||||
wins = true;
|
|
||||||
break; // no need to check all edges
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!is_owned)
|
|
||||||
{
|
|
||||||
wins = false;
|
|
||||||
break; // no need to check all edges
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
|
if (multi_edge)
|
||||||
|
{
|
||||||
|
// This state has multiple edges, so it is not
|
||||||
|
// a one player subgame for !owner
|
||||||
|
if ((*owner_ptr_)[v])
|
||||||
|
info.is_one_player1 = false;
|
||||||
|
else
|
||||||
|
info.is_one_player0 = false;
|
||||||
|
}
|
||||||
|
} // v
|
||||||
|
assert(info.all_parities.size() || info.is_empty);
|
||||||
|
info.is_one_parity = info.all_parities.size() == 1;
|
||||||
|
// Done
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
if (wins)
|
// Checks if an scc can be trivially solved,
|
||||||
|
// that is, all vertices of the scc belong to the
|
||||||
|
// attractor of a transition leaving the scc
|
||||||
|
inline subgame_info_t
|
||||||
|
fix_scc()
|
||||||
|
{
|
||||||
|
auto scc_acc = info_->acc_sets_of(c_scc_idx_);
|
||||||
|
// We will override all parities of edges leaving the scc
|
||||||
|
bool added[] = {false, false};
|
||||||
|
unsigned par_pair[2];
|
||||||
|
unsigned scc_new_par = std::max(scc_acc.max_set(), 1u);
|
||||||
|
if (scc_new_par&1)
|
||||||
|
{
|
||||||
|
par_pair[1] = scc_new_par;
|
||||||
|
par_pair[0] = scc_new_par+1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
par_pair[1] = scc_new_par+1;
|
||||||
|
par_pair[0] = scc_new_par;
|
||||||
|
}
|
||||||
|
acc_cond::mark_t even_mark({par_pair[0]});
|
||||||
|
acc_cond::mark_t odd_mark({par_pair[1]});
|
||||||
|
|
||||||
|
// Only necessary to pass tests
|
||||||
|
max_abs_par_ = std::max(par_pair[0], par_pair[1]);
|
||||||
|
|
||||||
|
for (unsigned v : c_states())
|
||||||
|
{
|
||||||
|
assert(subgame_[v] == unseen_mark);
|
||||||
|
for (auto &e : arena_->out(v))
|
||||||
|
{
|
||||||
|
// The outgoing edges are taken finitely often
|
||||||
|
// -> disregard parity
|
||||||
|
if (subgame_[e.dst] != unseen_mark)
|
||||||
|
{
|
||||||
|
// Edge leaving the scc
|
||||||
|
change_stash_.emplace_back(arena_->edge_number(e),
|
||||||
|
e.dst, e.acc);
|
||||||
|
if (w_.winner(e.dst))
|
||||||
|
{
|
||||||
|
// Winning region of player -> odd
|
||||||
|
e.acc = odd_mark;
|
||||||
|
added[1] = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Winning region of env -> even
|
||||||
|
e.acc = even_mark;
|
||||||
|
added[0] = true;
|
||||||
|
}
|
||||||
|
// Replace with self-loop
|
||||||
|
e.dst = e.src;
|
||||||
|
}
|
||||||
|
} // e
|
||||||
|
} // v
|
||||||
|
|
||||||
|
// Compute the attractors of the self-loops/transitions leaving scc
|
||||||
|
// These can be directly added to the winning states
|
||||||
|
// Note: attractors can not intersect therefore the order in which
|
||||||
|
// they are computed does not matter
|
||||||
|
unsigned dummy_rd;
|
||||||
|
|
||||||
|
for (bool p : {false, true})
|
||||||
|
if (added[p])
|
||||||
|
attr(dummy_rd, p, par_pair[p], true, par_pair[p]);
|
||||||
|
|
||||||
|
if (added[0] || added[1])
|
||||||
|
// Fix "negative" strategy
|
||||||
|
for (unsigned v : c_states())
|
||||||
|
if (subgame_[v] != unseen_mark)
|
||||||
|
s_[v] = std::abs(s_[v]);
|
||||||
|
|
||||||
|
return inspect_scc(unseen_mark);
|
||||||
|
} // fix_scc
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
attr(unsigned &rd, bool p, unsigned max_par,
|
||||||
|
bool acc_par, unsigned min_win_par)
|
||||||
|
{
|
||||||
|
// Computes the attractor of the winning set of player p within a
|
||||||
|
// subgame given as rd.
|
||||||
|
// If acc_par is true, max_par transitions are also accepting and
|
||||||
|
// the subgame count will be increased
|
||||||
|
// The attracted vertices are directly added to the set
|
||||||
|
|
||||||
|
// Increase rd meaning we create a new subgame
|
||||||
|
if (acc_par)
|
||||||
|
rd = ++rd_;
|
||||||
|
// todo replace with a variant of topo sort ?
|
||||||
|
// As proposed in Oink! / PGSolver
|
||||||
|
// Needs the transposed graph however
|
||||||
|
|
||||||
|
assert((!acc_par) || (acc_par && (max_par&1) == p));
|
||||||
|
assert(!acc_par || (0 < min_win_par));
|
||||||
|
assert((min_win_par <= max_par) && (max_par <= max_abs_par_));
|
||||||
|
|
||||||
|
bool grown = false;
|
||||||
|
// We could also directly mark states as owned,
|
||||||
|
// instead of adding them to to_add first,
|
||||||
|
// possibly reducing the number of iterations.
|
||||||
|
// However by making the algorithm complete a loop
|
||||||
|
// before adding it is like a backward bfs and (generally) reduces
|
||||||
|
// the size of the strategy
|
||||||
|
static std::vector<unsigned> to_add;
|
||||||
|
|
||||||
|
assert(to_add.empty());
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!to_add.empty())
|
||||||
|
{
|
||||||
|
grown = true;
|
||||||
|
for (unsigned v : to_add)
|
||||||
|
{
|
||||||
|
// v is winning
|
||||||
|
w_.set(v, p);
|
||||||
|
// Mark if demanded
|
||||||
|
if (acc_par)
|
||||||
|
{
|
||||||
|
assert(subgame_[v] == unseen_mark);
|
||||||
|
subgame_[v] = rd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
to_add.clear();
|
||||||
|
|
||||||
|
for (unsigned v : c_states())
|
||||||
|
{
|
||||||
|
if ((subgame_[v] < rd) || (w_(v, p)))
|
||||||
|
// Not in subgame or winning
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool is_owned = (*owner_ptr_)[v] == p;
|
||||||
|
bool wins = !is_owned;
|
||||||
|
// Loop over out-going
|
||||||
|
|
||||||
|
// Optim: If given the choice,
|
||||||
|
// we seek to go to the "oldest" subgame
|
||||||
|
// That is the subgame with the lowest rd value
|
||||||
|
unsigned min_subgame_idx = -1u;
|
||||||
|
for (const auto &e: arena_->out(v))
|
||||||
|
{
|
||||||
|
unsigned this_par = e.acc.max_set() - 1;
|
||||||
|
if ((subgame_[e.dst] >= rd) && (this_par <= max_par))
|
||||||
|
{
|
||||||
|
// Check if winning
|
||||||
|
if (w_(e.dst, p)
|
||||||
|
|| (acc_par && (min_win_par <= this_par)))
|
||||||
|
{
|
||||||
|
assert(!acc_par || (this_par < min_win_par) ||
|
||||||
|
(acc_par && (min_win_par <= this_par) &&
|
||||||
|
((this_par&1) == p)));
|
||||||
|
if (is_owned)
|
||||||
|
{
|
||||||
|
wins = true;
|
||||||
|
if (acc_par)
|
||||||
|
{
|
||||||
|
s_[v] = arena_->edge_number(e);
|
||||||
|
if (min_win_par <= this_par)
|
||||||
|
// max par edge
|
||||||
|
// change sign -> mark as needs
|
||||||
|
// to be possibly fixed
|
||||||
|
s_[v] = -s_[v];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//snapping
|
||||||
|
if (subgame_[e.dst] < min_subgame_idx)
|
||||||
|
{
|
||||||
|
s_[v] = arena_->edge_number(e);
|
||||||
|
min_subgame_idx = subgame_[e.dst];
|
||||||
|
if (!p)
|
||||||
|
// No optim for env
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}// owned
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!is_owned)
|
||||||
|
{
|
||||||
|
wins = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // winning
|
||||||
|
} // subgame
|
||||||
|
}// for edges
|
||||||
|
if (wins)
|
||||||
|
to_add.push_back(v);
|
||||||
|
} // for v
|
||||||
|
} while (!to_add.empty());
|
||||||
|
// done
|
||||||
|
|
||||||
|
assert(to_add.empty());
|
||||||
|
return grown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to check if transitions that are accepted due
|
||||||
|
// to their parity remain in the winning region of p
|
||||||
|
inline bool
|
||||||
|
fix_strat_acc(unsigned rd, bool p, unsigned min_win_par, unsigned max_par)
|
||||||
|
{
|
||||||
|
for (unsigned v : c_states())
|
||||||
|
{
|
||||||
|
// Only current attractor and owned
|
||||||
|
// and winning vertices are concerned
|
||||||
|
if ((subgame_[v] != rd) || !w_(v, p)
|
||||||
|
|| ((*owner_ptr_)[v] != p) || (s_[v] > 0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// owned winning vertex of attractor
|
||||||
|
// Get the strategy edge
|
||||||
|
s_[v] = -s_[v];
|
||||||
|
const auto &e_s = arena_->edge_storage(s_[v]);
|
||||||
|
// Optimization only for player
|
||||||
|
if (!p && w_(e_s.dst, p))
|
||||||
|
// If current strat is admissible -> nothing to do
|
||||||
|
// for env
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// This is an accepting edge that is no longer admissible
|
||||||
|
// or we seek a more desirable edge (for player)
|
||||||
|
assert(min_win_par <= e_s.acc.max_set() - 1);
|
||||||
|
assert(e_s.acc.max_set() - 1 <= max_par);
|
||||||
|
|
||||||
|
// Strategy heuristic : go to the oldest subgame
|
||||||
|
unsigned min_subgame_idx = -1u;
|
||||||
|
|
||||||
|
s_[v] = -1;
|
||||||
|
for (const auto &e_fix : arena_->out(v))
|
||||||
|
{
|
||||||
|
if (subgame_[e_fix.dst] >= rd)
|
||||||
|
{
|
||||||
|
unsigned this_par = e_fix.acc.max_set() - 1;
|
||||||
|
// This edge must have less than max_par,
|
||||||
|
// otherwise it would have already been attracted
|
||||||
|
assert((this_par <= max_par)
|
||||||
|
|| ((this_par&1) != (max_par&1)));
|
||||||
|
// if it is accepting and leads to the winning region
|
||||||
|
// -> valid fix
|
||||||
|
if ((min_win_par <= this_par)
|
||||||
|
&& (this_par <= max_par)
|
||||||
|
&& w_(e_fix.dst, p)
|
||||||
|
&& (subgame_[e_fix.dst] < min_subgame_idx))
|
||||||
|
{
|
||||||
|
// Max par edge to older subgame found
|
||||||
|
s_[v] = arena_->edge_number(e_fix);
|
||||||
|
min_subgame_idx = subgame_[e_fix.dst];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s_[v] == -1)
|
||||||
|
// NO fix found
|
||||||
|
// This state is NOT won by p due to any accepting edges
|
||||||
|
return true; // true -> grown
|
||||||
|
}
|
||||||
|
// Nothing to fix or all fixed
|
||||||
|
return false; // false -> not grown == all good
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void zielonka()
|
||||||
|
{
|
||||||
|
while (!w_stack_.empty())
|
||||||
|
{
|
||||||
|
auto this_work = w_stack_.back();
|
||||||
|
w_stack_.pop_back();
|
||||||
|
|
||||||
|
switch (this_work.wstep)
|
||||||
|
{
|
||||||
|
case (0):
|
||||||
{
|
{
|
||||||
// FIXME C++17 extract/insert could be useful here
|
assert(this_work.rd == 0);
|
||||||
set.emplace(s);
|
assert(this_work.min_par == 0);
|
||||||
it = complement.erase(it);
|
|
||||||
once_more = true;
|
unsigned rd;
|
||||||
|
assert(this_work.max_par <= max_abs_par_);
|
||||||
|
|
||||||
|
// Check if empty and get parities
|
||||||
|
subgame_info_t subgame_info =
|
||||||
|
inspect_scc(this_work.max_par);
|
||||||
|
|
||||||
|
if (subgame_info.is_empty)
|
||||||
|
// Nothing to do
|
||||||
|
break;
|
||||||
|
if (subgame_info.is_one_parity)
|
||||||
|
{
|
||||||
|
// Can be trivially solved
|
||||||
|
one_par_subgame_solver(subgame_info, this_work.max_par);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the winning parity boundaries
|
||||||
|
// -> Priority compression
|
||||||
|
// Optional, improves performance
|
||||||
|
// Highest actually occurring
|
||||||
|
unsigned max_par = *subgame_info.all_parities.begin();
|
||||||
|
unsigned min_win_par = max_par;
|
||||||
|
while ((min_win_par > 2) &&
|
||||||
|
(!subgame_info.all_parities.count(min_win_par-1)))
|
||||||
|
min_win_par -= 2;
|
||||||
|
assert(max_par > 0);
|
||||||
|
assert(!subgame_info.all_parities.empty());
|
||||||
|
assert(min_win_par > 0);
|
||||||
|
|
||||||
|
// Get the player
|
||||||
|
bool p = min_win_par&1;
|
||||||
|
assert((max_par&1) == (min_win_par&1));
|
||||||
|
// Attraction to highest par
|
||||||
|
// This increases rd_ and passes it to rd
|
||||||
|
attr(rd, p, max_par, true, min_win_par);
|
||||||
|
// All those attracted get subgame_[v] <- rd
|
||||||
|
|
||||||
|
// Continuation
|
||||||
|
w_stack_.emplace_back(1, rd, min_win_par, max_par);
|
||||||
|
// Recursion
|
||||||
|
w_stack_.emplace_back(0, 0, 0, min_win_par-1);
|
||||||
|
// Others attracted will have higher counts in subgame
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else
|
case (1):
|
||||||
++it;
|
{
|
||||||
}
|
unsigned rd = this_work.rd;
|
||||||
} while (once_more);
|
unsigned min_win_par = this_work.min_par;
|
||||||
return strategy;
|
unsigned max_par = this_work.max_par;
|
||||||
}
|
assert((min_win_par&1) == (max_par&1));
|
||||||
|
bool p = min_win_par&1;
|
||||||
|
// Check if the attractor of w_[!p] is equal to w_[!p]
|
||||||
|
// if so, player wins if there remain accepting transitions
|
||||||
|
// for max_par (see fix_strat_acc)
|
||||||
|
// This does not increase but reuse rd
|
||||||
|
bool grown = attr(rd, !p, max_par, false, min_win_par);
|
||||||
|
// todo investigate: A is an attractor, so the only way that
|
||||||
|
// attr(w[!p]) != w[!p] is if the max par "exit" edges lead
|
||||||
|
// to a trap for player/ exit the winning region of the
|
||||||
|
// player so we can do a fast check instead of the
|
||||||
|
// generic attr computation which only needs to be done
|
||||||
|
// if the fast check is positive
|
||||||
|
|
||||||
void solve_rec(const const_twa_graph_ptr& arena,
|
// Check if strategy needs to be fixed / is fixable
|
||||||
const std::vector<bool>* owner,
|
if (!grown)
|
||||||
region_t& subgame, unsigned max_parity,
|
// this only concerns parity accepting edges
|
||||||
region_t (&w)[2], strategy_t (&s)[2])
|
grown = fix_strat_acc(rd, p, min_win_par, max_par);
|
||||||
{
|
// If !grown we are done, and the partitions are valid
|
||||||
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 game at each call, subgame and max_parity are used to filter states
|
|
||||||
// and transitions.
|
|
||||||
if (subgame.empty())
|
|
||||||
return;
|
|
||||||
int p = max_parity % 2;
|
|
||||||
|
|
||||||
// Recursion on max_parity.
|
if (grown)
|
||||||
region_t u;
|
{
|
||||||
auto strat_u = attractor(arena, owner, subgame, u, max_parity, p, true);
|
// Reset current game without !p attractor
|
||||||
|
for (unsigned v : c_states())
|
||||||
|
if (!w_(v, !p) && (subgame_[v] >= rd))
|
||||||
|
{
|
||||||
|
// delete ownership
|
||||||
|
w_.unset(v);
|
||||||
|
// Mark as unseen
|
||||||
|
subgame_[v] = unseen_mark;
|
||||||
|
// Unset strat for testing
|
||||||
|
s_[v] = -1;
|
||||||
|
}
|
||||||
|
w_stack_.emplace_back(0, 0, 0, max_par);
|
||||||
|
// No need to do anything else
|
||||||
|
// the attractor of !p of this level is not changed
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("No valid workstep");
|
||||||
|
} // switch
|
||||||
|
} // while
|
||||||
|
} // zielonka
|
||||||
|
|
||||||
if (max_parity == 0)
|
// Undo change to the graph made along the way
|
||||||
{
|
inline void restore()
|
||||||
s[p] = std::move(strat_u);
|
{
|
||||||
w[p] = std::move(u);
|
// "Unfix" the edges leaving the sccs
|
||||||
// FIXME what about w[!p]?
|
// This is called once the game has been solved
|
||||||
return;
|
for (auto &e_stash : change_stash_)
|
||||||
}
|
{
|
||||||
|
auto &e = arena_->edge_storage(e_stash.e_num);
|
||||||
|
e.dst = e_stash.e_dst;
|
||||||
|
e.acc = e_stash.e_acc;
|
||||||
|
}
|
||||||
|
// Done
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned s: u)
|
// Empty all internal variables
|
||||||
subgame.erase(s);
|
inline void clean_up()
|
||||||
region_t w0[2]; // Player's winning region in the first recursive call.
|
{
|
||||||
strategy_t s0[2]; // Player's winning strategy in the first
|
info_ = nullptr;
|
||||||
// recursive call.
|
subgame_.clear();
|
||||||
solve_rec(arena, owner, subgame, max_parity - 1, w0, s0);
|
w_.has_winner_.clear();
|
||||||
if (w0[0].size() + w0[1].size() != subgame.size())
|
w_.winner_.clear();
|
||||||
throw std::runtime_error("size mismatch");
|
s_.clear();
|
||||||
//if (w0[p].size() != subgame.size())
|
rd_ = 0;
|
||||||
// for (unsigned s: subgame)
|
max_abs_par_ = 0;
|
||||||
// if (w0[p].find(s) == w0[p].end())
|
change_stash_.clear();
|
||||||
// w0[!p].insert(s);
|
}
|
||||||
subgame.insert(u.begin(), u.end());
|
|
||||||
|
|
||||||
if (w0[p].size() + u.size() == subgame.size())
|
// Dedicated solver for special cases
|
||||||
{
|
inline void one_par_subgame_solver(const subgame_info_t &info,
|
||||||
s[p] = std::move(strat_u);
|
unsigned max_par)
|
||||||
s[p].insert(s0[p].begin(), s0[p].end());
|
{
|
||||||
w[p].insert(subgame.begin(), subgame.end());
|
assert(info.all_parities.size() == 1);
|
||||||
return;
|
// The entire subgame is won by the player of the only parity
|
||||||
}
|
// Any edge will do
|
||||||
|
// todo optim for smaller circuit
|
||||||
|
// This subgame gets its own counter
|
||||||
|
++rd_;
|
||||||
|
unsigned rd = rd_;
|
||||||
|
unsigned one_par = *info.all_parities.begin();
|
||||||
|
bool winner = one_par & 1;
|
||||||
|
assert(one_par <= max_par);
|
||||||
|
|
||||||
// Recursion on game size.
|
for (unsigned v : c_states())
|
||||||
auto strat_wnp = attractor(arena, owner,
|
{
|
||||||
subgame, w0[!p], max_parity, !p, false);
|
if (subgame_[v] != unseen_mark)
|
||||||
|
continue;
|
||||||
|
// State of the subgame
|
||||||
|
subgame_[v] = rd;
|
||||||
|
w_.set(v, winner);
|
||||||
|
// Get the strategy
|
||||||
|
assert(s_[v] == -1);
|
||||||
|
for (const auto &e : arena_->out(v))
|
||||||
|
{
|
||||||
|
unsigned this_par = e.acc.max_set() - 1;
|
||||||
|
if ((subgame_[e.dst] >= rd) && (this_par <= max_par))
|
||||||
|
{
|
||||||
|
assert(this_par == one_par);
|
||||||
|
// Ok for strat
|
||||||
|
s_[v] = arena_->edge_number(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert((0 < s_[v]) && (s_[v] < unseen_mark));
|
||||||
|
}
|
||||||
|
// Done
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned s: w0[!p])
|
const unsigned unseen_mark = std::numeric_limits<unsigned>::max();
|
||||||
subgame.erase(s);
|
|
||||||
|
|
||||||
region_t w1[2]; // Odd's winning region in the second recursive call.
|
twa_graph_ptr arena_;
|
||||||
strategy_t s1[2]; // Odd's winning strategy in the second recursive call.
|
const std::vector<bool> *owner_ptr_;
|
||||||
solve_rec(arena, owner, subgame, max_parity, w1, s1);
|
unsigned rd_;
|
||||||
if (w1[0].size() + w1[1].size() != subgame.size())
|
winner_t w_;
|
||||||
throw std::runtime_error("size mismatch");
|
// Subgame array similar to the one from oink!
|
||||||
|
std::vector<unsigned> subgame_;
|
||||||
|
// strategies for env and player; For synthesis only player is needed
|
||||||
|
// We need a signed value here in order to "fix" the strategy
|
||||||
|
// during construction
|
||||||
|
std::vector<long long> s_;
|
||||||
|
|
||||||
w[p] = std::move(w1[p]);
|
// Informations about sccs andthe current scc
|
||||||
s[p] = std::move(s1[p]);
|
std::unique_ptr<scc_info> info_;
|
||||||
|
unsigned max_abs_par_; // Max parity occurring in the current scc
|
||||||
w[!p] = std::move(w1[!p]);
|
// Info on the current scc
|
||||||
w[!p].insert(w0[!p].begin(), w0[!p].end());
|
unsigned c_scc_idx_;
|
||||||
s[!p] = std::move(strat_wnp);
|
// Fixes made to the sccs that have to be undone
|
||||||
s[!p].insert(s0[!p].begin(), s0[!p].end());
|
// before returning
|
||||||
s[!p].insert(s1[!p].begin(), s1[!p].end());
|
std::vector<edge_stash_t> change_stash_;
|
||||||
|
// Change recursive calls to stack
|
||||||
subgame.insert(w0[!p].begin(), w0[!p].end());
|
std::vector<work_t> w_stack_;
|
||||||
}
|
};
|
||||||
|
|
||||||
} // anonymous
|
} // anonymous
|
||||||
|
|
||||||
|
bool solve_parity_game(const twa_graph_ptr& arena)
|
||||||
|
{
|
||||||
|
parity_game pg;
|
||||||
|
return pg.solve(arena);
|
||||||
|
}
|
||||||
|
|
||||||
void pg_print(std::ostream& os, const const_twa_graph_ptr& arena)
|
void pg_print(std::ostream& os, const const_twa_graph_ptr& arena)
|
||||||
{
|
{
|
||||||
auto owner = ensure_parity_game(arena, "pg_print");
|
auto owner = ensure_parity_game(arena, "pg_print");
|
||||||
|
|
@ -230,35 +806,13 @@ namespace spot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
solved_game parity_game_solve(const const_twa_graph_ptr& arena)
|
|
||||||
{
|
|
||||||
solved_game result;
|
|
||||||
result.arena = arena;
|
|
||||||
|
|
||||||
const std::vector<bool>* owner =
|
|
||||||
ensure_parity_game(arena, "parity_game_solve");
|
|
||||||
|
|
||||||
region_t states_;
|
|
||||||
unsigned ns = arena->num_states();
|
|
||||||
for (unsigned i = 0; i < ns; ++i)
|
|
||||||
states_.insert(i);
|
|
||||||
|
|
||||||
acc_cond::mark_t m{};
|
|
||||||
for (const auto& e: arena->edges())
|
|
||||||
m |= e.acc;
|
|
||||||
|
|
||||||
solve_rec(arena, owner, states_, m.max_set(),
|
|
||||||
result.winning_region, result.winning_strategy);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void propagate_players(spot::twa_graph_ptr& arena,
|
void propagate_players(spot::twa_graph_ptr& arena,
|
||||||
bool first_player, bool complete0)
|
bool first_player, bool complete0)
|
||||||
{
|
{
|
||||||
auto um = arena->acc().unsat_mark();
|
auto um = arena->acc().unsat_mark();
|
||||||
if (!um.first)
|
if (!um.first)
|
||||||
throw std::runtime_error("game winning condition is a tautology");
|
throw std::runtime_error
|
||||||
|
("propagate_players(): game winning condition is a tautology");
|
||||||
|
|
||||||
unsigned sink_env = 0;
|
unsigned sink_env = 0;
|
||||||
unsigned sink_con = 0;
|
unsigned sink_con = 0;
|
||||||
|
|
@ -311,45 +865,39 @@ namespace spot
|
||||||
}
|
}
|
||||||
|
|
||||||
twa_graph_ptr
|
twa_graph_ptr
|
||||||
highlight_strategy(twa_graph_ptr& aut, const strategy_t& s,
|
highlight_strategy(twa_graph_ptr& aut,
|
||||||
unsigned color)
|
int player0_color,
|
||||||
|
int player1_color)
|
||||||
{
|
{
|
||||||
unsigned ns = aut->num_states();
|
auto owner = ensure_parity_game(aut, "highlight_strategy()");
|
||||||
auto* highlight = aut->get_or_set_named_prop<std::map<unsigned, unsigned>>
|
region_t* w = aut->get_named_prop<region_t>("state-winner");
|
||||||
("highlight-edges");
|
strategy_t* s = aut->get_named_prop<strategy_t>("strategy");
|
||||||
|
if (!w || !s)
|
||||||
|
throw std::runtime_error("highlight_strategy(): "
|
||||||
|
"strategy not available, solve the game first");
|
||||||
|
|
||||||
for (auto [src, n]: s)
|
unsigned ns = aut->num_states();
|
||||||
|
auto* hl_edges = aut->get_or_set_named_prop<std::map<unsigned, unsigned>>
|
||||||
|
("highlight-edges");
|
||||||
|
auto* hl_states = aut->get_or_set_named_prop<std::map<unsigned, unsigned>>
|
||||||
|
("highlight-states");
|
||||||
|
|
||||||
|
if (unsigned sz = std::min(w->size(), s->size()); sz < ns)
|
||||||
|
ns = sz;
|
||||||
|
|
||||||
|
for (unsigned n = 0; n < ns; ++n)
|
||||||
{
|
{
|
||||||
if (src >= ns)
|
int color = (*w)[n] ? player1_color : player0_color;
|
||||||
throw std::runtime_error
|
if (color == -1)
|
||||||
("highlight_strategy(): strategy refers to unexisting states");
|
continue;
|
||||||
unsigned int i = 0;
|
(*hl_states)[n] = color;
|
||||||
for (auto& t: aut->out(src))
|
if ((*w)[n] == (*owner)[n])
|
||||||
if (i++ == n)
|
(*hl_edges)[(*s)[n]] = color;
|
||||||
{
|
|
||||||
(*highlight)[aut->edge_number(t)] = color;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return aut;
|
return aut;
|
||||||
}
|
}
|
||||||
|
|
||||||
twa_graph_ptr
|
|
||||||
solved_game::highlight_strategy(unsigned player, unsigned color)
|
|
||||||
{
|
|
||||||
auto aut = std::const_pointer_cast<twa_graph>(arena);
|
|
||||||
|
|
||||||
auto* highlight = aut->get_or_set_named_prop<std::map<unsigned, unsigned>>
|
|
||||||
("highlight-states");
|
|
||||||
unsigned ns = aut->num_states();
|
|
||||||
for (unsigned i = 0; i < ns; ++i)
|
|
||||||
if (player_winning_at(player, i))
|
|
||||||
(*highlight)[i] = color;
|
|
||||||
|
|
||||||
return spot::highlight_strategy(aut, winning_strategy[!!player], color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_state_players(twa_graph_ptr arena, std::vector<bool> owners)
|
void set_state_players(twa_graph_ptr arena, std::vector<bool> owners)
|
||||||
{
|
{
|
||||||
std::vector<bool>* owners_ptr = new std::vector<bool>(owners);
|
std::vector<bool>* owners_ptr = new std::vector<bool>(owners);
|
||||||
|
|
|
||||||
|
|
@ -47,27 +47,12 @@ namespace spot
|
||||||
bool complete0 = true);
|
bool complete0 = true);
|
||||||
|
|
||||||
|
|
||||||
typedef std::unordered_set<unsigned> region_t;
|
// false -> env, true -> player
|
||||||
typedef std::unordered_map<unsigned, unsigned> strategy_t;
|
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
|
/// \brief solve a parity-game
|
||||||
///
|
///
|
||||||
/// The arena is a deterministic max odd parity automaton with a
|
/// 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
|
/// This computes the winning strategy and winning region of this
|
||||||
/// game for player 1 using Zielonka's recursive algorithm.
|
/// game for player 1 using Zielonka's recursive algorithm.
|
||||||
/// \cite zielonka.98.tcs
|
/// \cite zielonka.98.tcs
|
||||||
|
///
|
||||||
|
/// Return the player winning in the initial state, and set
|
||||||
|
/// the state-winner and strategy named properties.
|
||||||
SPOT_API
|
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
|
/// \brief Print a max odd parity game using PG-solver syntax
|
||||||
SPOT_API
|
SPOT_API
|
||||||
|
|
@ -85,10 +73,12 @@ namespace spot
|
||||||
|
|
||||||
|
|
||||||
/// \brief Highlight the edges of a strategy on an automaton.
|
/// \brief Highlight the edges of a strategy on an automaton.
|
||||||
|
///
|
||||||
|
/// Pass a negative color to not display the corresponding strategy.
|
||||||
SPOT_API
|
SPOT_API
|
||||||
twa_graph_ptr highlight_strategy(twa_graph_ptr& arena,
|
twa_graph_ptr highlight_strategy(twa_graph_ptr& arena,
|
||||||
const strategy_t& s,
|
int player0_color = 5,
|
||||||
unsigned color);
|
int player1_color = 4);
|
||||||
|
|
||||||
/// \brief Set the owner for all the states.
|
/// \brief Set the owner for all the states.
|
||||||
SPOT_API
|
SPOT_API
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,27 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
cat >exp <<EOF
|
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;
|
parity 16;
|
||||||
0 1 0 1,2 "INIT";
|
0 1 0 1,2 "INIT";
|
||||||
2 1 1 3;
|
2 1 1 3;
|
||||||
|
|
@ -41,8 +62,18 @@ parity 16;
|
||||||
14 1 1 10,16;
|
14 1 1 10,16;
|
||||||
16 1 0 14,15;
|
16 1 0 14,15;
|
||||||
1 1 1 3,6;
|
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
|
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
|
diff out exp
|
||||||
|
|
||||||
cat >exp <<EOF
|
cat >exp <<EOF
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,7 @@
|
||||||
"</svg>\n"
|
"</svg>\n"
|
||||||
],
|
],
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7fcb3c55f9f0> >"
|
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f9bf8477330> >"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 2,
|
"execution_count": 2,
|
||||||
|
|
@ -360,29 +360,13 @@
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"The `parity_game_solve()` function returns a `solved_game` object."
|
"The `solve_parity_game()` function returns the player winning from the initial state (`False` for player 0, and `True` for player 1)."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 4,
|
"execution_count": 4,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"sol = spot.parity_game_solve(game)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"The solved game can be queried to know if a player is winning when the game starts in some given."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 5,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
|
|
@ -390,283 +374,27 @@
|
||||||
"True"
|
"True"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 5,
|
"execution_count": 4,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"sol.player_winning_at(1, game.get_init_state_number())"
|
"spot.solve_parity_game(game)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Calling the `highlight_strategy` method will decorate the original game with colors showing the winning region (states from which a player has a strategy to win), and strategy (which transition should be used for each winning state owned by that player) of a given player. Let's paint the strategy of player 1 in green (color 4) for this example:"
|
"Additional information about the player winning in each state, and the strategy have been stored in the automaton but are not displayed by default.\n",
|
||||||
|
"\n",
|
||||||
|
"Calling the `highlight_strategy` function will decorate the game's automaton with colors showing the winning regions (states from which a player has a strategy to win), and strategy (which transition should be used for each winning state owned by that player) of a given player. Here green corresponds to player 1 (who tries to satisfy the acceptance condition), and red to player 0 (who tries not to)."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 6,
|
"execution_count": 5,
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"image/svg+xml": [
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
|
|
||||||
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
|
|
||||||
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
|
|
||||||
"<!-- Generated by graphviz version 2.43.0 (0)\n",
|
|
||||||
" -->\n",
|
|
||||||
"<!-- Pages: 1 -->\n",
|
|
||||||
"<svg width=\"567pt\" height=\"338pt\"\n",
|
|
||||||
" viewBox=\"0.00 0.00 566.58 337.76\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
|
|
||||||
"<g id=\"graph0\" class=\"graph\" transform=\"scale(1.0 1.0) rotate(0) translate(4 333.76)\">\n",
|
|
||||||
"<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-333.76 562.58,-333.76 562.58,4 -4,4\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"197.79\" y=\"-315.56\" font-family=\"Lato\" font-size=\"14.00\">Fin(</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"220.79\" y=\"-315.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff7f00\">❷</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"236.79\" y=\"-315.56\" font-family=\"Lato\" font-size=\"14.00\">) & (Inf(</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"282.79\" y=\"-315.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"298.79\" y=\"-315.56\" font-family=\"Lato\" font-size=\"14.00\">) | Fin(</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"336.79\" y=\"-315.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#1f78b4\">⓿</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"352.79\" y=\"-315.56\" font-family=\"Lato\" font-size=\"14.00\">))</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"223.79\" y=\"-301.56\" font-family=\"Lato\" font-size=\"14.00\">[parity max odd 3]</text>\n",
|
|
||||||
"<!-- I -->\n",
|
|
||||||
"<!-- 0 -->\n",
|
|
||||||
"<g id=\"node2\" class=\"node\">\n",
|
|
||||||
"<title>0</title>\n",
|
|
||||||
"<ellipse fill=\"#ffffaa\" stroke=\"#33a02c\" stroke-width=\"2\" cx=\"56\" cy=\"-171.76\" rx=\"18\" ry=\"18\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"56\" y=\"-168.06\" font-family=\"Lato\" font-size=\"14.00\">0</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- I->0 -->\n",
|
|
||||||
"<g id=\"edge1\" class=\"edge\">\n",
|
|
||||||
"<title>I->0</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M1.15,-171.76C2.79,-171.76 17.15,-171.76 30.63,-171.76\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"37.94,-171.76 30.94,-174.91 34.44,-171.76 30.94,-171.76 30.94,-171.76 30.94,-171.76 34.44,-171.76 30.94,-168.61 37.94,-171.76 37.94,-171.76\"/>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 1 -->\n",
|
|
||||||
"<g id=\"node3\" class=\"node\">\n",
|
|
||||||
"<title>1</title>\n",
|
|
||||||
"<polygon fill=\"#ffffaa\" stroke=\"#33a02c\" stroke-width=\"2\" points=\"148.98,-163.76 126.02,-145.76 148.98,-127.76 171.94,-145.76 148.98,-163.76\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"148.98\" y=\"-142.06\" font-family=\"Lato\" font-size=\"14.00\">1</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 0->1 -->\n",
|
|
||||||
"<g id=\"edge2\" class=\"edge\">\n",
|
|
||||||
"<title>0->1</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M69.9,-159.68C76.09,-154.68 83.92,-149.43 92,-146.76 101.41,-143.65 112.21,-142.86 121.8,-143.03\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"128.85,-143.33 121.72,-146.18 125.35,-143.18 121.86,-143.04 121.86,-143.04 121.86,-143.04 125.35,-143.18 121.99,-139.89 128.85,-143.33 128.85,-143.33\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"94.5\" y=\"-165.56\" font-family=\"Lato\" font-size=\"14.00\">!a</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"92\" y=\"-150.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 2 -->\n",
|
|
||||||
"<g id=\"node4\" class=\"node\">\n",
|
|
||||||
"<title>2</title>\n",
|
|
||||||
"<polygon fill=\"#ffffaa\" stroke=\"#33a02c\" stroke-width=\"2\" points=\"148.98,-217.76 126.02,-199.76 148.98,-181.76 171.94,-199.76 148.98,-217.76\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"148.98\" y=\"-196.06\" font-family=\"Lato\" font-size=\"14.00\">2</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 0->2 -->\n",
|
|
||||||
"<g id=\"edge3\" class=\"edge\">\n",
|
|
||||||
"<title>0->2</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M73.69,-176.89C88.1,-181.33 109.07,-187.78 125.05,-192.7\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"132.01,-194.84 124.39,-195.79 128.66,-193.81 125.32,-192.78 125.32,-192.78 125.32,-192.78 128.66,-193.81 126.24,-189.77 132.01,-194.84 132.01,-194.84\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"96.5\" y=\"-205.56\" font-family=\"Lato\" font-size=\"14.00\">a</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"92\" y=\"-190.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 3 -->\n",
|
|
||||||
"<g id=\"node5\" class=\"node\">\n",
|
|
||||||
"<title>3</title>\n",
|
|
||||||
"<ellipse fill=\"#ffffaa\" stroke=\"black\" cx=\"241.96\" cy=\"-60.76\" rx=\"18\" ry=\"18\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"241.96\" y=\"-57.06\" font-family=\"Lato\" font-size=\"14.00\">3</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 1->3 -->\n",
|
|
||||||
"<g id=\"edge4\" class=\"edge\">\n",
|
|
||||||
"<title>1->3</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M160.06,-136.34C175.16,-122.24 203.86,-95.42 222.7,-77.82\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"228.21,-72.67 225.24,-79.75 225.65,-75.06 223.09,-77.45 223.09,-77.45 223.09,-77.45 225.65,-75.06 220.94,-75.15 228.21,-72.67 228.21,-72.67\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"191.96\" y=\"-126.56\" font-family=\"Lato\" font-size=\"14.00\">!b</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"189.96\" y=\"-111.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 4 -->\n",
|
|
||||||
"<g id=\"node6\" class=\"node\">\n",
|
|
||||||
"<title>4</title>\n",
|
|
||||||
"<ellipse fill=\"#ffffaa\" stroke=\"#33a02c\" stroke-width=\"2\" cx=\"241.96\" cy=\"-182.76\" rx=\"18\" ry=\"18\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"241.96\" y=\"-179.06\" font-family=\"Lato\" font-size=\"14.00\">4</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 1->4 -->\n",
|
|
||||||
"<g id=\"edge5\" class=\"edge\">\n",
|
|
||||||
"<title>1->4</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"#33a02c\" stroke-width=\"2\" d=\"M170.91,-146.58C181.75,-147.62 194.98,-149.89 205.96,-154.76 212.12,-157.49 218.12,-161.58 223.37,-165.8\"/>\n",
|
|
||||||
"<polygon fill=\"#33a02c\" stroke=\"#33a02c\" stroke-width=\"2\" points=\"228.73,-170.36 221.36,-168.22 225.74,-168.48 223.08,-166.21 223.4,-165.83 223.72,-165.44 226.39,-167.71 225.44,-163.43 228.73,-170.36 228.73,-170.36\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"193.96\" y=\"-173.56\" font-family=\"Lato\" font-size=\"14.00\">b</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"189.96\" y=\"-158.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 2->4 -->\n",
|
|
||||||
"<g id=\"edge6\" class=\"edge\">\n",
|
|
||||||
"<title>2->4</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"#33a02c\" stroke-width=\"2\" d=\"M167.95,-196.4C181.93,-193.79 201.45,-190.14 216.79,-187.27\"/>\n",
|
|
||||||
"<polygon fill=\"#33a02c\" stroke=\"#33a02c\" stroke-width=\"2\" points=\"223.94,-185.94 217.64,-190.32 220.6,-187.07 217.15,-187.72 217.06,-187.22 216.97,-186.73 220.41,-186.09 216.48,-184.13 223.94,-185.94 223.94,-185.94\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"193.96\" y=\"-210.56\" font-family=\"Lato\" font-size=\"14.00\">b</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"189.96\" y=\"-195.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 5 -->\n",
|
|
||||||
"<g id=\"node7\" class=\"node\">\n",
|
|
||||||
"<title>5</title>\n",
|
|
||||||
"<ellipse fill=\"#ffffaa\" stroke=\"black\" cx=\"427.92\" cy=\"-140.76\" rx=\"18\" ry=\"18\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"427.92\" y=\"-137.06\" font-family=\"Lato\" font-size=\"14.00\">5</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 2->5 -->\n",
|
|
||||||
"<g id=\"edge7\" class=\"edge\">\n",
|
|
||||||
"<title>2->5</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M160.75,-208.85C168.39,-214.75 179.16,-222.01 189.96,-225.76 275.06,-255.28 319.26,-296.01 391.92,-242.76 413.34,-227.05 401.22,-210.86 409.92,-185.76 412.38,-178.66 415.21,-170.99 417.85,-164.03\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"420.4,-157.35 420.84,-165.01 419.15,-160.62 417.9,-163.89 417.9,-163.89 417.9,-163.89 419.15,-160.62 414.96,-162.76 420.4,-157.35 420.4,-157.35\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"279.96\" y=\"-282.56\" font-family=\"Lato\" font-size=\"14.00\">!b</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"277.96\" y=\"-267.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 6 -->\n",
|
|
||||||
"<g id=\"node8\" class=\"node\">\n",
|
|
||||||
"<title>6</title>\n",
|
|
||||||
"<polygon fill=\"#ffffaa\" stroke=\"black\" points=\"528.25,-71.76 505.29,-53.76 528.25,-35.76 551.21,-53.76 528.25,-71.76\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"528.25\" y=\"-50.06\" font-family=\"Lato\" font-size=\"14.00\">6</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 3->6 -->\n",
|
|
||||||
"<g id=\"edge8\" class=\"edge\">\n",
|
|
||||||
"<title>3->6</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M257.63,-51.19C263.78,-47.39 271.08,-43.12 277.96,-39.76 319.9,-19.23 330.01,-10.35 375.92,-1.76 425.97,7.61 481.93,-23.1 509.68,-41.29\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"515.79,-45.4 508.22,-44.11 512.88,-43.45 509.98,-41.5 509.98,-41.5 509.98,-41.5 512.88,-43.45 511.74,-38.88 515.79,-45.4 515.79,-45.4\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"378.42\" y=\"-20.56\" font-family=\"Lato\" font-size=\"14.00\">!a</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"375.92\" y=\"-5.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 7 -->\n",
|
|
||||||
"<g id=\"node9\" class=\"node\">\n",
|
|
||||||
"<title>7</title>\n",
|
|
||||||
"<polygon fill=\"#ffffaa\" stroke=\"black\" points=\"334.94,-124.76 311.98,-106.76 334.94,-88.76 357.89,-106.76 334.94,-124.76\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"334.94\" y=\"-103.06\" font-family=\"Lato\" font-size=\"14.00\">7</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 3->7 -->\n",
|
|
||||||
"<g id=\"edge9\" class=\"edge\">\n",
|
|
||||||
"<title>3->7</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M258.39,-68.56C273.79,-76.35 297.5,-88.33 314.24,-96.8\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"320.51,-99.97 312.84,-99.62 317.38,-98.39 314.26,-96.81 314.26,-96.81 314.26,-96.81 317.38,-98.39 315.68,-94 320.51,-99.97 320.51,-99.97\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"282.46\" y=\"-104.56\" font-family=\"Lato\" font-size=\"14.00\">a</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"277.96\" y=\"-89.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff7f00\">❷</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 8 -->\n",
|
|
||||||
"<g id=\"node10\" class=\"node\">\n",
|
|
||||||
"<title>8</title>\n",
|
|
||||||
"<polygon fill=\"#ffffaa\" stroke=\"#33a02c\" stroke-width=\"2\" points=\"334.94,-222.76 311.98,-204.76 334.94,-186.76 357.89,-204.76 334.94,-222.76\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"334.94\" y=\"-201.06\" font-family=\"Lato\" font-size=\"14.00\">8</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 4->8 -->\n",
|
|
||||||
"<g id=\"edge10\" class=\"edge\">\n",
|
|
||||||
"<title>4->8</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M259.64,-186.79C273.79,-190.21 294.25,-195.16 310.12,-199\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"317.05,-200.67 309.5,-202.09 313.65,-199.85 310.24,-199.03 310.24,-199.03 310.24,-199.03 313.65,-199.85 310.98,-195.97 317.05,-200.67 317.05,-200.67\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"281.46\" y=\"-213.56\" font-family=\"Lato\" font-size=\"14.00\">1</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"277.96\" y=\"-198.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff7f00\">❷</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 5->6 -->\n",
|
|
||||||
"<g id=\"edge12\" class=\"edge\">\n",
|
|
||||||
"<title>5->6</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M442.18,-129.03C460.14,-113.14 492.15,-84.82 511.36,-67.81\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"516.89,-62.92 513.73,-69.92 514.27,-65.24 511.65,-67.56 511.65,-67.56 511.65,-67.56 514.27,-65.24 509.56,-65.2 516.89,-62.92 516.89,-62.92\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"466.42\" y=\"-125.56\" font-family=\"Lato\" font-size=\"14.00\">!a</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"463.92\" y=\"-110.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 5->7 -->\n",
|
|
||||||
"<g id=\"edge11\" class=\"edge\">\n",
|
|
||||||
"<title>5->7</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M409.86,-142.24C399.69,-142.52 386.71,-141.81 375.92,-137.76 366.67,-134.29 357.82,-127.88 350.82,-121.79\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"345.4,-116.81 352.69,-119.22 347.98,-119.18 350.56,-121.55 350.56,-121.55 350.56,-121.55 347.98,-119.18 348.43,-123.87 345.4,-116.81 345.4,-116.81\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"380.42\" y=\"-159.56\" font-family=\"Lato\" font-size=\"14.00\">a</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"375.92\" y=\"-144.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff7f00\">❷</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 6->3 -->\n",
|
|
||||||
"<g id=\"edge13\" class=\"edge\">\n",
|
|
||||||
"<title>6->3</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M505.57,-54.29C453.62,-55.57 322.27,-58.81 267.52,-60.15\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"260.31,-60.33 267.23,-57.01 263.81,-60.25 267.31,-60.16 267.31,-60.16 267.31,-60.16 263.81,-60.25 267.39,-63.31 260.31,-60.33 260.31,-60.33\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"379.42\" y=\"-75.56\" font-family=\"Lato\" font-size=\"14.00\">1</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"375.92\" y=\"-60.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 7->5 -->\n",
|
|
||||||
"<g id=\"edge14\" class=\"edge\">\n",
|
|
||||||
"<title>7->5</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M352.29,-102.15C363.83,-99.77 379.4,-98.37 391.92,-103.76 400.16,-107.31 407.38,-113.87 413.08,-120.49\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"417.5,-126 410.66,-122.51 415.31,-123.27 413.12,-120.54 413.12,-120.54 413.12,-120.54 415.31,-123.27 415.57,-118.57 417.5,-126 417.5,-126\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"379.42\" y=\"-122.56\" font-family=\"Lato\" font-size=\"14.00\">1</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"375.92\" y=\"-107.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff7f00\">❷</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 8->4 -->\n",
|
|
||||||
"<g id=\"edge15\" class=\"edge\">\n",
|
|
||||||
"<title>8->4</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M327.6,-192.49C320.95,-181.1 309.32,-164.86 293.96,-157.76 282.73,-152.57 270.27,-158.46 260.47,-165.83\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"255.04,-170.26 258.47,-163.39 257.75,-168.04 260.47,-165.83 260.47,-165.83 260.47,-165.83 257.75,-168.04 262.46,-168.27 255.04,-170.26 255.04,-170.26\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"281.96\" y=\"-176.56\" font-family=\"Lato\" font-size=\"14.00\">b</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"277.96\" y=\"-161.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff7f00\">❷</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 9 -->\n",
|
|
||||||
"<g id=\"node11\" class=\"node\">\n",
|
|
||||||
"<title>9</title>\n",
|
|
||||||
"<ellipse fill=\"#ffffaa\" stroke=\"#33a02c\" stroke-width=\"2\" cx=\"427.92\" cy=\"-212.76\" rx=\"18\" ry=\"18\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"427.92\" y=\"-209.06\" font-family=\"Lato\" font-size=\"14.00\">9</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 8->9 -->\n",
|
|
||||||
"<g id=\"edge16\" class=\"edge\">\n",
|
|
||||||
"<title>8->9</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"#33a02c\" stroke-width=\"2\" d=\"M356.13,-206.53C369.86,-207.74 388.1,-209.35 402.62,-210.62\"/>\n",
|
|
||||||
"<polygon fill=\"#33a02c\" stroke=\"#33a02c\" stroke-width=\"2\" points=\"409.84,-211.26 402.59,-213.78 406.31,-211.45 402.82,-211.14 402.86,-210.64 402.91,-210.15 406.39,-210.45 403.14,-207.51 409.84,-211.26 409.84,-211.26\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"377.92\" y=\"-227.56\" font-family=\"Lato\" font-size=\"14.00\">!b</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"375.92\" y=\"-212.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 10 -->\n",
|
|
||||||
"<g id=\"node12\" class=\"node\">\n",
|
|
||||||
"<title>10</title>\n",
|
|
||||||
"<polygon fill=\"#ffffaa\" stroke=\"#33a02c\" stroke-width=\"2\" points=\"528.25,-229.76 498.08,-211.76 528.25,-193.76 558.41,-211.76 528.25,-229.76\"/>\n",
|
|
||||||
"<text text-anchor=\"middle\" x=\"528.25\" y=\"-208.06\" font-family=\"Lato\" font-size=\"14.00\">10</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 9->10 -->\n",
|
|
||||||
"<g id=\"edge17\" class=\"edge\">\n",
|
|
||||||
"<title>9->10</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"black\" d=\"M441.39,-200.41C447.61,-195.11 455.58,-189.52 463.92,-186.76 479.35,-181.65 496.47,-189.25 509.02,-197.37\"/>\n",
|
|
||||||
"<polygon fill=\"black\" stroke=\"black\" points=\"514.78,-201.34 507.23,-199.96 511.9,-199.36 509.02,-197.37 509.02,-197.37 509.02,-197.37 511.9,-199.36 510.81,-194.78 514.78,-201.34 514.78,-201.34\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"467.42\" y=\"-205.56\" font-family=\"Lato\" font-size=\"14.00\">1</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"463.92\" y=\"-190.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"<!-- 10->9 -->\n",
|
|
||||||
"<g id=\"edge18\" class=\"edge\">\n",
|
|
||||||
"<title>10->9</title>\n",
|
|
||||||
"<path fill=\"none\" stroke=\"#33a02c\" stroke-width=\"2\" d=\"M503.51,-215.16C491.63,-216.48 477.02,-217.51 463.92,-216.76 460.44,-216.56 456.77,-216.25 453.17,-215.89\"/>\n",
|
|
||||||
"<polygon fill=\"#33a02c\" stroke=\"#33a02c\" stroke-width=\"2\" points=\"445.97,-215.1 453.27,-212.73 449.51,-214.99 452.98,-215.37 452.93,-215.87 452.88,-216.36 449.4,-215.98 452.59,-219 445.97,-215.1 445.97,-215.1\"/>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"467.42\" y=\"-235.56\" font-family=\"Lato\" font-size=\"14.00\">1</text>\n",
|
|
||||||
"<text text-anchor=\"start\" x=\"463.92\" y=\"-220.56\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#ff4da0\">❶</text>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"</g>\n",
|
|
||||||
"</svg>\n"
|
|
||||||
],
|
|
||||||
"text/plain": [
|
|
||||||
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7fcb3c55a1e0> >"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 6,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"sol.highlight_strategy(1, 4)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Because `highlight_strategy` simply decorates the original automaton, we can call it a second time to show that player 0 could win if it had a way to reach the red (color 5) region and play the red strategy."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 7,
|
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -903,24 +631,17 @@
|
||||||
"</svg>\n"
|
"</svg>\n"
|
||||||
],
|
],
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7fcb3c55a900> >"
|
"<spot.twa_graph; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_graph > *' at 0x7f9bf83e1060> >"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 7,
|
"execution_count": 5,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"sol.highlight_strategy(0, 5)"
|
"spot.highlight_strategy(game)"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|
@ -939,7 +660,7 @@
|
||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.8.5"
|
"version": "3.8.6rc1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue