diff --git a/spot/twaalgos/zlktree.cc b/spot/twaalgos/zlktree.cc index 93d7fae55..34c9505ff 100644 --- a/spot/twaalgos/zlktree.cc +++ b/spot/twaalgos/zlktree.cc @@ -20,13 +20,15 @@ #include "config.h" #include #include +#include +#include #include "spot/priv/bddalloc.hh" namespace spot { - zielonka_tree::zielonka_tree(acc_cond& cond) + zielonka_tree::zielonka_tree(const acc_cond& cond) { - acc_cond::acc_code& code = cond.get_acceptance(); + const acc_cond::acc_code& code = cond.get_acceptance(); auto used = code.used_sets(); unsigned c = used.count(); unsigned max = used.max_set(); @@ -225,4 +227,124 @@ namespace spot } return {branch, lvl}; } + + namespace + { + // A state in the zielonka_tree_transform output corresponds to a + // state in the input associated to a branch of the tree. + typedef std::pair zlk_state; + + struct zlk_state_hash + { + size_t + operator()(const zlk_state& s) const noexcept + { + return wang32_hash(s.first ^ wang32_hash(s.second)); + } + }; + } + + twa_graph_ptr + zielonka_tree_transform(const const_twa_graph_ptr& a) + { + auto res = make_twa_graph(a->get_dict()); + res->copy_ap_of(a); + zielonka_tree zlk(a->get_acceptance()); + + // Preserve determinism, weakness, and stutter-invariance + res->prop_copy(a, { false, true, true, true, true, true }); + + auto orig_states = new std::vector(); + auto branches = new std::vector(); + unsigned ns = a->num_states(); + orig_states->reserve(ns); // likely more are needed. + res->set_named_prop("original-states", orig_states); + res->set_named_prop("degen-levels", branches); + + // Associate each zlk_state to its number. + typedef std::unordered_map zs2num_map; + zs2num_map zs2num; + + // Queue of states to be processed. + std::deque todo; + + auto new_state = [&](zlk_state zs) + { + if (auto i = zs2num.find(zs); i != zs2num.end()) + return i->second; + + unsigned ns = res->new_state(); + zs2num[zs] = ns; + todo.emplace_back(zs); + + unsigned orig = zs.first; + assert(ns == orig_states->size()); + orig_states->emplace_back(orig); + branches->emplace_back(zs.second); + return ns; + }; + + zlk_state s(a->get_init_state_number(), zlk.first_branch()); + new_state(s); + unsigned max_color = 0; + while (!todo.empty()) + { + s = todo.front(); + todo.pop_front(); + int src = zs2num[s]; + unsigned branch = s.second; + + for (auto& i: a->out(s.first)) + { + auto [newbranch, prio] = zlk.step(branch, i.acc); + zlk_state d(i.dst, newbranch); + unsigned dst = new_state(d); + max_color = std::max(max_color, prio); + res->new_edge(src, dst, i.cond, {prio}); + } + } + + res->set_acceptance(max_color + 1, + acc_cond::acc_code::parity_min(!zlk.is_even(), + max_color + 1)); + + // compose original-states with the any previously existing one. + // We do that now, because for the bottommost copy below, it's better + // if we compose everything. + if (auto old_orig_states = + a->get_named_prop>("original-states")) + for (auto& s: *orig_states) + s = (*old_orig_states)[s]; + + // Now we will iterate over the SCCs in topological order to + // remember the "bottommost" SCCs that contain each original + // state. If an original state is duplicated in a higher SCC, + // it can be shunted away. Amen. + scc_info si_res(res, scc_info_options::TRACK_STATES); + unsigned res_scc_count = si_res.scc_count(); + unsigned maxorig = *std::max_element(orig_states->begin(), + orig_states->end()); + std::vector bottommost_occurence(maxorig + 1); + { + unsigned n = res_scc_count; + do + for (unsigned s: si_res.states_of(--n)) + bottommost_occurence[(*orig_states)[s]] = s; + while (n); + } + unsigned res_ns = res->num_states(); + std::vector retarget(res_ns); + for (unsigned n = 0; n < res_ns; ++n) + { + unsigned other = bottommost_occurence[(*orig_states)[n]]; + retarget[n] = + (si_res.scc_of(n) != si_res.scc_of(other)) ? other : n; + } + for (auto& e: res->edges()) + e.dst = retarget[e.dst]; + res->set_init_state(retarget[res->get_init_state_number()]); + res->purge_unreachable_states(); + + return res; + } } diff --git a/spot/twaalgos/zlktree.hh b/spot/twaalgos/zlktree.hh index 1552350b8..b67096f0c 100644 --- a/spot/twaalgos/zlktree.hh +++ b/spot/twaalgos/zlktree.hh @@ -37,7 +37,7 @@ namespace spot { public: /// \brief Build a Zielonka tree from the acceptance condition. - zielonka_tree(acc_cond& cond); + zielonka_tree(const acc_cond& cond); /// \brief The number of branches in the Zielonka tree. /// @@ -129,4 +129,17 @@ namespace spot bool has_streett_shape_ = true; }; + /// \ingroup twa_acc_transform + /// \brief Paritize an automaton using Zielonka tree. + /// + /// This corresponds to the application of Section 3 of + /// \cite casares.21.icalp + /// + /// The resulting automaton has a parity acceptance that is either + /// "min odd" or "min even", depending on the original acceptance. + /// It may uses up to n+1 colors if the input automaton has n + /// colors. Finally, it is colored, i.e., each output transition + /// has exactly one color. + SPOT_API + twa_graph_ptr zielonka_tree_transform(const const_twa_graph_ptr& aut); } diff --git a/tests/python/_zlktree.ipynb b/tests/python/_zlktree.ipynb index 15c3a8dbc..288f8d2f4 100644 --- a/tests/python/_zlktree.ipynb +++ b/tests/python/_zlktree.ipynb @@ -191,7 +191,7 @@ "\n" ], "text/plain": [ - " >" + " >" ] }, "metadata": {}, @@ -755,7 +755,7 @@ "\n" ], "text/plain": [ - " >" + " >" ] }, "metadata": {}, @@ -1111,7 +1111,7 @@ "\n" ], "text/plain": [ - " >" + " >" ] }, "metadata": {}, @@ -1247,7 +1247,7 @@ "\n" ], "text/plain": [ - " >" + " >" ] }, "metadata": {}, @@ -1358,7 +1358,7 @@ "\n" ], "text/plain": [ - " >" + " >" ] }, "metadata": {}, @@ -1421,7 +1421,7 @@ "\n" ], "text/plain": [ - " >" + " >" ] }, "metadata": {}, @@ -1484,7 +1484,7 @@ "\n" ], "text/plain": [ - " >" + " >" ] }, "metadata": {}, @@ -1511,10 +1511,741 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "id": "interesting-seller", "metadata": {}, "outputs": [], + "source": [ + "a = spot.automaton(\"\"\"HOA: v1 name: \"(FGp0 & ((XFp0 & F!p1) | F(Gp1 &\n", + "XG!p0))) | G(F!p0 & (XFp0 | F!p1) & F(Gp1 | G!p0))\" States: 14 Start:\n", + "0 AP: 2 \"p1\" \"p0\" Acceptance: 6 (Fin(0) & Fin(1)) | ((Fin(4)|Fin(5)) &\n", + "(Inf(2)&Inf(3))) properties: trans-labels explicit-labels trans-acc\n", + "complete properties: deterministic --BODY-- State: 0 [!0] 1 [0] 2\n", + "State: 1 [!0&!1] 1 {0 1 2 3 5} [0&!1] 3 [!0&1] 4 [0&1] 5 State: 2\n", + "[0&!1] 2 {1} [!0&1] 4 [!0&!1] 6 [0&1] 7 State: 3 [0&!1] 3 {1 3} [!0&1]\n", + "4 [!0&!1] 6 {0 1 2 3 5} [0&1] 8 State: 4 [!0&!1] 4 {1 2 3 5} [!0&1] 4\n", + "{2 4 5} [0&!1] 5 {1 3} [0&1] 5 {4} State: 5 [!0&1] 4 {2 4 5} [0&!1] 5\n", + "{1 3} [0&1] 8 {2 4} [!0&!1] 9 {1 2 3 5} State: 6 [0&!1] 3 {1 3} [!0&1]\n", + "4 [0&1] 5 [!0&!1] 10 State: 7 [!0&1] 4 [0&!1] 7 {1 3} [!0&!1] 11 [0&1]\n", + "12 {0 4} State: 8 [!0&1] 4 {2 4 5} [0&1] 5 {4} [0&!1] 8 {1 3} [!0&!1]\n", + "11 {1 3 5} State: 9 [!0&1] 4 {2 4 5} [0&!1] 5 {1 3} [0&1] 5 {4}\n", + "[!0&!1] 11 {1 3 5} State: 10 [!0&1] 4 [0&1] 8 [!0&!1] 10 {0 1 2 3 5}\n", + "[0&!1] 13 {1 2 3} State: 11 [!0&1] 4 {2 4 5} [0&!1] 8 {1 2 3} [0&1] 8\n", + "{2 4} [!0&!1] 11 {1 2 3 5} State: 12 [!0&1] 4 [0&1] 7 {0 2 4} [!0&!1]\n", + "9 [0&!1] 12 {1 3} State: 13 [!0&1] 4 [0&1] 5 [!0&!1] 10 {0 1 3 5}\n", + "[0&!1] 13 {1 3} --END--\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "informative-mainland", + "metadata": {}, + "outputs": [], + "source": [ + "b = spot.zielonka_tree_transform(a)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "angry-comedy", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "33" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "b.num_states()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "contained-combat", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.equivalent_to(b)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "paperback-handle", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "(Fin(\n", + "\n", + ") & (Fin(\n", + "\n", + ")|Fin(\n", + "\n", + ")) & (Inf(\n", + "\n", + ")&Inf(\n", + "\n", + "))) | Inf(\n", + "\n", + ")\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "0\n", + "\n", + "\n", + "\n", + "I->0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0->0\n", + "\n", + "\n", + "!p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0->0\n", + "\n", + "\n", + "!p0 & !p1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "p0 & !p1\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "1->0\n", + "\n", + "\n", + "!p0 & !p1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "1->1\n", + "\n", + "\n", + "p0 & !p1\n", + "\n", + "\n", + "\n", + "\n", + "1->1\n", + "\n", + "\n", + "!p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "1->1\n", + "\n", + "\n", + "p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + " *' at 0x7efe6a34b960> >" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "c = spot.automaton(\"\"\"\n", + "HOA: v1\n", + "States: 2\n", + "Start: 0\n", + "AP: 2 \"p1\" \"p0\"\n", + "Acceptance: 5 (Fin(0) & (Fin(3)|Fin(4)) & (Inf(1)&Inf(2))) | Inf(3)\n", + "properties: trans-labels explicit-labels trans-acc complete\n", + "properties: deterministic stutter-invariant\n", + "--BODY--\n", + "State: 0\n", + "[0&!1] 0 {2 3}\n", + "[!0&!1] 0 {2 3 4}\n", + "[!0&1] 1\n", + "[0&1] 1 {2 4}\n", + "State: 1\n", + "[!0&!1] 0 {0 2 3 4}\n", + "[!0&1] 1 {1}\n", + "[0&!1] 1 {2 3}\n", + "[0&1] 1 {1 2 4}\n", + "--END--\n", + "\"\"\"); c" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "tired-webcam", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inf(\n", + "\n", + ") | (Fin(\n", + "\n", + ") & (Inf(\n", + "\n", + ") | (Fin(\n", + "\n", + ") & Inf(\n", + "\n", + "))))\n", + "[parity min even 5]\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "0\n", + "\n", + "\n", + "\n", + "I->0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0->0\n", + "\n", + "\n", + "!p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "0->0\n", + "\n", + "\n", + "!p0 & !p1\n", + "\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "p0 & !p1\n", + "\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "1->0\n", + "\n", + "\n", + "!p0 & !p1\n", + "\n", + "\n", + "\n", + "\n", + "1->1\n", + "\n", + "\n", + "!p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "p0 & !p1\n", + "\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "2->0\n", + "\n", + "\n", + "!p0 & !p1\n", + "\n", + "\n", + "\n", + "\n", + "2->1\n", + "\n", + "\n", + "!p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "2->1\n", + "\n", + "\n", + "p0 & p1\n", + "\n", + "\n", + "\n", + "\n", + "2->2\n", + "\n", + "\n", + "p0 & !p1\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + " *' at 0x7efe6a34b540> >" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d = spot.zielonka_tree_transform(c); d" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "funny-taylor", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "c.equivalent_to(d)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "liable-update", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inf(\n", + "\n", + ") & (Fin(\n", + "\n", + ")|Fin(\n", + "\n", + "))\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "0\n", + "\n", + "\n", + "\n", + "I->0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0->0\n", + "\n", + "\n", + "!a & !b\n", + "\n", + "\n", + "\n", + "\n", + "0->0\n", + "\n", + "\n", + "a & !b\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "!a & b\n", + "\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "a & b\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "1->0\n", + "\n", + "\n", + "!a & !b\n", + "\n", + "\n", + "\n", + "1->0\n", + "\n", + "\n", + "a & !b\n", + "\n", + "\n", + "\n", + "\n", + "1->1\n", + "\n", + "\n", + "!a & b\n", + "\n", + "\n", + "\n", + "\n", + "1->1\n", + "\n", + "\n", + "a & b\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + " *' at 0x7efe6a301ed0> >" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Fin(\n", + "\n", + ") & (Inf(\n", + "\n", + ") | (Fin(\n", + "\n", + ") & Inf(\n", + "\n", + ")))\n", + "[parity min odd 4]\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "0\n", + "\n", + "\n", + "\n", + "I->0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0->0\n", + "\n", + "\n", + "!a & !b\n", + "\n", + "\n", + "\n", + "\n", + "0->0\n", + "\n", + "\n", + "a & !b\n", + "\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "!a & b\n", + "\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "a & b\n", + "\n", + "\n", + "\n", + "\n", + "1->1\n", + "\n", + "\n", + "!a & b\n", + "\n", + "\n", + "\n", + "\n", + "1->1\n", + "\n", + "\n", + "a & b\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "!a & !b\n", + "\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "a & !b\n", + "\n", + "\n", + "\n", + "\n", + "2->0\n", + "\n", + "\n", + "!a & !b\n", + "\n", + "\n", + "\n", + "\n", + "2->0\n", + "\n", + "\n", + "a & !b\n", + "\n", + "\n", + "\n", + "\n", + "2->1\n", + "\n", + "\n", + "!a & b\n", + "\n", + "\n", + "\n", + "\n", + "2->1\n", + "\n", + "\n", + "a & b\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + " *' at 0x7efe6a301720> >" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "e = spot.translate('!(GFa -> (GFb & GF(!b & !Xb)))', 'gen', 'det')\n", + "f = spot.zielonka_tree_transform(e)\n", + "display(e,f)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "complimentary-person", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "e.equivalent_to(f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "determined-bankruptcy", + "metadata": {}, + "outputs": [], "source": [] } ],