// -*- coding: utf-8 -*- // Copyright (C) 2017, 2018, 2020 Laboratoire de Recherche et Développement // de l'Epita (LRDE). // // This file is part of Spot, a model checking library. // // Spot is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // Spot is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public // License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "config.h" #include #include namespace spot { namespace { static const std::vector* ensure_parity_game(const const_twa_graph_ptr& arena, const char* fnname) { bool max, odd; arena->acc().is_parity(max, odd, true); if (!(max && odd)) throw std::runtime_error (std::string(fnname) + ": arena must have max-odd acceptance condition"); for (const auto& e : arena->edges()) if (!e.acc) throw std::runtime_error (std::string(fnname) + ": arena must be colorized"); auto owner = arena->get_named_prop>("state-player"); if (!owner) throw std::runtime_error (std::string(fnname) + ": automaton should define \"state-player\""); return owner; } strategy_t attractor(const const_twa_graph_ptr& arena, const std::vector* owner, const region_t& subgame, region_t& set, unsigned max_parity, int p, bool attr_max) { strategy_t strategy; std::set complement(subgame.begin(), subgame.end()); 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 { once_more = false; for (auto it = complement.begin(); it != complement.end();) { unsigned s = *it; unsigned i = 0; bool is_owned = (*owner)[s] == p; bool wins = !is_owned; for (const auto& e: arena->out(s)) { if ((e.acc & max_acc) && subgame.count(e.dst)) { if (set.count(e.dst) || (attr_max && e.acc.max_set() - 1 == max_parity)) { 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 (wins) { // FIXME C++17 extract/insert could be useful here set.emplace(s); it = complement.erase(it); once_more = true; } else ++it; } } while (once_more); return strategy; } void solve_rec(const const_twa_graph_ptr& arena, const std::vector* owner, region_t& subgame, unsigned max_parity, region_t (&w)[2], strategy_t (&s)[2]) { 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. region_t u; auto strat_u = attractor(arena, owner, 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) subgame.erase(s); region_t w0[2]; // Player's winning region in the first recursive call. strategy_t s0[2]; // Player's winning strategy in the first // recursive call. solve_rec(arena, owner, subgame, max_parity - 1, w0, s0); if (w0[0].size() + w0[1].size() != subgame.size()) throw std::runtime_error("size mismatch"); //if (w0[p].size() != subgame.size()) // for (unsigned s: subgame) // if (w0[p].find(s) == w0[p].end()) // w0[!p].insert(s); subgame.insert(u.begin(), u.end()); if (w0[p].size() + u.size() == subgame.size()) { s[p] = std::move(strat_u); s[p].insert(s0[p].begin(), s0[p].end()); w[p].insert(subgame.begin(), subgame.end()); return; } // Recursion on game size. auto strat_wnp = attractor(arena, owner, subgame, w0[!p], max_parity, !p, false); for (unsigned s: w0[!p]) subgame.erase(s); region_t w1[2]; // Odd's winning region in the second recursive call. strategy_t s1[2]; // Odd's winning strategy in the second recursive call. solve_rec(arena, owner, subgame, max_parity, w1, s1); if (w1[0].size() + w1[1].size() != subgame.size()) throw std::runtime_error("size mismatch"); w[p] = std::move(w1[p]); s[p] = std::move(s1[p]); w[!p] = std::move(w1[!p]); w[!p].insert(w0[!p].begin(), w0[!p].end()); s[!p] = std::move(strat_wnp); s[!p].insert(s0[!p].begin(), s0[!p].end()); s[!p].insert(s1[!p].begin(), s1[!p].end()); subgame.insert(w0[!p].begin(), w0[!p].end()); } } // anonymous void pg_print(std::ostream& os, const const_twa_graph_ptr& arena) { auto owner = ensure_parity_game(arena, "pg_print"); unsigned ns = arena->num_states(); unsigned init = arena->get_init_state_number(); os << "parity " << ns - 1 << ";\n"; std::vector seen(ns, false); std::vector todo({init}); while (!todo.empty()) { unsigned src = todo.back(); todo.pop_back(); if (seen[src]) continue; seen[src] = true; os << src << ' '; os << arena->out(src).begin()->acc.max_set() - 1 << ' '; os << (*owner)[src] << ' '; bool first = true; for (auto& e: arena->out(src)) { if (!first) os << ','; first = false; os << e.dst; if (!seen[e.dst]) todo.push_back(e.dst); } if (src == init) os << " \"INIT\""; os << ";\n"; } } solved_game parity_game_solve(const const_twa_graph_ptr& arena) { solved_game result; result.arena = arena; const std::vector* 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, bool first_player, bool complete0) { auto um = arena->acc().unsat_mark(); if (!um.first) throw std::runtime_error("game winning condition is a tautology"); unsigned sink_env = 0; unsigned sink_con = 0; std::vector seen(arena->num_states(), false); unsigned init = arena->get_init_state_number(); std::vector todo({init}); auto owner = new std::vector(arena->num_states(), false); (*owner)[init] = first_player; while (!todo.empty()) { unsigned src = todo.back(); todo.pop_back(); seen[src] = true; bdd missing = bddtrue; for (const auto& e: arena->out(src)) { bool osrc = (*owner)[src]; if (complete0 && !osrc) missing -= e.cond; if (!seen[e.dst]) { (*owner)[e.dst] = !osrc; todo.push_back(e.dst); } else if ((*owner)[e.dst] == osrc) { delete owner; throw std::runtime_error("propagate_players(): odd cycle detected"); } } if (complete0 && !(*owner)[src] && missing != bddfalse) { if (sink_env == 0) { sink_env = arena->new_state(); sink_con = arena->new_state(); owner->push_back(true); owner->push_back(false); arena->new_edge(sink_con, sink_env, bddtrue, um.second); arena->new_edge(sink_env, sink_con, bddtrue, um.second); } arena->new_edge(src, sink_con, missing, um.second); } } arena->set_named_prop("state-player", owner); } twa_graph_ptr highlight_strategy(twa_graph_ptr& aut, const strategy_t& s, unsigned color) { unsigned ns = aut->num_states(); auto* highlight = aut->get_or_set_named_prop> ("highlight-edges"); for (auto [src, n]: s) { if (src >= ns) throw std::runtime_error ("highlight_strategy(): strategy refers to unexisting states"); unsigned int i = 0; for (auto& t: aut->out(src)) if (i++ == n) { (*highlight)[aut->edge_number(t)] = color; break; } } return aut; } twa_graph_ptr solved_game::highlight_strategy(unsigned player, unsigned color) { auto aut = std::const_pointer_cast(arena); auto* highlight = aut->get_or_set_named_prop> ("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); } }