// -*- coding: utf-8 -*- // Copyright (C) 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 #include #include #include // Helper function/structures for split_2step namespace{ // Computes and stores the restriction // of each cond to the input domain and the support // This is useful as it avoids recomputation // and often many conditions are mapped to the same // restriction struct small_cacher_t { //e to e_in and support std::unordered_map, spot::bdd_hash> cond_hash_; void fill(const spot::const_twa_graph_ptr& aut, bdd output_bdd) { cond_hash_.reserve(aut->num_edges()/5+1); // 20% is about lowest number of different edge conditions // for benchmarks taken from syntcomp for (const auto& e : aut->edges()) { // Check if stored if (cond_hash_.find(e.cond) != cond_hash_.end()) continue; cond_hash_[e.cond] = std::pair( bdd_exist(e.cond, output_bdd), bdd_exist(bdd_support(e.cond), output_bdd)); } } // Get the condition restricted to input and support of a condition const std::pair& operator[](const bdd& econd) const { return cond_hash_.at(econd); } }; // Struct to locally store the information of all outgoing edges // of the state. struct e_info_t { e_info_t(const spot::twa_graph::edge_storage_t& e, const small_cacher_t& sm) : dst(e.dst), econd(e.cond), einsup(sm[e.cond]), acc(e.acc) { pre_hash = (spot::wang32_hash(dst) ^ std::hash()(acc)) * spot::fnv::prime; } inline size_t hash() const { return spot::wang32_hash(spot::bdd_hash()(econdout)) ^ pre_hash; } unsigned dst; bdd econd; mutable bdd econdout; std::pair einsup; spot::acc_cond::mark_t acc; size_t pre_hash; }; // We define a order between the edges to avoid creating multiple // states that in fact correspond to permutations of the order of the // outgoing edges struct less_info_t { // Note: orders via !econd! inline bool operator()(const e_info_t &lhs, const e_info_t &rhs) const { const int l_id = lhs.econd.id(); const int r_id = rhs.econd.id(); return std::tie(lhs.dst, lhs.acc, l_id) < std::tie(rhs.dst, rhs.acc, r_id); } }less_info; struct less_info_ptr_t { // Note: orders via !econdout! inline bool operator()(const e_info_t* lhs, const e_info_t* rhs)const { const int l_id = lhs->econdout.id(); const int r_id = rhs->econdout.id(); return std::tie(lhs->dst, lhs->acc, l_id) < std::tie(rhs->dst, rhs->acc, r_id); } }less_info_ptr; } namespace spot { twa_graph_ptr split_2step(const const_twa_graph_ptr& aut, const bdd& input_bdd, const bdd& output_bdd, bool complete_env, bool do_simplify) { auto split = make_twa_graph(aut->get_dict()); split->copy_ap_of(aut); split->copy_acceptance_of(aut); split->new_states(aut->num_states()); split->set_init_state(aut->get_init_state_number()); // The environment has all states // with num <= aut->num_states(); // So we can first loop over the aut // and then deduce the owner // a sort of hash-map for all new intermediate states std::unordered_multimap env_hash; env_hash.reserve((int) 1.5 * aut->num_states()); // a local map for edges leaving the current src // this avoids creating and then combining edges for each minterm // Note there are usually "few" edges leaving a state // and map has shown to be faster than unordered_map for // syntcomp examples std::map> env_edge_hash; typedef std::map>::mapped_type eeh_t; small_cacher_t small_cacher; small_cacher.fill(aut, output_bdd); // Cache vector for all outgoing edges of this states std::vector e_cache; // Vector of destinations actually reachable for a given // minterm in ins // Automatically "almost" sorted due to the sorting of e_cache std::vector dests; // If a completion is demanded we might have to create sinks // Sink controlled by player auto get_sink_con_state = [&split]() { static unsigned sink_con=0; if (SPOT_UNLIKELY(sink_con == 0)) sink_con = split->new_state(); return sink_con; }; // Loop over all states for (unsigned src = 0; src < aut->num_states(); ++src) { env_edge_hash.clear(); e_cache.clear(); auto out_cont = aut->out(src); // Short-cut if we do not want to // split the edges of nodes that have // a single outgoing edge if (do_simplify && (++out_cont.begin()) == out_cont.end()) { // "copy" the edge const auto& e = *out_cont.begin(); split->new_edge(src, e.dst, e.cond, e.acc); // Check if it needs to be completed if (complete_env) { bdd missing = bddtrue - bdd_exist(e.cond, output_bdd); if (missing != bddfalse) split->new_edge(src, get_sink_con_state(), missing); } // We are done for this state continue; } // Avoid looping over all minterms // we only loop over the minterms that actually exist bdd all_letters = bddfalse; bdd support = bddtrue; for (const auto& e : out_cont) { e_cache.emplace_back(e, small_cacher); all_letters |= e_cache.back().einsup.first; support &= e_cache.back().einsup.second; } // Complete for env if (complete_env && (all_letters != bddtrue)) split->new_edge(src, get_sink_con_state(), bddtrue - all_letters); // Sort to avoid that permutations of the same edges // get different intermediate states std::sort(e_cache.begin(), e_cache.end(), less_info); while (all_letters != bddfalse) { bdd one_letter = bdd_satoneset(all_letters, support, bddtrue); all_letters -= one_letter; dests.clear(); for (const auto& e_info : e_cache) // implies is faster than and if (bdd_implies(one_letter, e_info.einsup.first)) { e_info.econdout = bdd_appex(e_info.econd, one_letter, bddop_and, input_bdd); dests.push_back(&e_info); assert(e_info.econdout != bddfalse); } // By construction this should not be empty assert(!dests.empty()); // # dests is almost sorted -> insertion sort if (dests.size()>1) for (auto it = dests.begin(); it != dests.end(); ++it) std::rotate(std::upper_bound(dests.begin(), it, *it, less_info_ptr), it, it + 1); bool to_add = true; size_t h = fnv::init; for (const auto& t: dests) h ^= t->hash(); auto range_h = env_hash.equal_range(h); for (auto it_h = range_h.first; it_h != range_h.second; ++it_h) { unsigned i = it_h->second; auto out = split->out(i); if (std::equal(out.begin(), out.end(), dests.begin(), dests.end(), [](const twa_graph::edge_storage_t& x, const e_info_t* y) { return x.dst == y->dst && x.acc == y->acc && x.cond.id() == y->econdout.id(); })) { to_add = false; auto it = env_edge_hash.find(i); if (it != env_edge_hash.end()) it->second.second |= one_letter; else env_edge_hash.emplace(i, eeh_t(split->new_edge(src, i, bddtrue), one_letter)); break; } } if (to_add) { unsigned d = split->new_state(); unsigned n_e = split->new_edge(src, d, bddtrue); env_hash.emplace(h, d); env_edge_hash.emplace(d, eeh_t(n_e, one_letter)); for (const auto &t: dests) split->new_edge(d, t->dst, t->econdout, t->acc); } } // letters // save locally stored condition for (const auto& elem : env_edge_hash) split->edge_data(elem.second.first).cond = elem.second.second; } // v-src split->merge_edges(); split->prop_universal(spot::trival::maybe()); // The named property // compute the owners // env is equal to false std::vector* owner = new std::vector(split->num_states(), false); // All "new" states belong to the player std::fill(owner->begin()+aut->num_states(), owner->end(), true); split->set_named_prop("state-player", owner); // Done return split; } twa_graph_ptr unsplit_2step(const const_twa_graph_ptr& aut) { constexpr unsigned unseen_mark = std::numeric_limits::max(); twa_graph_ptr out = make_twa_graph(aut->get_dict()); out->copy_acceptance_of(aut); out->copy_ap_of(aut); // split_2step is not guaranteed to produce // states that alternate between env and player do to do_simplify auto owner_ptr = aut->get_named_prop>("state-player"); if (!owner_ptr) throw std::runtime_error("unsplit requires the named prop " "state-player as set by split_2step"); std::vector state_map(aut->num_states(), unseen_mark); auto seen = [&](unsigned s){return state_map[s] != unseen_mark; }; auto map_s = [&](unsigned s) { if (!seen(s)) state_map[s] = out->new_state(); return state_map[s]; }; std::deque todo; todo.push_back(aut->get_init_state_number()); map_s(aut->get_init_state_number()); while (!todo.empty()) { unsigned cur = todo.front(); unsigned cur_m = map_s(cur); todo.pop_front(); for (const auto& i : aut->out(cur)) { // if the dst is also owned env if (!(*owner_ptr)[i.dst]) { // This can only happen if there is only // one outgoing edges for cur assert(([&aut, cur]()->bool { auto out_cont = aut->out(cur); return (++(out_cont.begin()) == out_cont.end()); })()); if (!seen(i.dst)) todo.push_back(i.dst); out->new_edge(cur_m, map_s(i.dst), i.cond, i.acc); continue; // Done with cur } for (const auto& o : aut->out(i.dst)) { if (!seen(o.dst)) todo.push_back(o.dst); out->new_edge(cur_m, map_s(o.dst), i.cond & o.cond, i.acc | o.acc); } } } out->set_init_state(map_s(aut->get_init_state_number())); // Try to set outputs if (bdd* outptr = aut->get_named_prop("synthesis-outputs")) out->set_named_prop("synthesis-outputs", new bdd(*outptr)); return out; } spot::twa_graph_ptr apply_strategy(const spot::twa_graph_ptr& arena, bdd all_outputs, bool unsplit, bool keep_acc) { std::vector* w_ptr = arena->get_named_prop>("state-winner"); std::vector* s_ptr = arena->get_named_prop>("strategy"); std::vector* sp_ptr = arena->get_named_prop>("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"); std::vector& w = *w_ptr; std::vector& 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); constexpr unsigned unseen_mark = std::numeric_limits::max(); std::vector todo{arena->get_init_state_number()}; std::vector pg2aut(arena->num_states(), unseen_mark); aut->set_init_state(aut->new_state()); pg2aut[arena->get_init_state_number()] = aut->get_init_state_number(); while (!todo.empty()) { unsigned v = todo.back(); todo.pop_back(); // Check if a simplification occurred // in the aut and we have env -> env // Env edge -> keep all for (auto &e1: arena->out(v)) { assert(w.at(e1.dst)); // Check if a simplification occurred // in the aut and we have env -> env if (!(*sp_ptr)[e1.dst]) { assert(([&arena, v]() { auto out_cont = arena->out(v); return (++(out_cont.begin()) == out_cont.end()); })()); // If so we do not need to unsplit if (pg2aut[e1.dst] == unseen_mark) { pg2aut[e1.dst] = aut->new_state(); todo.push_back(e1.dst); } // Create the edge aut->new_edge(pg2aut[v], pg2aut[e1.dst], e1.cond, keep_acc ? e1.acc : spot::acc_cond::mark_t({})); // Done continue; } if (!unsplit) { 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); } aut->new_edge(unsplit ? pg2aut[v] : pg2aut[e1.dst], pg2aut[e2.dst], unsplit ? (e1.cond & e2.cond) : e2.cond, keep_acc ? e2.acc : spot::acc_cond::mark_t({})); } } 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) { const std::vector& sp_pg = * sp_ptr; std::vector sp_aut(aut->num_states()); std::vector 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(std::move(sp_aut))); aut->set_named_prop( "state-winner", new std::vector(aut->num_states(), true)); aut->set_named_prop( "strategy", new std::vector(std::move(str_aut))); } return aut; } } // spot