From 2ae9da1bc6922e9013ad7d10175e61d8dca4f208 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 10 Mar 2022 12:16:18 +0100 Subject: [PATCH 01/66] twagraph: merge_edges supports finite automata * spot/twa/twagraph.cc: don't remove false-labeled edges if the automaton uses state-based acceptance and the edge is a self loop --- spot/twa/twagraph.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/spot/twa/twagraph.cc b/spot/twa/twagraph.cc index 882714ab2..d422becb0 100644 --- a/spot/twa/twagraph.cc +++ b/spot/twa/twagraph.cc @@ -230,11 +230,15 @@ namespace spot // them. }); + bool is_state_acc = this->prop_state_acc().is_true(); + unsigned out = 0; unsigned in = 1; // Skip any leading false edge. - while (in < tend && trans[in].cond == bddfalse) + while (in < tend + && trans[in].cond == bddfalse + && (!is_state_acc || trans[in].src != trans[in].dst)) ++in; if (in < tend) { @@ -243,7 +247,9 @@ namespace spot trans[out] = trans[in]; for (++in; in < tend; ++in) { - if (trans[in].cond == bddfalse) // Unusable edge + if (trans[in].cond == bddfalse + && (!is_state_acc + || trans[in].src != trans[in].dst)) // Unusable edge continue; // Merge edges with the same source, destination, and // colors. (We test the source last, because this is the From 4a646e5aa0eeeb9527db0076a30b45877661226a Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 14 Jan 2022 08:56:28 +0100 Subject: [PATCH 02/66] tl: implement SERE derivation --- python/spot/impl.i | 2 + spot/tl/Makefile.am | 2 + spot/tl/derive.cc | 383 ++++++++++++++++++++++++++++++++++++++++++++ spot/tl/derive.hh | 43 +++++ 4 files changed, 430 insertions(+) create mode 100644 spot/tl/derive.cc create mode 100644 spot/tl/derive.hh diff --git a/python/spot/impl.i b/python/spot/impl.i index 2291730ed..09e29f6e9 100644 --- a/python/spot/impl.i +++ b/python/spot/impl.i @@ -86,6 +86,7 @@ #include #include +#include #include #include #include @@ -631,6 +632,7 @@ namespace std { %include %include +%include %include %include %include diff --git a/spot/tl/Makefile.am b/spot/tl/Makefile.am index 6c7650875..1e5a68363 100644 --- a/spot/tl/Makefile.am +++ b/spot/tl/Makefile.am @@ -28,6 +28,7 @@ tl_HEADERS = \ declenv.hh \ defaultenv.hh \ delta2.hh \ + derive.hh \ dot.hh \ environment.hh \ exclusive.hh \ @@ -54,6 +55,7 @@ libtl_la_SOURCES = \ declenv.cc \ defaultenv.cc \ delta2.cc \ + derive.cc \ dot.cc \ exclusive.cc \ formula.cc \ diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc new file mode 100644 index 000000000..cec4f3bcd --- /dev/null +++ b/spot/tl/derive.cc @@ -0,0 +1,383 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2021 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 + +namespace spot +{ + namespace + { + static std::vector + formula_aps(formula f) + { + auto res = std::unordered_set(); + + f.traverse([&res](formula f) + { + if (f.is(op::ap)) + { + res.insert(f.ap_name()); + return true; + } + + return false; + }); + + return std::vector(res.begin(), res.end()); + } + } + + twa_graph_ptr + derive_finite_automaton(formula f, bool deterministic) + { + auto bdd_dict = make_bdd_dict(); + auto aut = make_twa_graph(bdd_dict); + + aut->prop_state_acc(true); + const auto acc_mark = aut->set_buchi(); + + auto formula2state = robin_hood::unordered_map(); + + unsigned init_state = aut->new_state(); + aut->set_init_state(init_state); + + formula2state.insert({ f, init_state }); + + auto f_aps = formula_aps(f); + for (auto& ap : f_aps) + aut->register_ap(ap); + bdd all_aps = aut->ap_vars(); + + auto todo = std::vector>(); + todo.push_back({f, init_state}); + + auto state_names = new std::vector(); + std::ostringstream ss; + ss << f; + state_names->push_back(ss.str()); + + auto find_dst = [&](formula derivative) -> unsigned + { + unsigned dst; + auto it = formula2state.find(derivative); + if (it != formula2state.end()) + { + dst = it->second; + } + else + { + dst = aut->new_state(); + todo.push_back({derivative, dst}); + formula2state.insert({derivative, dst}); + std::ostringstream ss; + ss << derivative; + state_names->push_back(ss.str()); + } + + return dst; + }; + + while (!todo.empty()) + { + auto [curr_f, curr_state] = todo[todo.size() - 1]; + todo.pop_back(); + + auto curr_acc_mark = curr_f.accepts_eword() + ? acc_mark + : acc_cond::mark_t(); + + for (const bdd one : minterms_of(bddtrue, all_aps)) + { + formula derivative = + partial_derivation(curr_f, one, bdd_dict, aut.get()); + + // no transition possible from this letter + if (derivative.is(op::ff)) + continue; + + // either the formula isn't an OrRat, or if it is we consider it as + // as a whole to get a deterministic automaton + if (deterministic || !derivative.is(op::OrRat)) + { + auto dst = find_dst(derivative); + aut->new_edge(curr_state, dst, one, curr_acc_mark); + continue; + } + + // formula is an OrRat and we want a non deterministic automaton, + // so consider each child as a destination + for (const auto& subformula : derivative) + { + auto dst = find_dst(subformula); + aut->new_edge(curr_state, dst, one, curr_acc_mark); + } + } + + // if state has no transitions and should be accepting, create + // artificial transition + if (aut->get_graph().state_storage(curr_state).succ == 0 + && curr_f.accepts_eword()) + aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); + } + + aut->set_named_prop("state-names", state_names); + + aut->merge_edges(); + + return aut; + } + + twa_graph_ptr + derive_automaton(formula f, bool deterministic) + { + auto bdd_dict = make_bdd_dict(); + auto aut = make_twa_graph(bdd_dict); + + aut->prop_state_acc(true); + const auto acc_mark = aut->set_buchi(); + + auto formula2state = robin_hood::unordered_map(); + + unsigned init_state = aut->new_state(); + aut->set_init_state(init_state); + + formula2state.insert({ f, init_state }); + + auto f_aps = formula_aps(f); + for (auto& ap : f_aps) + aut->register_ap(ap); + bdd all_aps = aut->ap_vars(); + bdd alive = bdd_ithvar(aut->register_ap("alive")); + + auto todo = std::vector>(); + todo.push_back({f, init_state}); + + auto state_names = new std::vector(); + std::ostringstream ss; + ss << f; + state_names->push_back(ss.str()); + + auto find_dst = [&](formula derivative) -> unsigned + { + unsigned dst; + auto it = formula2state.find(derivative); + if (it != formula2state.end()) + { + dst = it->second; + } + else + { + dst = aut->new_state(); + todo.push_back({derivative, dst}); + formula2state.insert({derivative, dst}); + std::ostringstream ss; + ss << derivative; + state_names->push_back(ss.str()); + } + + return dst; + }; + + while (!todo.empty()) + { + auto [curr_f, curr_state] = todo[todo.size() - 1]; + todo.pop_back(); + + for (const bdd one : minterms_of(bddtrue, all_aps)) + { + formula derivative = + partial_derivation(curr_f, one, bdd_dict, aut.get()); + + // no transition possible from this letter + if (derivative.is(op::ff)) + continue; + + // either the formula isn't an OrRat, or if it is we consider it as + // a whole to get a deterministic automaton + if (deterministic || !derivative.is(op::OrRat)) + { + auto dst = find_dst(derivative); + aut->new_edge(curr_state, dst, one & alive); + continue; + } + + // formula is an OrRat and we want a non deterministic automaton, + // so consider each child as a destination + for (const auto& subformula : derivative) + { + auto dst = find_dst(subformula); + aut->new_edge(curr_state, dst, one & alive); + } + } + } + + unsigned end_state = aut->new_state(); + state_names->push_back("end"); + + for (const auto& [state_formula, state] : formula2state) + { + if (!state_formula.accepts_eword()) + continue; + + aut->new_edge(state, end_state, !alive); + } + + aut->new_edge(end_state, end_state, !alive, acc_mark); + + aut->set_named_prop("state-names", state_names); + + return aut; + } + + formula + partial_derivation(formula f, const bdd var, const bdd_dict_ptr& d, + void* owner) + { + if (f.is_boolean()) + { + auto f_bdd = formula_to_bdd(f, d, owner); + + if (bdd_implies(var, f_bdd)) + return formula::eword(); + + return formula::ff(); + } + + switch (f.kind()) + { + // handled by is_boolean above + case op::ff: + case op::tt: + case op::ap: + SPOT_UNREACHABLE(); + + case op::eword: + return formula::ff(); + + // d(E.F) = { d(E).F } U { c(E).d(F) } + case op::Concat: + { + formula E = f[0]; + formula F = f.all_but(0); + + auto res = + formula::Concat({ partial_derivation(E, var, d, owner), F }); + + if (E.accepts_eword()) + res = formula::OrRat( + { res, partial_derivation(F, var, d, owner) }); + + return res; + } + + // d(E*) = d(E).E* + // d(E[*i..j]) = d(E).E[*(i-1)..(j-1)] + case op::Star: + { + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + formula d_E = partial_derivation(f[0], var, d, owner); + + return formula::Concat({ d_E, formula::Star(f[0], min, max) }); + } + + // d(E[:*i..j]) = E:E[:*(i-1)..(j-1)] + (eword if i == 0 or c(d(E))) + case op::FStar: + { + formula E = f[0]; + + if (f.min() == 0 && f.max() == 0) + return formula::tt(); + + auto d_E = partial_derivation(E, var, d, owner); + + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto results = std::vector(); + + auto E_i_j_minus = formula::FStar(E, min, max); + results.push_back(formula::Fusion({ d_E, E_i_j_minus })); + + if (d_E.accepts_eword()) + results.push_back(d_E); + + if (f.min() == 0) + results.push_back(formula::eword()); + + return formula::OrRat(std::move(results)); + } + + // d(E && F) = d(E) && d(F) + // d(E + F) = {d(E)} U {d(F)} + case op::AndRat: + case op::OrRat: + { + std::vector subderivations; + for (auto subformula : f) + { + auto subderivation = + partial_derivation(subformula, var, d, owner); + subderivations.push_back(subderivation); + } + return formula::multop(f.kind(), std::move(subderivations)); + } + + // d(E:F) = {d(E):F} U {c(d(E)).d(F)} + case op::Fusion: + { + formula E = f[0]; + formula F = f.all_but(0); + + auto d_E = partial_derivation(E, var, d, owner); + auto res = formula::Fusion({ d_E, F }); + + if (d_E.accepts_eword()) + res = + formula::OrRat({ res, partial_derivation(F, var, d, owner) }); + + return res; + } + + case op::first_match: + { + formula E = f[0]; + auto d_E = partial_derivation(E, var, d, owner); + // if d_E.accepts_eword(), first_match(d_E) will return eword + return formula::first_match(d_E); + } + + default: + std::cerr << "unimplemented kind " + << static_cast(f.kind()) + << std::endl; + SPOT_UNIMPLEMENTED(); + } + return formula::ff(); + } +} diff --git a/spot/tl/derive.hh b/spot/tl/derive.hh new file mode 100644 index 000000000..410a24e37 --- /dev/null +++ b/spot/tl/derive.hh @@ -0,0 +1,43 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2021 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 . + +#pragma once + +#include + +#include + +#include +#include +#include + +namespace spot +{ + /// \ingroup tl_misc + /// \brief Produce a SERE formula's partial derivative + SPOT_API formula + partial_derivation(formula f, const bdd var, const bdd_dict_ptr& d, + void* owner); + + SPOT_API twa_graph_ptr + derive_automaton(formula f, bool deterministic = true); + + SPOT_API twa_graph_ptr + derive_finite_automaton(formula f, bool deterministic = true); +} From 175012b919f38c89b8275671578537b8acafbda4 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 10 Mar 2022 15:45:50 +0100 Subject: [PATCH 03/66] twaalgos: extract internal sere2dfa --- spot/twaalgos/ltl2tgba_fm.cc | 56 ++++++++++++++++++++++++++++++++++++ spot/twaalgos/ltl2tgba_fm.hh | 3 ++ 2 files changed, 59 insertions(+) diff --git a/spot/twaalgos/ltl2tgba_fm.cc b/spot/twaalgos/ltl2tgba_fm.cc index 09e88acae..a3f0f2aa3 100644 --- a/spot/twaalgos/ltl2tgba_fm.cc +++ b/spot/twaalgos/ltl2tgba_fm.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -2226,4 +2227,59 @@ namespace spot return a; } + twa_graph_ptr + sere_to_tgba(formula f, const bdd_dict_ptr& dict) + { + f = negative_normal_form(f); + + tl_simplifier* s = new tl_simplifier(dict); + twa_graph_ptr a = make_twa_graph(dict); + + translate_dict d(a, s, false, false, false); + ratexp_to_dfa sere2dfa(d); + + auto [dfa, namer, state] = sere2dfa.succ(f); + + // language was empty, build an automaton with one non accepting state + if (dfa == nullptr) + { + auto res = make_twa_graph(dict); + res->set_init_state(res->new_state()); + res->prop_universal(true); + res->prop_complete(false); + res->prop_stutter_invariant(true); + res->prop_terminal(true); + res->prop_state_acc(true); + return res; + } + + auto res = make_twa_graph(dfa, {false, false, true, false, false, false}); + + // HACK: translate_dict registers the atomic propositions in the "final" + // automaton that would be produced by a full translation, not in the + // intermediate automaton we're interested in. We can copy them from the + // resulting automaton. + res->copy_ap_of(a); + + res->prop_state_acc(true); + const auto acc_mark = res->set_buchi(); + + size_t sn = namer->state_to_name.size(); + for (size_t i = 0; i < sn; ++i) + { + formula g = namer->state_to_name[i]; + if (g.accepts_eword()) + { + if (res->get_graph().state_storage(i).succ == 0) + res->new_edge(i, i, bddfalse, acc_mark); + else + { + for (auto& e : res->out(i)) + e.acc = acc_mark; + } + } + } + + return res; + } } diff --git a/spot/twaalgos/ltl2tgba_fm.hh b/spot/twaalgos/ltl2tgba_fm.hh index 717ae6b1b..7dba4aee0 100644 --- a/spot/twaalgos/ltl2tgba_fm.hh +++ b/spot/twaalgos/ltl2tgba_fm.hh @@ -89,4 +89,7 @@ namespace spot bool unambiguous = false, const output_aborter* aborter = nullptr, bool label_with_ltl = false); + + SPOT_API twa_graph_ptr + sere_to_tgba(formula f, const bdd_dict_ptr& dict); } From 3b3ec16b20c343eb00b3053d0ff308f7410cd854 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 14 Mar 2022 15:17:51 +0100 Subject: [PATCH 04/66] twaalgos: add from_finite * spot/twaalgos/remprop.cc, spot/twaalgos/remprop.hh: add a from_finite function to perform the opposite operation to to_finite --- spot/twaalgos/remprop.cc | 47 ++++++++++++++++++++++++++++++++++++++++ spot/twaalgos/remprop.hh | 3 +++ 2 files changed, 50 insertions(+) diff --git a/spot/twaalgos/remprop.cc b/spot/twaalgos/remprop.cc index eb7c54dd0..092c1f50c 100644 --- a/spot/twaalgos/remprop.cc +++ b/spot/twaalgos/remprop.cc @@ -245,4 +245,51 @@ namespace spot } + twa_graph_ptr from_finite(const_twa_graph_ptr aut, const char* alive) + { + twa_graph_ptr res = + make_twa_graph(aut, + { true, false, true, false, false, false }); + + if (aut->get_named_prop>("state-names")) + res->copy_state_names_from(aut); + auto* names = res->get_named_prop>("state-names"); + + unsigned alive_sink = res->new_state(); + if (names != nullptr) + names->push_back("sink"); + auto acc = res->acc().all_sets(); + auto alive_bdd = bdd_ithvar(res->register_ap(alive)); + res->new_edge(alive_sink, alive_sink, !alive_bdd, acc); + + unsigned ns = res->num_states(); + for (unsigned s = 0; s < ns; ++s) + { + if (s == alive_sink) + continue; + + bool was_acc = res->state_is_accepting(s); + + // erase accepting marks, require alive on non-accepting transition, + // and remove self-loop edges used to mark acceptance + auto i = res->out_iteraser(s); + while (i) + { + if (i->src == i->dst && i->cond == bddfalse) + { + i.erase(); + continue; + } + + i->cond &= alive_bdd; + i->acc = {}; + ++i; + } + + if (was_acc) + res->new_edge(s, alive_sink, !alive_bdd); + } + + return res; + } } diff --git a/spot/twaalgos/remprop.hh b/spot/twaalgos/remprop.hh index ab234fed9..4121d6554 100644 --- a/spot/twaalgos/remprop.hh +++ b/spot/twaalgos/remprop.hh @@ -53,5 +53,8 @@ namespace spot SPOT_API twa_graph_ptr to_finite(const_twa_graph_ptr aut, const char* alive = "alive"); + /// \brief The opposite of the to_finite operation + SPOT_API twa_graph_ptr + from_finite(const_twa_graph_ptr aut, const char* alive = "alive"); } From 2df8e200d8bcd19584bb997aa72a4206a83de485 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 15 Mar 2022 17:06:05 +0100 Subject: [PATCH 05/66] derive: use from_finite --- spot/tl/derive.cc | 106 +++++----------------------------------------- 1 file changed, 11 insertions(+), 95 deletions(-) diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc index cec4f3bcd..9def9e2eb 100644 --- a/spot/tl/derive.cc +++ b/spot/tl/derive.cc @@ -22,6 +22,7 @@ #include #include #include +#include namespace spot { @@ -140,6 +141,12 @@ namespace spot aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); } + // if we only have an initial state with no transitions, then our language + // is empty + if (aut->num_states() == 1 + && aut->get_graph().state_storage(aut->get_init_state_number()).succ == 0) + return nullptr; + aut->set_named_prop("state-names", state_names); aut->merge_edges(); @@ -150,103 +157,12 @@ namespace spot twa_graph_ptr derive_automaton(formula f, bool deterministic) { - auto bdd_dict = make_bdd_dict(); - auto aut = make_twa_graph(bdd_dict); + auto finite = derive_finite_automaton(f, deterministic); - aut->prop_state_acc(true); - const auto acc_mark = aut->set_buchi(); + if (finite == nullptr) + return nullptr; - auto formula2state = robin_hood::unordered_map(); - - unsigned init_state = aut->new_state(); - aut->set_init_state(init_state); - - formula2state.insert({ f, init_state }); - - auto f_aps = formula_aps(f); - for (auto& ap : f_aps) - aut->register_ap(ap); - bdd all_aps = aut->ap_vars(); - bdd alive = bdd_ithvar(aut->register_ap("alive")); - - auto todo = std::vector>(); - todo.push_back({f, init_state}); - - auto state_names = new std::vector(); - std::ostringstream ss; - ss << f; - state_names->push_back(ss.str()); - - auto find_dst = [&](formula derivative) -> unsigned - { - unsigned dst; - auto it = formula2state.find(derivative); - if (it != formula2state.end()) - { - dst = it->second; - } - else - { - dst = aut->new_state(); - todo.push_back({derivative, dst}); - formula2state.insert({derivative, dst}); - std::ostringstream ss; - ss << derivative; - state_names->push_back(ss.str()); - } - - return dst; - }; - - while (!todo.empty()) - { - auto [curr_f, curr_state] = todo[todo.size() - 1]; - todo.pop_back(); - - for (const bdd one : minterms_of(bddtrue, all_aps)) - { - formula derivative = - partial_derivation(curr_f, one, bdd_dict, aut.get()); - - // no transition possible from this letter - if (derivative.is(op::ff)) - continue; - - // either the formula isn't an OrRat, or if it is we consider it as - // a whole to get a deterministic automaton - if (deterministic || !derivative.is(op::OrRat)) - { - auto dst = find_dst(derivative); - aut->new_edge(curr_state, dst, one & alive); - continue; - } - - // formula is an OrRat and we want a non deterministic automaton, - // so consider each child as a destination - for (const auto& subformula : derivative) - { - auto dst = find_dst(subformula); - aut->new_edge(curr_state, dst, one & alive); - } - } - } - - unsigned end_state = aut->new_state(); - state_names->push_back("end"); - - for (const auto& [state_formula, state] : formula2state) - { - if (!state_formula.accepts_eword()) - continue; - - aut->new_edge(state, end_state, !alive); - } - - aut->new_edge(end_state, end_state, !alive, acc_mark); - - aut->set_named_prop("state-names", state_names); - - return aut; + return from_finite(finite); } formula From 5b3b292b1064beb211a48bbbb1c2de932b7f7266 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 18 Mar 2022 18:05:53 +0100 Subject: [PATCH 06/66] derive: no nullptr handling --- spot/tl/derive.cc | 9 --------- 1 file changed, 9 deletions(-) diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc index 9def9e2eb..699c8634f 100644 --- a/spot/tl/derive.cc +++ b/spot/tl/derive.cc @@ -141,12 +141,6 @@ namespace spot aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); } - // if we only have an initial state with no transitions, then our language - // is empty - if (aut->num_states() == 1 - && aut->get_graph().state_storage(aut->get_init_state_number()).succ == 0) - return nullptr; - aut->set_named_prop("state-names", state_names); aut->merge_edges(); @@ -159,9 +153,6 @@ namespace spot { auto finite = derive_finite_automaton(f, deterministic); - if (finite == nullptr) - return nullptr; - return from_finite(finite); } From eea35cdb31df47e565cbb8a675cc0afa441ad6eb Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 18 Mar 2022 19:27:19 +0100 Subject: [PATCH 07/66] derive: extract AndNLM rewriting --- spot/tl/derive.cc | 55 ++++++++++++++++++++++++++++++++++++ spot/tl/derive.hh | 3 ++ spot/twaalgos/ltl2tgba_fm.cc | 51 ++------------------------------- 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc index 699c8634f..a24fbac53 100644 --- a/spot/tl/derive.cc +++ b/spot/tl/derive.cc @@ -48,6 +48,61 @@ namespace spot } } + formula + rewrite_and_nlm(formula f) + { + unsigned s = f.size(); + std::vector final; + std::vector non_final; + + for (auto g: f) + if (g.accepts_eword()) + final.emplace_back(g); + else + non_final.emplace_back(g); + + if (non_final.empty()) + // (a* & b*);c = (a*|b*);c + return formula::OrRat(std::move(final)); + if (!final.empty()) + { + // let F_i be final formulae + // N_i be non final formula + // (F_1 & ... & F_n & N_1 & ... & N_m) + // = (F_1 | ... | F_n);[*] && (N_1 & ... & N_m) + // | (F_1 | ... | F_n) && (N_1 & ... & N_m);[*] + formula f = formula::OrRat(std::move(final)); + formula n = formula::AndNLM(std::move(non_final)); + formula t = formula::one_star(); + formula ft = formula::Concat({f, t}); + formula nt = formula::Concat({n, t}); + formula ftn = formula::AndRat({ft, n}); + formula fnt = formula::AndRat({f, nt}); + return formula::OrRat({ftn, fnt}); + } + // No final formula. + // Translate N_1 & N_2 & ... & N_n into + // N_1 && (N_2;[*]) && ... && (N_n;[*]) + // | (N_1;[*]) && N_2 && ... && (N_n;[*]) + // | (N_1;[*]) && (N_2;[*]) && ... && N_n + formula star = formula::one_star(); + std::vector disj; + for (unsigned n = 0; n < s; ++n) + { + std::vector conj; + for (unsigned m = 0; m < s; ++m) + { + formula g = f[m]; + if (n != m) + g = formula::Concat({g, star}); + conj.emplace_back(g); + } + disj.emplace_back(formula::AndRat(std::move(conj))); + } + return formula::OrRat(std::move(disj)); + } + + twa_graph_ptr derive_finite_automaton(formula f, bool deterministic) { diff --git a/spot/tl/derive.hh b/spot/tl/derive.hh index 410a24e37..247f85b59 100644 --- a/spot/tl/derive.hh +++ b/spot/tl/derive.hh @@ -40,4 +40,7 @@ namespace spot SPOT_API twa_graph_ptr derive_finite_automaton(formula f, bool deterministic = true); + + SPOT_API formula + rewrite_and_nlm(formula f); } diff --git a/spot/twaalgos/ltl2tgba_fm.cc b/spot/twaalgos/ltl2tgba_fm.cc index a3f0f2aa3..9c7674b0f 100644 --- a/spot/twaalgos/ltl2tgba_fm.cc +++ b/spot/twaalgos/ltl2tgba_fm.cc @@ -19,6 +19,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -748,55 +749,7 @@ namespace spot SPOT_UNREACHABLE(); case op::AndNLM: { - unsigned s = f.size(); - vec final; - vec non_final; - - for (auto g: f) - if (g.accepts_eword()) - final.emplace_back(g); - else - non_final.emplace_back(g); - - if (non_final.empty()) - // (a* & b*);c = (a*|b*);c - return recurse_and_concat(formula::OrRat(std::move(final))); - if (!final.empty()) - { - // let F_i be final formulae - // N_i be non final formula - // (F_1 & ... & F_n & N_1 & ... & N_m) - // = (F_1 | ... | F_n);[*] && (N_1 & ... & N_m) - // | (F_1 | ... | F_n) && (N_1 & ... & N_m);[*] - formula f = formula::OrRat(std::move(final)); - formula n = formula::AndNLM(std::move(non_final)); - formula t = formula::one_star(); - formula ft = formula::Concat({f, t}); - formula nt = formula::Concat({n, t}); - formula ftn = formula::AndRat({ft, n}); - formula fnt = formula::AndRat({f, nt}); - return recurse_and_concat(formula::OrRat({ftn, fnt})); - } - // No final formula. - // Translate N_1 & N_2 & ... & N_n into - // N_1 && (N_2;[*]) && ... && (N_n;[*]) - // | (N_1;[*]) && N_2 && ... && (N_n;[*]) - // | (N_1;[*]) && (N_2;[*]) && ... && N_n - formula star = formula::one_star(); - vec disj; - for (unsigned n = 0; n < s; ++n) - { - vec conj; - for (unsigned m = 0; m < s; ++m) - { - formula g = f[m]; - if (n != m) - g = formula::Concat({g, star}); - conj.emplace_back(g); - } - disj.emplace_back(formula::AndRat(std::move(conj))); - } - return recurse_and_concat(formula::OrRat(std::move(disj))); + return recurse_and_concat(rewrite_and_nlm(f)); } case op::AndRat: { From 1925910f4a6d4b2d88c76a4d3fb207f84dc24aaa Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 18 Mar 2022 19:27:37 +0100 Subject: [PATCH 08/66] derive: handle AndNLM --- spot/tl/derive.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc index a24fbac53..3180d4815 100644 --- a/spot/tl/derive.cc +++ b/spot/tl/derive.cc @@ -310,6 +310,12 @@ namespace spot return formula::multop(f.kind(), std::move(subderivations)); } + case op::AndNLM: + { + formula rewrite = rewrite_and_nlm(f); + return partial_derivation(rewrite, var, d, owner); + } + // d(E:F) = {d(E):F} U {c(d(E)).d(F)} case op::Fusion: { From a046a4983cf56842b6eb733e7e6847ad8cfdd068 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 30 Mar 2022 21:52:34 +0200 Subject: [PATCH 09/66] derive: use first --- spot/tl/derive.cc | 207 ++++++++++++++++++++++++++++++++++++++++++++++ spot/tl/derive.hh | 6 ++ 2 files changed, 213 insertions(+) diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc index 3180d4815..2b1873ed2 100644 --- a/spot/tl/derive.cc +++ b/spot/tl/derive.cc @@ -28,6 +28,106 @@ namespace spot { namespace { + static std::pair + first(formula f, const bdd_dict_ptr& d, void* owner) + { + if (f.is_boolean()) + { + bdd res = formula_to_bdd(f, d, owner); + return { res, bdd_support(res) }; + } + + switch (f.kind()) + { + // handled by is_boolean above + case op::ff: + case op::tt: + case op::ap: + case op::And: + case op::Or: + SPOT_UNREACHABLE(); + + case op::eword: + return { bddfalse, bddtrue }; + + case op::OrRat: + { + bdd res = bddfalse; + bdd support = bddtrue; + for (auto subformula : f) + { + auto [r, sup] = first(subformula, d, owner); + res |= r; + support &= sup; + } + return { res, support }; + } + + case op::AndRat: + { + bdd res = bddtrue; + bdd support = bddtrue; + for (auto subformula : f) + { + auto [r, sup] = first(subformula, d, owner); + res &= r; + support &= sup; + } + return { res, support }; + } + + case op::AndNLM: + return first(rewrite_and_nlm(f), d, owner); + + case op::Concat: + { + auto [res, support] = first(f[0], d, owner); + + if (f[0].accepts_eword()) + { + auto [r, sup] = first(f.all_but(0), d, owner); + res |= r; + support &= sup; + } + + return { res, support }; + } + + case op::Fusion: + { + auto [res, support] = first(f[0], d, owner); + + // this should be computed only if f[0] recognizes words of size 1 + // or accepts eword ? + auto p = first(f.all_but(0), d, owner); + + return { res, support & p.second }; + } + + case op::Star: + case op::first_match: + return first(f[0], d, owner); + + case op::FStar: + { + auto [res, support] = first(f[0], d, owner); + + if (f.min() == 0) + res = bddtrue; + + return { res, support }; + } + + default: + std::cerr << "unimplemented kind " + << static_cast(f.kind()) + << std::endl; + SPOT_UNIMPLEMENTED(); + } + + return { bddfalse, bddtrue }; + } + static std::vector formula_aps(formula f) { @@ -102,6 +202,105 @@ namespace spot return formula::OrRat(std::move(disj)); } + twa_graph_ptr + derive_finite_automaton_with_first(formula f, bool deterministic) + { + auto bdd_dict = make_bdd_dict(); + auto aut = make_twa_graph(bdd_dict); + + aut->prop_state_acc(true); + const auto acc_mark = aut->set_buchi(); + + auto formula2state = robin_hood::unordered_map(); + + unsigned init_state = aut->new_state(); + aut->set_init_state(init_state); + + formula2state.insert({ f, init_state }); + + auto f_aps = formula_aps(f); + for (auto& ap : f_aps) + aut->register_ap(ap); + + auto todo = std::vector>(); + todo.push_back({f, init_state}); + + auto state_names = new std::vector(); + std::ostringstream ss; + ss << f; + state_names->push_back(ss.str()); + + auto find_dst = [&](formula derivative) -> unsigned + { + unsigned dst; + auto it = formula2state.find(derivative); + if (it != formula2state.end()) + { + dst = it->second; + } + else + { + dst = aut->new_state(); + todo.push_back({derivative, dst}); + formula2state.insert({derivative, dst}); + std::ostringstream ss; + ss << derivative; + state_names->push_back(ss.str()); + } + + return dst; + }; + + while (!todo.empty()) + { + auto [curr_f, curr_state] = todo[todo.size() - 1]; + todo.pop_back(); + + auto curr_acc_mark = curr_f.accepts_eword() + ? acc_mark + : acc_cond::mark_t(); + + auto [firsts, firsts_support] = first(curr_f, bdd_dict, aut.get()); + for (const bdd one : minterms_of(firsts, firsts_support)) + { + formula derivative = + partial_derivation(curr_f, one, bdd_dict, aut.get()); + + // no transition possible from this letter + if (derivative.is(op::ff)) + continue; + + // either the formula isn't an OrRat, or if it is we consider it as + // as a whole to get a deterministic automaton + if (deterministic || !derivative.is(op::OrRat)) + { + auto dst = find_dst(derivative); + aut->new_edge(curr_state, dst, one, curr_acc_mark); + continue; + } + + // formula is an OrRat and we want a non deterministic automaton, + // so consider each child as a destination + for (const auto& subformula : derivative) + { + auto dst = find_dst(subformula); + aut->new_edge(curr_state, dst, one, curr_acc_mark); + } + } + + // if state has no transitions and should be accepting, create + // artificial transition + if (aut->get_graph().state_storage(curr_state).succ == 0 + && curr_f.accepts_eword()) + aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); + } + + aut->set_named_prop("state-names", state_names); + + aut->merge_edges(); + + return aut; + } twa_graph_ptr derive_finite_automaton(formula f, bool deterministic) @@ -203,6 +402,14 @@ namespace spot return aut; } + twa_graph_ptr + derive_automaton_with_first(formula f, bool deterministic) + { + auto finite = derive_finite_automaton_with_first(f, deterministic); + + return from_finite(finite); + } + twa_graph_ptr derive_automaton(formula f, bool deterministic) { diff --git a/spot/tl/derive.hh b/spot/tl/derive.hh index 247f85b59..1947951ed 100644 --- a/spot/tl/derive.hh +++ b/spot/tl/derive.hh @@ -38,9 +38,15 @@ namespace spot SPOT_API twa_graph_ptr derive_automaton(formula f, bool deterministic = true); + SPOT_API twa_graph_ptr + derive_automaton_with_first(formula f, bool deterministic = true); + SPOT_API twa_graph_ptr derive_finite_automaton(formula f, bool deterministic = true); + SPOT_API twa_graph_ptr + derive_finite_automaton_with_first(formula f, bool deterministic = true); + SPOT_API formula rewrite_and_nlm(formula f); } From 00ad02070b0d410b4f4bb858d961c7edadc03b1c Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 7 Jul 2022 16:38:33 +0200 Subject: [PATCH 10/66] graph: filter accepting sinks in univ_dest_mapper --- spot/graph/graph.hh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spot/graph/graph.hh b/spot/graph/graph.hh index 3b43f751b..16d117ffc 100644 --- a/spot/graph/graph.hh +++ b/spot/graph/graph.hh @@ -557,10 +557,11 @@ namespace spot { std::map, unsigned> uniq_; G& g_; + unsigned acc_sink_; public: - univ_dest_mapper(G& graph) - : g_(graph) + univ_dest_mapper(G& graph, unsigned sink = -1u) + : g_(graph), acc_sink_(sink) { } @@ -570,6 +571,9 @@ namespace spot std::vector tmp(begin, end); std::sort(tmp.begin(), tmp.end()); tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end()); + if (acc_sink_ != -1u && tmp.size() > 1) + tmp.erase(std::remove(tmp.begin(), tmp.end(), acc_sink_), + tmp.end()); auto p = uniq_.emplace(tmp, 0); if (p.second) p.first->second = g_.new_univ_dests(tmp.begin(), tmp.end()); From 6ebbb930240e5411c43ec5a0665d52d80235def9 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 7 Jul 2022 16:40:41 +0200 Subject: [PATCH 11/66] twaalgos: filter accepting sinks in oe combiner --- spot/twaalgos/alternation.cc | 21 ++++++++++++++++++--- spot/twaalgos/alternation.hh | 3 ++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/spot/twaalgos/alternation.cc b/spot/twaalgos/alternation.cc index c7b2a17d5..d5d59a961 100644 --- a/spot/twaalgos/alternation.cc +++ b/spot/twaalgos/alternation.cc @@ -28,8 +28,8 @@ namespace spot { - outedge_combiner::outedge_combiner(const twa_graph_ptr& aut) - : aut_(aut), vars_(bddtrue) + outedge_combiner::outedge_combiner(const twa_graph_ptr& aut, unsigned sink) + : aut_(aut), vars_(bddtrue), acc_sink_(sink) { } @@ -50,6 +50,9 @@ namespace spot bdd out = bddtrue; for (unsigned d: aut_->univ_dests(e.dst)) { + if (d == acc_sink_) + continue; + auto p = state_to_var.emplace(d, 0); if (p.second) { @@ -78,7 +81,17 @@ namespace spot { bdd cond = bdd_exist(cube, vars_); bdd dest = bdd_existcomp(cube, vars_); - while (dest != bddtrue) + + if (dest == bddtrue) + { + // if dest is bddtrue then the accepting sink is the only + // destination for this edge, in that case don't filter it out + assert(acc_sink_ != -1u); + aut_->new_edge(st, acc_sink_, cond); + continue; + } + + do { assert(bdd_low(dest) == bddfalse); auto it = var_to_state.find(bdd_var(dest)); @@ -86,6 +99,8 @@ namespace spot univ_dest.push_back(it->second); dest = bdd_high(dest); } + while (dest != bddtrue); + std::sort(univ_dest.begin(), univ_dest.end()); aut_->new_univ_edge(st, univ_dest.begin(), univ_dest.end(), cond); univ_dest.clear(); diff --git a/spot/twaalgos/alternation.hh b/spot/twaalgos/alternation.hh index a03ddc121..9490272a1 100644 --- a/spot/twaalgos/alternation.hh +++ b/spot/twaalgos/alternation.hh @@ -49,8 +49,9 @@ namespace spot std::map state_to_var; std::map var_to_state; bdd vars_; + unsigned acc_sink_; public: - outedge_combiner(const twa_graph_ptr& aut); + outedge_combiner(const twa_graph_ptr& aut, unsigned sink = -1u); ~outedge_combiner(); bdd operator()(unsigned st); void new_dests(unsigned st, bdd out) const; From e4bfebf36f5ce6b9d44ad1fe03d389d63ebc62de Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 21 Jun 2022 13:54:32 +0200 Subject: [PATCH 12/66] twaalgos: add LTL to AA translation --- python/spot/impl.i | 2 + spot/twaalgos/Makefile.am | 2 + spot/twaalgos/translate_aa.cc | 320 ++++++++++++++++++++++++++++++++++ spot/twaalgos/translate_aa.hh | 32 ++++ 4 files changed, 356 insertions(+) create mode 100644 spot/twaalgos/translate_aa.cc create mode 100644 spot/twaalgos/translate_aa.hh diff --git a/python/spot/impl.i b/python/spot/impl.i index 09e29f6e9..471b85cbb 100644 --- a/python/spot/impl.i +++ b/python/spot/impl.i @@ -163,6 +163,7 @@ #include #include #include +#include #include #include #include @@ -790,6 +791,7 @@ def state_is_accepting(self, src) -> "bool": %include %include %include +%include %include %include %include diff --git a/spot/twaalgos/Makefile.am b/spot/twaalgos/Makefile.am index 8e5b929d0..432f1a85d 100644 --- a/spot/twaalgos/Makefile.am +++ b/spot/twaalgos/Makefile.am @@ -99,6 +99,7 @@ twaalgos_HEADERS = \ totgba.hh \ toweak.hh \ translate.hh \ + translate_aa.hh \ word.hh \ zlktree.hh @@ -177,6 +178,7 @@ libtwaalgos_la_SOURCES = \ totgba.cc \ toweak.cc \ translate.cc \ + translate_aa.cc \ word.cc \ zlktree.cc diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc new file mode 100644 index 000000000..0663651de --- /dev/null +++ b/spot/twaalgos/translate_aa.cc @@ -0,0 +1,320 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2013-2018, 2020-2021 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 + +namespace spot +{ + namespace + { + struct ltl_to_aa_builder + { + ltl_to_aa_builder(twa_graph_ptr aut, unsigned accepting_sink) + : aut_(aut) + , accepting_sink_(accepting_sink) + , uniq_(aut_->get_graph(), accepting_sink) + , oe_(aut_, accepting_sink) + { + } + + twa_graph_ptr aut_; + unsigned accepting_sink_; + internal::univ_dest_mapper uniq_; + outedge_combiner oe_; + + unsigned recurse(formula f) + { + switch (f.kind()) + { + case op::ff: + return aut_->new_state(); + + case op::tt: + { + unsigned init_state = aut_->new_state(); + aut_->new_edge(init_state, accepting_sink_, bddtrue, {}); + return init_state; + } + + case op::ap: + case op::Not: + { + unsigned init_state = aut_->new_state(); + + bdd ap; + if (f.kind() == op::ap) + ap = bdd_ithvar(aut_->register_ap(f.ap_name())); + else + ap = bdd_nithvar(aut_->register_ap(f[0].ap_name())); + + aut_->new_edge(init_state, accepting_sink_, ap, {}); + return init_state; + } + + // FIXME: is this right for LTLf? + case op::strong_X: + case op::X: + { + unsigned sub_init_state = recurse(f[0]); + unsigned new_init_state = aut_->new_state(); + aut_->new_edge(new_init_state, sub_init_state, bddtrue, {}); + return new_init_state; + } + + case op::Or: + { + unsigned init_state = aut_->new_state(); + + for (const auto& sub_formula : f) + { + unsigned sub_init = recurse(sub_formula); + for (auto& e : aut_->out(sub_init)) + { + unsigned dst = e.dst; + if (aut_->is_univ_dest(e.dst)) + { + auto dests = aut_->univ_dests(e); + dst = uniq_.new_univ_dests(dests.begin(), dests.end()); + } + aut_->new_edge(init_state, dst, e.cond, {}); + } + } + + return init_state; + } + + case op::And: + { + unsigned init_state = aut_->new_state(); + + outedge_combiner oe(aut_, accepting_sink_); + bdd comb = bddtrue; + for (const auto& sub_formula : f) + { + unsigned sub_init = recurse(sub_formula); + comb &= oe_(sub_init); + } + oe_.new_dests(init_state, comb); + + return init_state; + } + + case op::U: + case op::W: + { + auto acc = f.kind() == op::U + ? acc_cond::mark_t{0} + : acc_cond::mark_t{}; + + unsigned init_state = aut_->new_state(); + + unsigned lhs_init = recurse(f[0]); + unsigned rhs_init = recurse(f[1]); + + std::vector new_dests; + for (auto& e : aut_->out(lhs_init)) + { + auto dests = aut_->univ_dests(e); + std::copy(dests.begin(), dests.end(), + std::back_inserter(new_dests)); + new_dests.push_back(init_state); + + unsigned dest = uniq_.new_univ_dests(new_dests.begin(), + new_dests.end()); + aut_->new_edge(init_state, dest, e.cond, acc); + + new_dests.clear(); + } + + for (auto& e : aut_->out(rhs_init)) + { + unsigned dst = e.dst; + if (aut_->is_univ_dest(e.dst)) + { + auto dests = aut_->univ_dests(e); + dst = uniq_.new_univ_dests(dests.begin(), dests.end()); + } + aut_->new_edge(init_state, dst, e.cond, {}); + } + + return init_state; + } + + case op::R: + case op::M: + { + auto acc = f.kind() == op::M + ? acc_cond::mark_t{0} + : acc_cond::mark_t{}; + + unsigned init_state = aut_->new_state(); + + unsigned lhs_init = recurse(f[0]); + unsigned rhs_init = recurse(f[1]); + + std::vector new_dests; + for (auto& e : aut_->out(rhs_init)) + { + auto dests = aut_->univ_dests(e); + std::copy(dests.begin(), dests.end(), + std::back_inserter(new_dests)); + new_dests.push_back(init_state); + + unsigned dst = uniq_.new_univ_dests(new_dests.begin(), + new_dests.end()); + aut_->new_edge(init_state, dst, e.cond, acc); + + new_dests.clear(); + } + + std::vector dsts; + for (const auto& lhs_e : aut_->out(lhs_init)) + { + const auto& lhs_dsts = aut_->univ_dests(lhs_e); + std::copy(lhs_dsts.begin(), lhs_dsts.end(), + std::back_inserter(dsts)); + size_t lhs_dest_num = dsts.size(); + + for (const auto& rhs_e : aut_->out(rhs_init)) + { + const auto& rhs_dsts = aut_->univ_dests(rhs_e); + std::copy(rhs_dsts.begin(), rhs_dsts.end(), + std::back_inserter(dsts)); + + bdd cond = lhs_e.cond & rhs_e.cond; + + unsigned dst = uniq_.new_univ_dests(dsts.begin(), + dsts.end()); + aut_->new_edge(init_state, dst, cond, {}); + + // reset to only lhs' current edge destinations + dsts.resize(lhs_dest_num); + } + dsts.clear(); + } + + return init_state; + } + + // F(phi) = tt U phi + case op::F: + { + auto acc = acc_cond::mark_t{0}; + + // if phi is boolean then we can reuse its initial state (otherwise + // we can't because of potential self loops) + if (f[0].is_boolean()) + { + unsigned init_state = recurse(f[0]); + aut_->new_edge(init_state, init_state, bddtrue, acc); + return init_state; + } + + unsigned init_state = aut_->new_state(); + unsigned sub_init = recurse(f[0]); + + aut_->new_edge(init_state, init_state, bddtrue, acc); + + for (auto& e : aut_->out(sub_init)) + aut_->new_edge(init_state, e.dst, e.cond, {}); + + return init_state; + } + + // G phi = ff R phi + case op::G: + { + unsigned init_state = aut_->new_state(); + + unsigned sub_init = recurse(f[0]); + + // translate like R, but only the self loop part; `ff` cancels out + // the product of edges + std::vector new_dests; + for (auto& e : aut_->out(sub_init)) + { + auto dests = aut_->univ_dests(e); + std::copy(dests.begin(), dests.end(), + std::back_inserter(new_dests)); + new_dests.push_back(init_state); + + unsigned dst = uniq_.new_univ_dests(new_dests.begin(), + new_dests.end()); + aut_->new_edge(init_state, dst, e.cond, {}); + + new_dests.clear(); + } + + return init_state; + } + + case op::eword: + case op::Xor: + case op::Implies: + case op::Equiv: + case op::Closure: + case op::NegClosure: + case op::NegClosureMarked: + case op::EConcat: + case op::EConcatMarked: + case op::UConcat: + case op::OrRat: + case op::AndRat: + case op::AndNLM: + case op::Concat: + case op::Fusion: + case op::Star: + case op::FStar: + case op::first_match: + SPOT_UNREACHABLE(); + return -1; + } + + SPOT_UNREACHABLE(); + } + }; + } + + twa_graph_ptr + ltl_to_aa(formula f, bdd_dict_ptr& dict, bool purge_dead_states) + { + SPOT_ASSERT(f.is_ltl_formula()); + f = negative_normal_form(f); + + auto aut = make_twa_graph(dict); + aut->set_co_buchi(); + + unsigned accepting_sink = aut->new_state(); + aut->new_edge(accepting_sink, accepting_sink, bddtrue, {}); + auto builder = ltl_to_aa_builder(aut, accepting_sink); + + unsigned init_state = builder.recurse(f); + aut->set_init_state(init_state); + + if (purge_dead_states) + aut->purge_dead_states(); + + return aut; + } +} diff --git a/spot/twaalgos/translate_aa.hh b/spot/twaalgos/translate_aa.hh new file mode 100644 index 000000000..9a8760072 --- /dev/null +++ b/spot/twaalgos/translate_aa.hh @@ -0,0 +1,32 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2010-2015, 2017, 2019-2020 Laboratoire de +// Recherche et Développement de l'Epita (LRDE). +// Copyright (C) 2003, 2004, 2005, 2006 Laboratoire d'Informatique de +// Paris 6 (LIP6), département Systèmes Répartis Coopératifs (SRC), +// Université Pierre et Marie Curie. +// +// 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 . + +#pragma once + +#include +#include + +namespace spot +{ + SPOT_API twa_graph_ptr + ltl_to_aa(formula f, bdd_dict_ptr& dict, bool purge_dead_states = false); +} From 43ed07d283240509ea7a45dda57841542b61f1a6 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 7 Jul 2022 17:57:05 +0200 Subject: [PATCH 13/66] ltl2aa: factorize self-loop creation --- spot/twaalgos/translate_aa.cc | 63 +++++++++++++++-------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 0663651de..531196442 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -43,6 +43,29 @@ namespace spot internal::univ_dest_mapper uniq_; outedge_combiner oe_; + void add_self_loop(twa_graph::edge_storage_t const& e, + unsigned init_state, + acc_cond::mark_t acc) + { + if (e.dst == accepting_sink_) + { + // avoid creating a univ_dests vector if the only dest is an + // accepting sink, which can be simplified, only keeping the self + // loop + aut_->new_edge(init_state, init_state, e.cond, acc); + return; + } + + auto dests = aut_->univ_dests(e); + std::vector new_dests(dests.begin(), dests.end()); + new_dests.push_back(init_state); + + unsigned dst = uniq_.new_univ_dests(new_dests.begin(), + new_dests.end()); + aut_->new_edge(init_state, dst, e.cond, acc); + } + + unsigned recurse(formula f) { switch (f.kind()) @@ -134,18 +157,7 @@ namespace spot std::vector new_dests; for (auto& e : aut_->out(lhs_init)) - { - auto dests = aut_->univ_dests(e); - std::copy(dests.begin(), dests.end(), - std::back_inserter(new_dests)); - new_dests.push_back(init_state); - - unsigned dest = uniq_.new_univ_dests(new_dests.begin(), - new_dests.end()); - aut_->new_edge(init_state, dest, e.cond, acc); - - new_dests.clear(); - } + add_self_loop(e, init_state, acc); for (auto& e : aut_->out(rhs_init)) { @@ -173,20 +185,8 @@ namespace spot unsigned lhs_init = recurse(f[0]); unsigned rhs_init = recurse(f[1]); - std::vector new_dests; for (auto& e : aut_->out(rhs_init)) - { - auto dests = aut_->univ_dests(e); - std::copy(dests.begin(), dests.end(), - std::back_inserter(new_dests)); - new_dests.push_back(init_state); - - unsigned dst = uniq_.new_univ_dests(new_dests.begin(), - new_dests.end()); - aut_->new_edge(init_state, dst, e.cond, acc); - - new_dests.clear(); - } + add_self_loop(e, init_state, acc); std::vector dsts; for (const auto& lhs_e : aut_->out(lhs_init)) @@ -253,18 +253,7 @@ namespace spot // the product of edges std::vector new_dests; for (auto& e : aut_->out(sub_init)) - { - auto dests = aut_->univ_dests(e); - std::copy(dests.begin(), dests.end(), - std::back_inserter(new_dests)); - new_dests.push_back(init_state); - - unsigned dst = uniq_.new_univ_dests(new_dests.begin(), - new_dests.end()); - aut_->new_edge(init_state, dst, e.cond, {}); - - new_dests.clear(); - } + add_self_loop(e, init_state, {}); return init_state; } From ffd60219b5216c572405de97d3843da0ae89c1c6 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 13 Jul 2022 16:11:54 +0200 Subject: [PATCH 14/66] psl not working --- spot/twaalgos/translate_aa.cc | 78 ++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 531196442..c68b30268 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -20,6 +20,7 @@ #include "config.h" #include #include +#include #include #include @@ -258,6 +259,81 @@ namespace spot return init_state; } + case op::UConcat: + { + // FIXME: combine out edges with rhs ! + //unsigned rhs_init = recurse(f[1]); + twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0]); + + const auto& dict = sere_aut->get_dict(); + + std::map old_to_new; + std::map state_to_var; + std::map var_to_state; + bdd vars = bddtrue; + bdd aps = sere_aut->ap_vars(); + std::vector univ_dest; + + // registers a state in various maps and returns the index of the + // anonymous bdd var representing that state + auto register_state = [&](unsigned st) -> int { + auto p = state_to_var.emplace(st, 0); + if (p.second) + { + int v = dict->register_anonymous_variables(1, this); + p.first->second = v; + var_to_state.emplace(v, st); + + unsigned new_st = aut_->new_state(); + old_to_new.emplace(st, new_st); + + vars &= bdd_ithvar(v); + } + + return p.first->second; + }; + + unsigned ns = sere_aut->num_states(); + for (unsigned st = 0; st < ns; ++st) + { + register_state(st); + + bdd sig = bddfalse; + for (const auto& e : sere_aut->out(st)) + { + int st_bddi = register_state(e.dst); + sig |= e.cond & bdd_ithvar(st_bddi); + } + + for (bdd cond : minterms_of(bddtrue, aps)) + { + bdd dest = bdd_appex(sig, cond, bddop_and, aps); + while (dest != bddtrue) + { + assert(bdd_low(dest) == bddfalse); + auto it = var_to_state.find(bdd_var(dest)); + assert(it != var_to_state.end()); + auto it2 = old_to_new.find(it->second); + assert(it2 != old_to_new.end()); + univ_dest.push_back(it2->second); + dest = bdd_high(dest); + } + + auto it = old_to_new.find(st); + assert(it != old_to_new.end()); + unsigned src = it->second; + unsigned dst = uniq_.new_univ_dests(univ_dest.begin(), + univ_dest.end()); + aut_->new_edge(src, dst, cond, {}); + } + } + + auto it = old_to_new.find(sere_aut->get_init_state_number()); + assert(it != old_to_new.end()); + + return it->second; + } + case op::eword: case op::Xor: case op::Implies: @@ -267,7 +343,6 @@ namespace spot case op::NegClosureMarked: case op::EConcat: case op::EConcatMarked: - case op::UConcat: case op::OrRat: case op::AndRat: case op::AndNLM: @@ -288,7 +363,6 @@ namespace spot twa_graph_ptr ltl_to_aa(formula f, bdd_dict_ptr& dict, bool purge_dead_states) { - SPOT_ASSERT(f.is_ltl_formula()); f = negative_normal_form(f); auto aut = make_twa_graph(dict); From 7b376a212c6e0e3762ff1ea50568f0ad4533b091 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 31 Aug 2022 10:59:39 +0200 Subject: [PATCH 15/66] Add ltl2aa binary to tests/core --- tests/Makefile.am | 2 ++ tests/core/.gitignore | 1 + tests/core/ltl2aa.cc | 22 ++++++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 tests/core/ltl2aa.cc diff --git a/tests/Makefile.am b/tests/Makefile.am index 6f7abf994..9bf0cef73 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -76,6 +76,7 @@ check_PROGRAMS = \ core/intvcomp \ core/intvcmp2 \ core/kripkecat \ + core/ltl2aa \ core/ltl2dot \ core/ltl2text \ core/ltlrel \ @@ -125,6 +126,7 @@ core_cube_SOURCES = core/cube.cc core_equals_SOURCES = core/equalsf.cc core_kind_SOURCES = core/kind.cc core_length_SOURCES = core/length.cc +core_ltl2aa_SOURCES = core/ltl2aa.cc core_ltl2dot_SOURCES = core/readltl.cc core_ltl2dot_CPPFLAGS = $(AM_CPPFLAGS) -DDOTTY core_ltl2text_SOURCES = core/readltl.cc diff --git a/tests/core/.gitignore b/tests/core/.gitignore index d4ebfae45..fdee02715 100644 --- a/tests/core/.gitignore +++ b/tests/core/.gitignore @@ -33,6 +33,7 @@ kripkecat length .libs ikwiad +ltl2aa ltl2dot ltl2text ltlmagic diff --git a/tests/core/ltl2aa.cc b/tests/core/ltl2aa.cc new file mode 100644 index 000000000..82b4b9c7e --- /dev/null +++ b/tests/core/ltl2aa.cc @@ -0,0 +1,22 @@ +#include "config.h" + +#include + +#include +#include +#include +#include + +int main(int argc, char * argv[]) +{ + if (argc < 3) + return 1; + + spot::formula f = spot::parse_formula(argv[1]); + spot::bdd_dict_ptr d = spot::make_bdd_dict(); + auto aut = ltl_to_aa(f, d, true); + + std::ofstream out(argv[2]); + spot::print_hoa(out, aut); + return 0; +} From 8e8e44c5f9222882b5f0e3aa9f2a9c6a9ecd50fc Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 9 Aug 2022 12:24:37 +0200 Subject: [PATCH 16/66] ltl2aa: fix R & M operators handling --- spot/twaalgos/translate_aa.cc | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index c68b30268..490ffd7a7 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -189,31 +189,9 @@ namespace spot for (auto& e : aut_->out(rhs_init)) add_self_loop(e, init_state, acc); - std::vector dsts; - for (const auto& lhs_e : aut_->out(lhs_init)) - { - const auto& lhs_dsts = aut_->univ_dests(lhs_e); - std::copy(lhs_dsts.begin(), lhs_dsts.end(), - std::back_inserter(dsts)); - size_t lhs_dest_num = dsts.size(); - - for (const auto& rhs_e : aut_->out(rhs_init)) - { - const auto& rhs_dsts = aut_->univ_dests(rhs_e); - std::copy(rhs_dsts.begin(), rhs_dsts.end(), - std::back_inserter(dsts)); - - bdd cond = lhs_e.cond & rhs_e.cond; - - unsigned dst = uniq_.new_univ_dests(dsts.begin(), - dsts.end()); - aut_->new_edge(init_state, dst, cond, {}); - - // reset to only lhs' current edge destinations - dsts.resize(lhs_dest_num); - } - dsts.clear(); - } + bdd comb = oe_(lhs_init); + comb &= oe_(rhs_init); + oe_.new_dests(init_state, comb); return init_state; } From 0c76e6dd211476ef79e5c0146719bfeb4ba76b03 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 31 Aug 2022 13:59:05 +0200 Subject: [PATCH 17/66] ltl2aa: fix bdd manipulation in UConcat --- spot/twaalgos/translate_aa.cc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 490ffd7a7..b15dfc279 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -286,22 +286,26 @@ namespace spot for (bdd cond : minterms_of(bddtrue, aps)) { bdd dest = bdd_appex(sig, cond, bddop_and, aps); - while (dest != bddtrue) + while (dest != bddfalse) { - assert(bdd_low(dest) == bddfalse); + assert(bdd_high(dest) == bddtrue); auto it = var_to_state.find(bdd_var(dest)); assert(it != var_to_state.end()); auto it2 = old_to_new.find(it->second); assert(it2 != old_to_new.end()); univ_dest.push_back(it2->second); - dest = bdd_high(dest); + dest = bdd_low(dest); } auto it = old_to_new.find(st); assert(it != old_to_new.end()); unsigned src = it->second; - unsigned dst = uniq_.new_univ_dests(univ_dest.begin(), - univ_dest.end()); + + unsigned dst = univ_dest.empty() + ? accepting_sink_ + : (uniq_.new_univ_dests(univ_dest.begin(), + univ_dest.end())); + aut_->new_edge(src, dst, cond, {}); } } From 4153ce0655ba34eccb94a8374b442382fdf5e105 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 6 Sep 2022 16:07:28 +0200 Subject: [PATCH 18/66] ltl2aa: share dict between sere and final aut --- spot/tl/derive.cc | 10 ++++++---- spot/tl/derive.hh | 6 ++++-- spot/twaalgos/translate_aa.cc | 10 ++++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc index 2b1873ed2..c6c328786 100644 --- a/spot/tl/derive.cc +++ b/spot/tl/derive.cc @@ -203,9 +203,9 @@ namespace spot } twa_graph_ptr - derive_finite_automaton_with_first(formula f, bool deterministic) + derive_finite_automaton_with_first(formula f, bdd_dict_ptr bdd_dict, + bool deterministic) { - auto bdd_dict = make_bdd_dict(); auto aut = make_twa_graph(bdd_dict); aut->prop_state_acc(true); @@ -403,9 +403,11 @@ namespace spot } twa_graph_ptr - derive_automaton_with_first(formula f, bool deterministic) + derive_automaton_with_first(formula f, bdd_dict_ptr bdd_dict, + bool deterministic) { - auto finite = derive_finite_automaton_with_first(f, deterministic); + auto finite = derive_finite_automaton_with_first(f, bdd_dict, + deterministic); return from_finite(finite); } diff --git a/spot/tl/derive.hh b/spot/tl/derive.hh index 1947951ed..9e094c7b6 100644 --- a/spot/tl/derive.hh +++ b/spot/tl/derive.hh @@ -39,13 +39,15 @@ namespace spot derive_automaton(formula f, bool deterministic = true); SPOT_API twa_graph_ptr - derive_automaton_with_first(formula f, bool deterministic = true); + derive_automaton_with_first(formula f, bdd_dict_ptr bdd_dict, + bool deterministic = true); SPOT_API twa_graph_ptr derive_finite_automaton(formula f, bool deterministic = true); SPOT_API twa_graph_ptr - derive_finite_automaton_with_first(formula f, bool deterministic = true); + derive_finite_automaton_with_first(formula f, bdd_dict_ptr bdd_dict, + bool deterministic = true); SPOT_API formula rewrite_and_nlm(formula f); diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index b15dfc279..0a29a0671 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -39,6 +39,11 @@ namespace spot { } + ~ltl_to_aa_builder() + { + aut_->get_dict()->unregister_all_my_variables(this); + } + twa_graph_ptr aut_; unsigned accepting_sink_; internal::univ_dest_mapper uniq_; @@ -241,9 +246,9 @@ namespace spot { // FIXME: combine out edges with rhs ! //unsigned rhs_init = recurse(f[1]); - twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0]); + const auto& dict = aut_->get_dict(); + twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0], dict); - const auto& dict = sere_aut->get_dict(); std::map old_to_new; std::map state_to_var; @@ -271,6 +276,7 @@ namespace spot return p.first->second; }; + aut_->copy_ap_of(sere_aut); unsigned ns = sere_aut->num_states(); for (unsigned st = 0; st < ns; ++st) { From 58965475fb32c5415a5fc009ac739b3c18b606de Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 6 Sep 2022 16:31:17 +0200 Subject: [PATCH 19/66] ltl2aa: implem closure --- spot/twaalgos/translate_aa.cc | 41 ++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 0a29a0671..5330c787d 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -71,6 +71,35 @@ namespace spot aut_->new_edge(init_state, dst, e.cond, acc); } + unsigned copy_sere_aut_to_res(twa_graph_ptr sere_aut) + { + std::map old_to_new; + auto register_state = [&](unsigned st) -> unsigned { + auto p = old_to_new.emplace(st, 0); + if (p.second) + { + unsigned new_st = aut_->new_state(); + p.first->second = new_st; + } + return p.first->second; + }; + + unsigned ns = sere_aut->num_states(); + for (unsigned st = 0; st < ns; ++st) + { + unsigned new_st = register_state(st); + for (const auto& e : sere_aut->out(st)) + { + if (sere_aut->state_is_accepting(e.dst)) + aut_->new_edge(new_st, accepting_sink_, e.cond); + else + aut_->new_edge(new_st, register_state(e.dst), e.cond); + } + } + + return register_state(sere_aut->get_init_state_number()); + } + unsigned recurse(formula f) { @@ -322,13 +351,19 @@ namespace spot return it->second; } + case op::Closure: + { + twa_graph_ptr sere_aut = + derive_finite_automaton_with_first(f[0], aut_->get_dict()); + return copy_sere_aut_to_res(sere_aut); + } + + case op::NegClosure: + case op::NegClosureMarked: case op::eword: case op::Xor: case op::Implies: case op::Equiv: - case op::Closure: - case op::NegClosure: - case op::NegClosureMarked: case op::EConcat: case op::EConcatMarked: case op::OrRat: From 93c50e1610158a3ace711b58929197a53fc7dc7a Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 6 Sep 2022 16:31:50 +0200 Subject: [PATCH 20/66] ltl2aa: place new state in var_to_state map --- spot/twaalgos/translate_aa.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 5330c787d..c82b81564 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -294,10 +294,10 @@ namespace spot { int v = dict->register_anonymous_variables(1, this); p.first->second = v; - var_to_state.emplace(v, st); unsigned new_st = aut_->new_state(); old_to_new.emplace(st, new_st); + var_to_state.emplace(v, new_st); vars &= bdd_ithvar(v); } @@ -326,9 +326,7 @@ namespace spot assert(bdd_high(dest) == bddtrue); auto it = var_to_state.find(bdd_var(dest)); assert(it != var_to_state.end()); - auto it2 = old_to_new.find(it->second); - assert(it2 != old_to_new.end()); - univ_dest.push_back(it2->second); + univ_dest.push_back(it->second); dest = bdd_low(dest); } From eca0bd459036a13a88ab7b4a594d5ef5f0a5fdb6 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 16 Sep 2022 03:40:22 +0200 Subject: [PATCH 21/66] ltl2aa: fix two bugs in SERE aut merge --- spot/twaalgos/translate_aa.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index c82b81564..daf9126cb 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -73,6 +73,7 @@ namespace spot unsigned copy_sere_aut_to_res(twa_graph_ptr sere_aut) { + aut_->copy_ap_of(sere_aut); std::map old_to_new; auto register_state = [&](unsigned st) -> unsigned { auto p = old_to_new.emplace(st, 0); @@ -340,6 +341,7 @@ namespace spot univ_dest.end())); aut_->new_edge(src, dst, cond, {}); + univ_dest.clear(); } } From 0957c11c9429f34e9558c03b65db6b3ba1f35e12 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 16 Sep 2022 03:41:30 +0200 Subject: [PATCH 22/66] ltl2aa: finish SERE aut merging with rhs outedges --- spot/twaalgos/alternation.cc | 21 +++++++++++++++++++-- spot/twaalgos/alternation.hh | 2 +- spot/twaalgos/translate_aa.cc | 20 ++++++++++++++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/spot/twaalgos/alternation.cc b/spot/twaalgos/alternation.cc index d5d59a961..03b8d5c2a 100644 --- a/spot/twaalgos/alternation.cc +++ b/spot/twaalgos/alternation.cc @@ -38,7 +38,7 @@ namespace spot aut_->get_dict()->unregister_all_my_variables(this); } - bdd outedge_combiner::operator()(unsigned st) + bdd outedge_combiner::operator()(unsigned st, const std::vector& dst_filter) { const auto& dict = aut_->get_dict(); bdd res = bddtrue; @@ -47,6 +47,21 @@ namespace spot bdd res2 = bddfalse; for (auto& e: aut_->out(d1)) { + // handle edge filtering + if (!dst_filter.empty()) + { + // if any edge destination is an accepting state in the SERE + // automaton, handle the edge, otherwise skip it + auto univ_dests = aut_->univ_dests(e.dst); + if (std::all_of(univ_dests.begin(), univ_dests.end(), + [&](unsigned dst) + { + return std::find(dst_filter.begin(), dst_filter.end(), dst) + == dst_filter.end(); + })) + continue; + } + bdd out = bddtrue; for (unsigned d: aut_->univ_dests(e.dst)) { @@ -65,7 +80,9 @@ namespace spot } res2 |= e.cond & out; } - res &= res2; + + if (res2 != bddfalse) + res &= res2; } return res; } diff --git a/spot/twaalgos/alternation.hh b/spot/twaalgos/alternation.hh index 9490272a1..4949006f2 100644 --- a/spot/twaalgos/alternation.hh +++ b/spot/twaalgos/alternation.hh @@ -53,7 +53,7 @@ namespace spot public: outedge_combiner(const twa_graph_ptr& aut, unsigned sink = -1u); ~outedge_combiner(); - bdd operator()(unsigned st); + bdd operator()(unsigned st, const std::vector& dst_filter = std::vector()); void new_dests(unsigned st, bdd out) const; }; diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index daf9126cb..11b783691 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -274,8 +274,7 @@ namespace spot case op::UConcat: { - // FIXME: combine out edges with rhs ! - //unsigned rhs_init = recurse(f[1]); + unsigned rhs_init = recurse(f[1]); const auto& dict = aut_->get_dict(); twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0], dict); @@ -286,6 +285,7 @@ namespace spot bdd vars = bddtrue; bdd aps = sere_aut->ap_vars(); std::vector univ_dest; + std::vector acc_states; // registers a state in various maps and returns the index of the // anonymous bdd var representing that state @@ -300,6 +300,9 @@ namespace spot old_to_new.emplace(st, new_st); var_to_state.emplace(v, new_st); + if (sere_aut->state_is_accepting(st)) + acc_states.push_back(new_st); + vars &= bdd_ithvar(v); } @@ -345,9 +348,22 @@ namespace spot } } + for (unsigned st = 0; st < ns; ++st) + { + auto it = old_to_new.find(st); + assert(it != old_to_new.end()); + unsigned new_st = it->second; + + bdd comb = bddtrue; + comb &= oe_(new_st, acc_states); + comb &= oe_(rhs_init); + oe_.new_dests(new_st, comb); + } + auto it = old_to_new.find(sere_aut->get_init_state_number()); assert(it != old_to_new.end()); + //aut_->merge_edges(); return it->second; } From dec854ee079763fbd25f64453ac54795846a4be3 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 16 Sep 2022 15:48:07 +0200 Subject: [PATCH 23/66] ltl2aa: finalize UConcat --- spot/twaalgos/alternation.cc | 6 +++++- spot/twaalgos/alternation.hh | 3 ++- spot/twaalgos/translate_aa.cc | 11 +++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/spot/twaalgos/alternation.cc b/spot/twaalgos/alternation.cc index 03b8d5c2a..205979f92 100644 --- a/spot/twaalgos/alternation.cc +++ b/spot/twaalgos/alternation.cc @@ -38,7 +38,8 @@ namespace spot aut_->get_dict()->unregister_all_my_variables(this); } - bdd outedge_combiner::operator()(unsigned st, const std::vector& dst_filter) + bdd outedge_combiner::operator()(unsigned st, const std::vector& dst_filter, + bool remove_original_edges) { const auto& dict = aut_->get_dict(); bdd res = bddtrue; @@ -79,6 +80,9 @@ namespace spot out &= bdd_ithvar(p.first->second); } res2 |= e.cond & out; + + if (remove_original_edges) + e.cond = bddfalse; } if (res2 != bddfalse) diff --git a/spot/twaalgos/alternation.hh b/spot/twaalgos/alternation.hh index 4949006f2..8d1027e8b 100644 --- a/spot/twaalgos/alternation.hh +++ b/spot/twaalgos/alternation.hh @@ -53,7 +53,8 @@ namespace spot public: outedge_combiner(const twa_graph_ptr& aut, unsigned sink = -1u); ~outedge_combiner(); - bdd operator()(unsigned st, const std::vector& dst_filter = std::vector()); + bdd operator()(unsigned st, const std::vector& dst_filter = std::vector(), + bool remove_original_edges = false); void new_dests(unsigned st, bdd out) const; }; diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 11b783691..bd1a1d3de 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -355,15 +355,18 @@ namespace spot unsigned new_st = it->second; bdd comb = bddtrue; - comb &= oe_(new_st, acc_states); - comb &= oe_(rhs_init); - oe_.new_dests(new_st, comb); + comb &= oe_(new_st, acc_states, true); + if (comb != bddtrue) + { + comb &= oe_(rhs_init); + oe_.new_dests(new_st, comb); + } } auto it = old_to_new.find(sere_aut->get_init_state_number()); assert(it != old_to_new.end()); - //aut_->merge_edges(); + aut_->merge_edges(); return it->second; } From e5d7ba9e22afb9b412e036a7a43f495552481fcc Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 16 Sep 2022 15:49:56 +0200 Subject: [PATCH 24/66] ltl2aa: comment --- spot/twaalgos/translate_aa.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index bd1a1d3de..1fd6e03df 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -285,6 +285,7 @@ namespace spot bdd vars = bddtrue; bdd aps = sere_aut->ap_vars(); std::vector univ_dest; + // TODO: this should be a std::vector ! std::vector acc_states; // registers a state in various maps and returns the index of the From 465b135f44839b624648cbd355f14231051ad2e4 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 20 Sep 2022 22:42:40 +0200 Subject: [PATCH 25/66] ltl2aa: implement EConcat --- spot/twaalgos/translate_aa.cc | 69 +++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 1fd6e03df..d4128ed4f 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -71,34 +71,43 @@ namespace spot aut_->new_edge(init_state, dst, e.cond, acc); } - unsigned copy_sere_aut_to_res(twa_graph_ptr sere_aut) + unsigned copy_sere_aut_to_res(twa_graph_ptr sere_aut, + std::map& old_to_new, + std::vector* acc_states = nullptr, + bool use_accepting_sink = true) { + unsigned ns = sere_aut->num_states(); + + // TODO: create all new states at once, keeping an initial offset (the + // number of states already present in aut_) aut_->copy_ap_of(sere_aut); - std::map old_to_new; auto register_state = [&](unsigned st) -> unsigned { auto p = old_to_new.emplace(st, 0); if (p.second) { unsigned new_st = aut_->new_state(); p.first->second = new_st; + if (acc_states != nullptr && sere_aut->state_is_accepting(st)) + acc_states->push_back(new_st); } return p.first->second; }; - unsigned ns = sere_aut->num_states(); for (unsigned st = 0; st < ns; ++st) { unsigned new_st = register_state(st); for (const auto& e : sere_aut->out(st)) { - if (sere_aut->state_is_accepting(e.dst)) + if (use_accepting_sink && sere_aut->state_is_accepting(e.dst)) aut_->new_edge(new_st, accepting_sink_, e.cond); else aut_->new_edge(new_st, register_state(e.dst), e.cond); } } - return register_state(sere_aut->get_init_state_number()); + auto it = old_to_new.find(sere_aut->get_init_state_number()); + assert(it != old_to_new.end()); + return it->second; } @@ -272,6 +281,47 @@ namespace spot return init_state; } + case op::EConcat: + { + unsigned rhs_init = recurse(f[1]); + const auto& dict = aut_->get_dict(); + twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0], dict); + + // TODO: this should be a std::vector ! + std::vector acc_states; + std::map old_to_new; + copy_sere_aut_to_res(sere_aut, old_to_new, &acc_states, false); + + std::vector acc_edges; + unsigned ns = sere_aut->num_states(); + for (unsigned st = 0; st < ns; ++st) + { + auto it = old_to_new.find(st); + assert(it != old_to_new.end()); + unsigned new_st = it->second; + + for (auto& e : aut_->out(new_st)) + { + e.acc = acc_cond::mark_t{0}; + if (std::find(acc_states.begin(), acc_states.end(), e.dst) + != acc_states.end()) + acc_edges.push_back(aut_->edge_number(e)); + } + } + + for (unsigned i : acc_edges) + { + auto& e1 = aut_->edge_storage(i); + for (const auto& e2 : aut_->out(rhs_init)) + aut_->new_edge(e1.src, e2.dst, e1.cond & e2.cond); + } + + auto it = old_to_new.find(sere_aut->get_init_state_number()); + assert(it != old_to_new.end()); + + return it->second; + } + case op::UConcat: { unsigned rhs_init = recurse(f[1]); @@ -375,7 +425,8 @@ namespace spot { twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0], aut_->get_dict()); - return copy_sere_aut_to_res(sere_aut); + std::map old_to_new; + return copy_sere_aut_to_res(sere_aut, old_to_new); } case op::NegClosure: @@ -384,7 +435,6 @@ namespace spot case op::Xor: case op::Implies: case op::Equiv: - case op::EConcat: case op::EConcatMarked: case op::OrRat: case op::AndRat: @@ -419,7 +469,10 @@ namespace spot aut->set_init_state(init_state); if (purge_dead_states) - aut->purge_dead_states(); + { + aut->purge_dead_states(); + aut->merge_edges(); + } return aut; } From 07a283498ff985147f9a66797ffd809cafe63451 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 30 Sep 2022 01:25:45 +0200 Subject: [PATCH 26/66] alternation: fix bug introduced in oe_combiner turns out sometimes we want to account for bddfalse --- spot/twaalgos/alternation.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spot/twaalgos/alternation.cc b/spot/twaalgos/alternation.cc index 205979f92..fb95fd6f3 100644 --- a/spot/twaalgos/alternation.cc +++ b/spot/twaalgos/alternation.cc @@ -85,8 +85,7 @@ namespace spot e.cond = bddfalse; } - if (res2 != bddfalse) - res &= res2; + res &= res2; } return res; } From 7b936819cc1bb7847dda4d61cb3b8ef77b550b68 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 30 Sep 2022 01:32:01 +0200 Subject: [PATCH 27/66] ltl2aa: handle edge case in UConcat If SERE recognizes false, then combined with UConcat the property is always true. --- spot/twaalgos/translate_aa.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index d4128ed4f..c18570d41 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -328,6 +328,13 @@ namespace spot const auto& dict = aut_->get_dict(); twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0], dict); + // DFA recognizes the empty language, so {0} []-> rhs is always true + unsigned ns = sere_aut->num_states(); + bool has_accepting_state = false; + for (unsigned st = 0; st < ns && !has_accepting_state; ++st) + has_accepting_state = sere_aut->state_is_accepting(st); + if (!has_accepting_state) + return accepting_sink_; std::map old_to_new; std::map state_to_var; @@ -361,7 +368,6 @@ namespace spot }; aut_->copy_ap_of(sere_aut); - unsigned ns = sere_aut->num_states(); for (unsigned st = 0; st < ns; ++st) { register_state(st); From e80c98751d679d736520a1fe0af17c7e80d97782 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 3 Nov 2022 06:58:21 +0100 Subject: [PATCH 28/66] sere_to_tgba: produce state-names --- spot/twaalgos/ltl2tgba_fm.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spot/twaalgos/ltl2tgba_fm.cc b/spot/twaalgos/ltl2tgba_fm.cc index 9c7674b0f..838db28be 100644 --- a/spot/twaalgos/ltl2tgba_fm.cc +++ b/spot/twaalgos/ltl2tgba_fm.cc @@ -2218,9 +2218,11 @@ namespace spot const auto acc_mark = res->set_buchi(); size_t sn = namer->state_to_name.size(); + auto names = new std::vector(sn); for (size_t i = 0; i < sn; ++i) { formula g = namer->state_to_name[i]; + (*names)[i] = str_psl(g); if (g.accepts_eword()) { if (res->get_graph().state_storage(i).succ == 0) @@ -2233,6 +2235,8 @@ namespace spot } } + res->set_named_prop("state-names", names); + return res; } } From 89543e6a73881efc5e4cfdd9dbec1bed13af603b Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 10 Nov 2022 07:18:29 +0100 Subject: [PATCH 29/66] derive: option for some optimisations --- spot/tl/derive.cc | 17 ++++++++++++++--- spot/tl/derive.hh | 10 ++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc index c6c328786..f55998660 100644 --- a/spot/tl/derive.cc +++ b/spot/tl/derive.cc @@ -204,7 +204,7 @@ namespace spot twa_graph_ptr derive_finite_automaton_with_first(formula f, bdd_dict_ptr bdd_dict, - bool deterministic) + bool deterministic, derive_opts options) { auto aut = make_twa_graph(bdd_dict); @@ -264,7 +264,7 @@ namespace spot for (const bdd one : minterms_of(firsts, firsts_support)) { formula derivative = - partial_derivation(curr_f, one, bdd_dict, aut.get()); + partial_derivation(curr_f, one, bdd_dict, aut.get(), options); // no transition possible from this letter if (derivative.is(op::ff)) @@ -422,7 +422,7 @@ namespace spot formula partial_derivation(formula f, const bdd var, const bdd_dict_ptr& d, - void* owner) + void* owner, derive_opts options) { if (f.is_boolean()) { @@ -472,6 +472,17 @@ namespace spot formula d_E = partial_derivation(f[0], var, d, owner); + if (options.concat_star_distribute && !f[0].is_finite() && d_E.is(op::OrRat)) + { + std::vector distributed; + for (auto g : d_E) + { + distributed.push_back(formula::Concat({g, formula::Star(f[0], min, max)})); + } + + return formula::OrRat(distributed); + } + return formula::Concat({ d_E, formula::Star(f[0], min, max) }); } diff --git a/spot/tl/derive.hh b/spot/tl/derive.hh index 9e094c7b6..993db2ed2 100644 --- a/spot/tl/derive.hh +++ b/spot/tl/derive.hh @@ -29,11 +29,17 @@ namespace spot { + + struct derive_opts + { + bool concat_star_distribute = true; + }; + /// \ingroup tl_misc /// \brief Produce a SERE formula's partial derivative SPOT_API formula partial_derivation(formula f, const bdd var, const bdd_dict_ptr& d, - void* owner); + void* owner, derive_opts options = {}); SPOT_API twa_graph_ptr derive_automaton(formula f, bool deterministic = true); @@ -47,7 +53,7 @@ namespace spot SPOT_API twa_graph_ptr derive_finite_automaton_with_first(formula f, bdd_dict_ptr bdd_dict, - bool deterministic = true); + bool deterministic = true, derive_opts options = {}); SPOT_API formula rewrite_and_nlm(formula f); From 0fdd3c31f4b7b6840947e9f73f207e640b78eed3 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 21 Nov 2022 10:37:14 +0100 Subject: [PATCH 30/66] derive: add options to control distribution --- spot/tl/derive.cc | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/spot/tl/derive.cc b/spot/tl/derive.cc index f55998660..5e8526eec 100644 --- a/spot/tl/derive.cc +++ b/spot/tl/derive.cc @@ -451,12 +451,30 @@ namespace spot formula E = f[0]; formula F = f.all_but(0); - auto res = - formula::Concat({ partial_derivation(E, var, d, owner), F }); + formula d_E = partial_derivation(E, var, d, owner, options); + + formula res; + + if (options.concat_star_distribute && d_E.is(op::OrRat)) + { + std::vector distributed; + for (auto g : d_E) + { + distributed.push_back(formula::Concat({g, F})); + } + + res = formula::OrRat(distributed); + } + else + { + res = + formula::Concat({ partial_derivation(E, var, d, owner, options), F }); + } + if (E.accepts_eword()) res = formula::OrRat( - { res, partial_derivation(F, var, d, owner) }); + { res, partial_derivation(F, var, d, owner, options) }); return res; } @@ -470,7 +488,7 @@ namespace spot ? formula::unbounded() : (f.max() - 1); - formula d_E = partial_derivation(f[0], var, d, owner); + formula d_E = partial_derivation(f[0], var, d, owner, options); if (options.concat_star_distribute && !f[0].is_finite() && d_E.is(op::OrRat)) { @@ -494,7 +512,7 @@ namespace spot if (f.min() == 0 && f.max() == 0) return formula::tt(); - auto d_E = partial_derivation(E, var, d, owner); + auto d_E = partial_derivation(E, var, d, owner, options); auto min = f.min() == 0 ? 0 : (f.min() - 1); auto max = f.max() == formula::unbounded() @@ -524,7 +542,7 @@ namespace spot for (auto subformula : f) { auto subderivation = - partial_derivation(subformula, var, d, owner); + partial_derivation(subformula, var, d, owner, options); subderivations.push_back(subderivation); } return formula::multop(f.kind(), std::move(subderivations)); @@ -533,7 +551,7 @@ namespace spot case op::AndNLM: { formula rewrite = rewrite_and_nlm(f); - return partial_derivation(rewrite, var, d, owner); + return partial_derivation(rewrite, var, d, owner, options); } // d(E:F) = {d(E):F} U {c(d(E)).d(F)} @@ -542,12 +560,12 @@ namespace spot formula E = f[0]; formula F = f.all_but(0); - auto d_E = partial_derivation(E, var, d, owner); + auto d_E = partial_derivation(E, var, d, owner, options); auto res = formula::Fusion({ d_E, F }); if (d_E.accepts_eword()) res = - formula::OrRat({ res, partial_derivation(F, var, d, owner) }); + formula::OrRat({ res, partial_derivation(F, var, d, owner, options) }); return res; } @@ -555,7 +573,7 @@ namespace spot case op::first_match: { formula E = f[0]; - auto d_E = partial_derivation(E, var, d, owner); + auto d_E = partial_derivation(E, var, d, owner, options); // if d_E.accepts_eword(), first_match(d_E) will return eword return formula::first_match(d_E); } From b9f461c0257bf8eb69eab9d35729f916b301945a Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 30 Nov 2022 15:28:49 +0100 Subject: [PATCH 31/66] expansions: draft --- python/spot/impl.i | 2 + spot/tl/Makefile.am | 2 + spot/tl/expansions.cc | 418 ++++++++++++++++++++++++++++++++++++++++++ spot/tl/expansions.hh | 46 +++++ tests/Makefile.am | 2 + tests/core/expand.cc | 25 +++ 6 files changed, 495 insertions(+) create mode 100644 spot/tl/expansions.cc create mode 100644 spot/tl/expansions.hh create mode 100644 tests/core/expand.cc diff --git a/python/spot/impl.i b/python/spot/impl.i index 471b85cbb..725655c08 100644 --- a/python/spot/impl.i +++ b/python/spot/impl.i @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -634,6 +635,7 @@ namespace std { %include %include %include +%include %include %include %include diff --git a/spot/tl/Makefile.am b/spot/tl/Makefile.am index 1e5a68363..abb431267 100644 --- a/spot/tl/Makefile.am +++ b/spot/tl/Makefile.am @@ -32,6 +32,7 @@ tl_HEADERS = \ dot.hh \ environment.hh \ exclusive.hh \ + expansions.hh \ formula.hh \ hierarchy.hh \ length.hh \ @@ -58,6 +59,7 @@ libtl_la_SOURCES = \ derive.cc \ dot.cc \ exclusive.cc \ + expansions.cc \ formula.cc \ hierarchy.cc \ length.cc \ diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc new file mode 100644 index 000000000..4fb2c91c3 --- /dev/null +++ b/spot/tl/expansions.cc @@ -0,0 +1,418 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2021 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 + +namespace spot +{ + namespace + { + static void + insert_or_merge(expansion_t& exp, bdd letter, formula suffix) + { + auto res = exp.insert({letter, suffix}); + if (!res.second) + { + auto it = res.first; + it->second = formula::OrRat({it->second, suffix}); + } + } + + // FIXME: could probably just return a map directly + static std::vector + formula_aps(formula f) + { + auto res = std::unordered_set(); + + f.traverse([&res](formula f) + { + if (f.is(op::ap)) + { + res.insert(f.ap_name()); + return true; + } + + return false; + }); + + return std::vector(res.begin(), res.end()); + } + formula + rewrite_and_nlm(formula f) + { + unsigned s = f.size(); + std::vector final; + std::vector non_final; + + for (auto g: f) + if (g.accepts_eword()) + final.emplace_back(g); + else + non_final.emplace_back(g); + + if (non_final.empty()) + // (a* & b*);c = (a*|b*);c + return formula::OrRat(std::move(final)); + if (!final.empty()) + { + // let F_i be final formulae + // N_i be non final formula + // (F_1 & ... & F_n & N_1 & ... & N_m) + // = (F_1 | ... | F_n);[*] && (N_1 & ... & N_m) + // | (F_1 | ... | F_n) && (N_1 & ... & N_m);[*] + formula f = formula::OrRat(std::move(final)); + formula n = formula::AndNLM(std::move(non_final)); + formula t = formula::one_star(); + formula ft = formula::Concat({f, t}); + formula nt = formula::Concat({n, t}); + formula ftn = formula::AndRat({ft, n}); + formula fnt = formula::AndRat({f, nt}); + return formula::OrRat({ftn, fnt}); + } + // No final formula. + // Translate N_1 & N_2 & ... & N_n into + // N_1 && (N_2;[*]) && ... && (N_n;[*]) + // | (N_1;[*]) && N_2 && ... && (N_n;[*]) + // | (N_1;[*]) && (N_2;[*]) && ... && N_n + formula star = formula::one_star(); + std::vector disj; + for (unsigned n = 0; n < s; ++n) + { + std::vector conj; + for (unsigned m = 0; m < s; ++m) + { + formula g = f[m]; + if (n != m) + g = formula::Concat({g, star}); + conj.emplace_back(g); + } + disj.emplace_back(formula::AndRat(std::move(conj))); + } + return formula::OrRat(std::move(disj)); + } + } + + formula + expansion_to_formula(expansion_t e, bdd_dict_ptr& d) + { + std::vector res; + + for (const auto& [key, val] : e) + { + formula prefix = bdd_to_formula(key, d); + res.push_back(formula::Concat({prefix, val})); + } + + return formula::OrRat(res); + } + + expansion_t + expansion(formula f, const bdd_dict_ptr& d, void *owner) + { + if (f.is_boolean()) + { + auto f_bdd = formula_to_bdd(f, d, owner); + + if (f_bdd == bddfalse) + return {}; + + return {{f_bdd, formula::eword()}}; + } + + + switch (f.kind()) + { + case op::ff: + case op::tt: + case op::ap: + SPOT_UNREACHABLE(); + + case op::eword: + return {{bddfalse, formula::ff()}}; + + case op::Concat: + { + auto exps = expansion(f[0], d, owner); + + expansion_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, formula::Concat({form, f.all_but(0)})}); + } + + if (f[0].accepts_eword()) + { + auto exps_rest = expansion(f.all_but(0), d, owner); + for (const auto& [bdd_l, form] : exps_rest) + { + insert_or_merge(res, bdd_l, form); + } + } + return res; + } + + case op::FStar: + { + formula E = f[0]; + + if (f.min() == 0 && f.max() == 0) + return {{bddtrue, formula::eword()}}; + + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto E_i_j_minus = formula::FStar(E, min, max); + + auto exp = expansion(E, d, owner); + expansion_t res; + for (const auto& [li, ei] : exp) + { + insert_or_merge(res, li, formula::Fusion({ei, E_i_j_minus})); + + if (ei.accepts_eword() && f.min() != 0) + { + for (const auto& [ki, fi] : expansion(E_i_j_minus, d, owner)) + { + // FIXME: build bdd once + if ((li & ki) != bddfalse) + insert_or_merge(res, li & ki, fi); + } + } + } + if (f.min() == 0) + insert_or_merge(res, bddtrue, formula::eword()); + + return res; + } + + case op::Star: + { + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto exps = expansion(f[0], d, owner); + + expansion_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); + } + + return res; + } + + case op::AndNLM: + { + formula rewrite = rewrite_and_nlm(f); + return expansion(rewrite, d, owner); + } + + case op::first_match: + { + auto exps = expansion(f[0], d, owner); + + expansion_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, formula::first_match(form)}); + } + + return res; + } + + case op::Fusion: + { + expansion_t res; + formula E = f[0]; + formula F = f.all_but(0); + + expansion_t Ei = expansion(E, d, owner); + // TODO: std::option + expansion_t Fj = expansion(F, d, owner); + + for (const auto& [li, ei] : Ei) + { + if (ei.accepts_eword()) + { + for (const auto& [kj, fj] : Fj) + if ((li & kj) != bddfalse) + insert_or_merge(res, li & kj, fj); + } + insert_or_merge(res, li, formula::Fusion({ei, F})); + } + + return res; + } + + case op::AndRat: + case op::OrRat: + { + expansion_t res; + for (const auto& sub_f : f) + { + auto exps = expansion(sub_f, d, owner); + + if (exps.empty()) + { + if (f.kind() == op::OrRat) + continue; + + // op::AndRat: one of the expansions was empty (the only + // edge was `false`), so the AndRat is empty as + // well + res.clear(); + break; + } + + if (res.empty()) + { + res = std::move(exps); + continue; + } + + expansion_t new_res; + for (const auto& [l_key, l_val] : exps) + { + for (const auto& [r_key, r_val] : res) + { + if ((l_key & r_key) != bddfalse) + insert_or_merge(new_res, l_key & r_key, formula::multop(f.kind(), {l_val, r_val})); + + if (f.is(op::OrRat)) + { + if ((l_key & !r_key) != bddfalse) + insert_or_merge(new_res, l_key & !r_key, l_val); + + if ((!l_key & r_key) != bddfalse) + insert_or_merge(new_res, !l_key & r_key, r_val); + } + } + } + + res = std::move(new_res); + } + + return res; + } + + default: + std::cerr << "unimplemented kind " + << static_cast(f.kind()) + << std::endl; + SPOT_UNIMPLEMENTED(); + } + + return {}; + } + + twa_graph_ptr + expand_automaton(formula f, bdd_dict_ptr d) + { + auto finite = expand_finite_automaton(f, d); + return from_finite(finite); + } + + twa_graph_ptr + expand_finite_automaton(formula f, bdd_dict_ptr d) + { + auto aut = make_twa_graph(d); + + aut->prop_state_acc(true); + const auto acc_mark = aut->set_buchi(); + + auto formula2state = robin_hood::unordered_map(); + + unsigned init_state = aut->new_state(); + aut->set_init_state(init_state); + formula2state.insert({ f, init_state }); + + auto f_aps = formula_aps(f); + for (auto& ap : f_aps) + aut->register_ap(ap); + + auto todo = std::vector>(); + todo.push_back({f, init_state}); + + auto state_names = new std::vector(); + std::ostringstream ss; + ss << f; + state_names->push_back(ss.str()); + + auto find_dst = [&](formula suffix) -> unsigned + { + unsigned dst; + auto it = formula2state.find(suffix); + if (it != formula2state.end()) + { + dst = it->second; + } + else + { + dst = aut->new_state(); + todo.push_back({suffix, dst}); + formula2state.insert({suffix, dst}); + std::ostringstream ss; + ss << suffix; + state_names->push_back(ss.str()); + } + + return dst; + }; + + while (!todo.empty()) + { + auto [curr_f, curr_state] = todo[todo.size() - 1]; + todo.pop_back(); + + auto curr_acc_mark= curr_f.accepts_eword() + ? acc_mark + : acc_cond::mark_t(); + + auto exp = expansion(curr_f, d, aut.get()); + + for (const auto& [letter, suffix] : exp) + { + if (suffix.is(op::ff)) + continue; + + auto dst = find_dst(suffix); + aut->new_edge(curr_state, dst, letter, curr_acc_mark); + } + + // if state has no transitions and should be accepting, create + // artificial transition + if (aut->get_graph().state_storage(curr_state).succ == 0 + && curr_f.accepts_eword()) + aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); + } + + aut->set_named_prop("state-names", state_names); + aut->merge_edges(); + return aut; + } +} diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh new file mode 100644 index 000000000..af80d7e8b --- /dev/null +++ b/spot/tl/expansions.hh @@ -0,0 +1,46 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2021 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 . + +#pragma once + +#include + +#include + +#include +#include +#include +#include + +namespace spot +{ + using expansion_t = std::map; + + SPOT_API expansion_t + expansion(formula f, const bdd_dict_ptr& d, void *owner); + + SPOT_API formula + expansion_to_formula(expansion_t e, bdd_dict_ptr& d); + + SPOT_API twa_graph_ptr + expand_automaton(formula f, bdd_dict_ptr d); + + SPOT_API twa_graph_ptr + expand_finite_automaton(formula f, bdd_dict_ptr d); +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 9bf0cef73..a061ba23d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -69,6 +69,7 @@ check_PROGRAMS = \ core/cube \ core/emptchk \ core/equals \ + core/expand \ core/graph \ core/kind \ core/length \ @@ -111,6 +112,7 @@ core_bricks_SOURCES = core/bricks.cc core_checkpsl_SOURCES = core/checkpsl.cc core_checkta_SOURCES = core/checkta.cc core_emptchk_SOURCES = core/emptchk.cc +core_expand_SOURCES = core/expand.cc core_graph_SOURCES = core/graph.cc core_ikwiad_SOURCES = core/ikwiad.cc core_intvcomp_SOURCES = core/intvcomp.cc diff --git a/tests/core/expand.cc b/tests/core/expand.cc new file mode 100644 index 000000000..a589d6370 --- /dev/null +++ b/tests/core/expand.cc @@ -0,0 +1,25 @@ +#include "config.h" + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc != 2) + return 1; + + spot::formula f = spot::parse_infix_sere(argv[1]).f; + auto d = spot::make_bdd_dict(); + + auto m = spot::expansion(f, d, nullptr); + + for (const auto& [bdd_l, form] : m) + std::cout << '[' << bdd_to_formula(bdd_l, d) << ']' << ": " << form << std::endl; + std::cout << "formula: " << expansion_to_formula(m, d) << std::endl; + + d->unregister_all_my_variables(nullptr); + + return 0; +} From 1240fec39beeb851933bf54c0cf223f91b58892c Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 15 Dec 2022 08:39:13 +0100 Subject: [PATCH 32/66] expansions: first_match deterministic --- spot/tl/expansions.cc | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 4fb2c91c3..689e90a82 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -236,10 +236,45 @@ namespace spot { auto exps = expansion(f[0], d, owner); - expansion_t res; + expansion_t ndet_res; for (const auto& [bdd_l, form] : exps) { - res.insert({bdd_l, formula::first_match(form)}); + ndet_res.insert({bdd_l, form}); + } + + bdd or_labels = bddfalse; + bdd support = bddtrue; + bool is_det = true; + for (const auto& [l, _] : ndet_res) + { + support &= bdd_support(l); + if (is_det) + is_det = !bdd_have_common_assignment(l, or_labels); + or_labels |= l; + } + + if (is_det) + { + // we don't need to determinize the expansion, it's already + // deterministic + for (auto& [_, dest] : ndet_res) + dest = formula::first_match(dest); + return ndet_res; + } + + expansion_t res; + // TODO: extraire en fonction indépendante + lambda choix wrapper + std::vector dests; + for (bdd l: minterms_of(or_labels, support)) + { + for (const auto& [ndet_label, ndet_dest] : ndet_res) + { + if (bdd_implies(l, ndet_label)) + dests.push_back(ndet_dest); + } + formula or_dests = formula::OrRat(dests); + res.insert({l, formula::first_match(or_dests)}); + dests.clear(); } return res; From 3c6929829d8c70f993442d03f8412ccf8eab78d1 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 15 Dec 2022 10:44:37 +0100 Subject: [PATCH 33/66] expansions: split-off OrRat case --- spot/tl/expansions.cc | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 689e90a82..b3f6eed9b 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -305,7 +305,6 @@ namespace spot } case op::AndRat: - case op::OrRat: { expansion_t res; for (const auto& sub_f : f) @@ -314,9 +313,6 @@ namespace spot if (exps.empty()) { - if (f.kind() == op::OrRat) - continue; - // op::AndRat: one of the expansions was empty (the only // edge was `false`), so the AndRat is empty as // well @@ -337,15 +333,6 @@ namespace spot { if ((l_key & r_key) != bddfalse) insert_or_merge(new_res, l_key & r_key, formula::multop(f.kind(), {l_val, r_val})); - - if (f.is(op::OrRat)) - { - if ((l_key & !r_key) != bddfalse) - insert_or_merge(new_res, l_key & !r_key, l_val); - - if ((!l_key & r_key) != bddfalse) - insert_or_merge(new_res, !l_key & r_key, r_val); - } } } @@ -355,6 +342,28 @@ namespace spot return res; } + case op::OrRat: + { + expansion_t res; + for (const auto& sub_f : f) + { + auto exps = expansion(sub_f, d, owner); + if (exps.empty()) + continue; + + if (res.empty()) + { + res = std::move(exps); + continue; + } + + for (const auto& [label, dest] : exps) + insert_or_merge(res, label, dest); + } + + return res; + } + default: std::cerr << "unimplemented kind " << static_cast(f.kind()) From 9361116431a0a39aabce1418906a2d51306881a5 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 21 Dec 2022 11:05:16 +0100 Subject: [PATCH 34/66] expansions: multiple implementations --- spot/tl/expansions.cc | 214 ++++++++++++++++++++++++++++++++++-------- spot/tl/expansions.hh | 12 +++ 2 files changed, 188 insertions(+), 38 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index b3f6eed9b..8a7e3d8ad 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -28,10 +28,47 @@ namespace spot { namespace { - static void - insert_or_merge(expansion_t& exp, bdd letter, formula suffix) + class expansion_basic final : expansion_builder { - auto res = exp.insert({letter, suffix}); + public: + using exp_map = expansion_builder::exp_map; + + expansion_basic() + {} + + expansion_basic(exp_map&& m) + : bdd2formula_(m) + , formula2bdd_() + {} + + void insert(bdd letter, formula suffix) final; + + void finalize() final + {} + + exp_map& result() final + { + return bdd2formula_; + } + + bool empty() final + { + return bdd2formula_.empty(); + } + + void clear() final + { + bdd2formula_.clear(); + } + + private: + exp_map bdd2formula_; + std::map formula2bdd_; + }; + + void expansion_basic::insert(bdd letter, formula suffix) + { + auto res = bdd2formula_.insert({letter, suffix}); if (!res.second) { auto it = res.first; @@ -39,6 +76,93 @@ namespace spot } } + class expansion_merge_formulas final : expansion_builder + { + public: + using exp_map = expansion_builder::exp_map; + + expansion_merge_formulas() + {} + + expansion_merge_formulas(exp_map&& m) + : res_() + , terms_(m.begin(), m.end()) + {} + + void insert(bdd letter, formula suffix) final; + + void finalize() final; + + exp_map& result() final + { + return res_; + } + + bool empty() final + { + return terms_.empty(); + } + + void clear() final + { + terms_.clear(); + res_.clear(); + } + + private: + std::vector> terms_; + exp_map res_; + }; + + void expansion_merge_formulas::insert(bdd letter, formula suffix) + { + terms_.push_back({letter, suffix}); + } + + void expansion_merge_formulas::finalize() + { + res_.clear(); + + // Given such terms: + // + // - a . ϕ1 + // - a . ϕ2 + // - b . ϕ1 + // + // Merge them by suffix: + // + // - (a ∨ b) . ϕ1 + // - a . ϕ2 + std::map suffix2letter; + for (const auto& [letter, suffix]: terms_) + { + auto res = suffix2letter.insert({suffix, letter}); + if (!res.second) + { + auto it = res.first; + it->second |= letter; + } + } + + // Given such terms: + // + // - a . ϕ1 + // - a . ϕ2 + // + // Merge them by letter: + // + // - a . (ϕ1 ∨ ϕ2) + for (const auto& [suffix, letter]: suffix2letter) + { + auto res = res_.insert({letter, suffix}); + if (!res.second) + { + auto it = res.first; + it->second = formula::OrRat({it->second, suffix}); + } + } + } + // FIXME: could probably just return a map directly static std::vector formula_aps(formula f) @@ -58,6 +182,7 @@ namespace spot return std::vector(res.begin(), res.end()); } + formula rewrite_and_nlm(formula f) { @@ -130,6 +255,8 @@ namespace spot expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner) { + using expansion_type = expansion_merge_formulas; + if (f.is_boolean()) { auto f_bdd = formula_to_bdd(f, d, owner); @@ -155,10 +282,10 @@ namespace spot { auto exps = expansion(f[0], d, owner); - expansion_t res; + expansion_type res; for (const auto& [bdd_l, form] : exps) { - res.insert({bdd_l, formula::Concat({form, f.all_but(0)})}); + res.insert(bdd_l, formula::Concat({form, f.all_but(0)})); } if (f[0].accepts_eword()) @@ -166,10 +293,12 @@ namespace spot auto exps_rest = expansion(f.all_but(0), d, owner); for (const auto& [bdd_l, form] : exps_rest) { - insert_or_merge(res, bdd_l, form); + res.insert(bdd_l, form); } } - return res; + + res.finalize(); + return res.result(); } case op::FStar: @@ -187,10 +316,10 @@ namespace spot auto E_i_j_minus = formula::FStar(E, min, max); auto exp = expansion(E, d, owner); - expansion_t res; + expansion_type res; for (const auto& [li, ei] : exp) { - insert_or_merge(res, li, formula::Fusion({ei, E_i_j_minus})); + res.insert(li, formula::Fusion({ei, E_i_j_minus})); if (ei.accepts_eword() && f.min() != 0) { @@ -198,14 +327,15 @@ namespace spot { // FIXME: build bdd once if ((li & ki) != bddfalse) - insert_or_merge(res, li & ki, fi); + res.insert(li & ki, fi); } } } if (f.min() == 0) - insert_or_merge(res, bddtrue, formula::eword()); + res.insert(bddtrue, formula::eword()); - return res; + res.finalize(); + return res.result(); } case op::Star: @@ -217,13 +347,14 @@ namespace spot auto exps = expansion(f[0], d, owner); - expansion_t res; + expansion_type res; for (const auto& [bdd_l, form] : exps) { - res.insert({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); + res.insert(bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})); } - return res; + res.finalize(); + return res.result(); } case op::AndNLM: @@ -236,16 +367,17 @@ namespace spot { auto exps = expansion(f[0], d, owner); - expansion_t ndet_res; + expansion_type ndet_res; for (const auto& [bdd_l, form] : exps) { - ndet_res.insert({bdd_l, form}); + ndet_res.insert(bdd_l, form); } bdd or_labels = bddfalse; bdd support = bddtrue; bool is_det = true; - for (const auto& [l, _] : ndet_res) + ndet_res.finalize(); + for (const auto& [l, _] : ndet_res.result()) { support &= bdd_support(l); if (is_det) @@ -257,32 +389,33 @@ namespace spot { // we don't need to determinize the expansion, it's already // deterministic - for (auto& [_, dest] : ndet_res) + for (auto& [_, dest] : ndet_res.result()) dest = formula::first_match(dest); - return ndet_res; + return ndet_res.result(); } - expansion_t res; + expansion_type res; // TODO: extraire en fonction indépendante + lambda choix wrapper std::vector dests; for (bdd l: minterms_of(or_labels, support)) { - for (const auto& [ndet_label, ndet_dest] : ndet_res) + for (const auto& [ndet_label, ndet_dest] : ndet_res.result()) { if (bdd_implies(l, ndet_label)) dests.push_back(ndet_dest); } formula or_dests = formula::OrRat(dests); - res.insert({l, formula::first_match(or_dests)}); + res.insert(l, formula::first_match(or_dests)); dests.clear(); } - return res; + res.finalize(); + return res.result(); } case op::Fusion: { - expansion_t res; + expansion_type res; formula E = f[0]; formula F = f.all_but(0); @@ -296,17 +429,18 @@ namespace spot { for (const auto& [kj, fj] : Fj) if ((li & kj) != bddfalse) - insert_or_merge(res, li & kj, fj); + res.insert(li & kj, fj); } - insert_or_merge(res, li, formula::Fusion({ei, F})); + res.insert(li, formula::Fusion({ei, F})); } - return res; + res.finalize(); + return res.result(); } case op::AndRat: { - expansion_t res; + expansion_type res; for (const auto& sub_f : f) { auto exps = expansion(sub_f, d, owner); @@ -322,29 +456,32 @@ namespace spot if (res.empty()) { - res = std::move(exps); + res = expansion_type(std::move(exps)); + res.finalize(); continue; } - expansion_t new_res; + expansion_type new_res; for (const auto& [l_key, l_val] : exps) { - for (const auto& [r_key, r_val] : res) + for (const auto& [r_key, r_val] : res.result()) { if ((l_key & r_key) != bddfalse) - insert_or_merge(new_res, l_key & r_key, formula::multop(f.kind(), {l_val, r_val})); + new_res.insert(l_key & r_key, formula::multop(f.kind(), {l_val, r_val})); } } res = std::move(new_res); + res.finalize(); } - return res; + res.finalize(); + return res.result(); } case op::OrRat: { - expansion_t res; + expansion_type res; for (const auto& sub_f : f) { auto exps = expansion(sub_f, d, owner); @@ -353,15 +490,16 @@ namespace spot if (res.empty()) { - res = std::move(exps); + res = expansion_type(std::move(exps)); continue; } for (const auto& [label, dest] : exps) - insert_or_merge(res, label, dest); + res.insert(label, dest); } - return res; + res.finalize(); + return res.result(); } default: diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index af80d7e8b..8a5e3cb07 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -32,6 +32,18 @@ namespace spot { using expansion_t = std::map; + class expansion_builder + { + public: + using exp_map = std::map; + + virtual void insert(bdd letter, formula suffix) = 0; + virtual void finalize() = 0; + virtual exp_map& result() = 0; + virtual bool empty() = 0; + virtual void clear() = 0; + }; + SPOT_API expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner); From 12a8d5382dd8d157b8b4e80b4dd4615184fafd48 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 20 Jan 2023 14:28:35 +0100 Subject: [PATCH 35/66] expansions: add BDD method --- spot/tl/expansions.cc | 260 ++++++++++++++++++++++++++++++++++++------ spot/tl/expansions.hh | 12 +- 2 files changed, 233 insertions(+), 39 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 8a7e3d8ad..593bd0c4d 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -18,6 +18,7 @@ // along with this program. If not, see . #include "config.h" +#include #include #include #include @@ -33,10 +34,10 @@ namespace spot public: using exp_map = expansion_builder::exp_map; - expansion_basic() + expansion_basic(bdd_dict_ptr d) {} - expansion_basic(exp_map&& m) + expansion_basic(exp_map&& m, bdd_dict_ptr d) : bdd2formula_(m) , formula2bdd_() {} @@ -81,10 +82,10 @@ namespace spot public: using exp_map = expansion_builder::exp_map; - expansion_merge_formulas() + expansion_merge_formulas(bdd_dict_ptr d) {} - expansion_merge_formulas(exp_map&& m) + expansion_merge_formulas(exp_map&& m, bdd_dict_ptr d) : res_() , terms_(m.begin(), m.end()) {} @@ -163,6 +164,182 @@ namespace spot } } + class expansion_bdd final : expansion_builder + { + public: + using exp_map = expansion_builder::exp_map; + + expansion_bdd(bdd_dict_ptr d) + : anon_set_(bddtrue) + , d_(d) + {} + + expansion_bdd(exp_map&& m, bdd_dict_ptr d) + : anon_set_(bddtrue) + , d_(d) + { + for (const auto& [letter, suffix] : m) + { + insert(letter, suffix); + } + } + + expansion_bdd(const expansion_bdd&) = delete; + + expansion_bdd& + operator=(const expansion_bdd& other) = delete; + + expansion_bdd& + operator=(const expansion_bdd&& other) + { + d_->unregister_all_my_variables(this); + + anon_set_ = std::move(other.anon_set_); + exp_ = std::move(other.exp_); + res_ = std::move(other.res_); + formula2bdd_ = std::move(other.formula2bdd_); + bdd2formula_ = std::move(other.bdd2formula_); + + d_ = other.d_; + d_->register_all_variables_of(&other, this); + + return *this; + } + + ~expansion_bdd() + { + d_->unregister_all_my_variables(this); + } + + void insert(bdd letter, formula suffix) final; + + void finalize() final; + + exp_map& result() final + { + return res_; + } + + bool empty() final + { + return formula2bdd_.empty(); + } + + void clear() final + { + formula2bdd_.clear(); + bdd2formula_.clear(); + exp_ = bddfalse; + anon_set_ = bddtrue; + res_.clear(); + } + + private: + bdd exp_; + bdd anon_set_; + std::map formula2bdd_; + std::map bdd2formula_; + exp_map res_; + bdd_dict_ptr d_; + + formula var_to_formula(int var); + formula conj_bdd_to_sere(bdd b); + }; + + formula + expansion_bdd::var_to_formula(int var) + { + formula f = bdd2formula_[var]; + assert(f); + return f; + } + + formula + expansion_bdd::conj_bdd_to_sere(bdd b) + { + if (b == bddtrue) + return formula::tt(); + if (b == bddfalse) + return formula::ff(); + + // Unroll the first loop of the next do/while loop so that we + // do not have to create v when b is not a conjunction. + formula res = var_to_formula(bdd_var(b)); + bdd high = bdd_high(b); + if (high == bddfalse) + { + res = formula::Not(res); + b = bdd_low(b); + } + else + { + assert(bdd_low(b) == bddfalse); + b = high; + } + if (b == bddtrue) + return res; + std::vector v{std::move(res)}; + do + { + res = var_to_formula(bdd_var(b)); + high = bdd_high(b); + if (high == bddfalse) + { + res = formula::Not(res); + b = bdd_low(b); + } + else + { + assert(bdd_low(b) == bddfalse); + b = high; + } + assert(b != bddfalse); + v.emplace_back(std::move(res)); + } + while (b != bddtrue); + return formula::multop(op::AndRat, std::move(v)); + } + + void expansion_bdd::insert(bdd letter, formula suffix) + { + + int anon_var_num; + auto it = formula2bdd_.find(suffix); + if (it != formula2bdd_.end()) + { + anon_var_num = it->second; + } + else + { + anon_var_num = d_->register_anonymous_variables(1, this); + formula2bdd_.insert({suffix, anon_var_num}); + bdd2formula_.insert({anon_var_num, suffix}); + } + + bdd var = bdd_ithvar(anon_var_num); + anon_set_ &= var; + exp_ |= letter & var; + } + + void expansion_bdd::finalize() + { + minato_isop isop(exp_); + bdd cube; + while ((cube = isop.next()) != bddfalse) + { + bdd letter = bdd_exist(cube, anon_set_); + bdd suffix = bdd_existcomp(cube, anon_set_); + formula dest = conj_bdd_to_sere(suffix); + + auto it = res_.insert({letter, dest}); + if (!it.second) + { + auto it2 = it.first; + it2->second = formula::OrRat({it2->second, dest}); + } + } + } + // FIXME: could probably just return a map directly static std::vector formula_aps(formula f) @@ -252,11 +429,11 @@ namespace spot return formula::OrRat(res); } - expansion_t - expansion(formula f, const bdd_dict_ptr& d, void *owner) - { - using expansion_type = expansion_merge_formulas; + template + expansion_t + expansion_impl(formula f, const bdd_dict_ptr& d, void *owner, expansion_builder::expand_opt opts) + { if (f.is_boolean()) { auto f_bdd = formula_to_bdd(f, d, owner); @@ -280,9 +457,9 @@ namespace spot case op::Concat: { - auto exps = expansion(f[0], d, owner); + auto exps = expansion(f[0], d, owner, opts); - expansion_type res; + ExpansionBuilder res(d); for (const auto& [bdd_l, form] : exps) { res.insert(bdd_l, formula::Concat({form, f.all_but(0)})); @@ -290,7 +467,7 @@ namespace spot if (f[0].accepts_eword()) { - auto exps_rest = expansion(f.all_but(0), d, owner); + auto exps_rest = expansion(f.all_but(0), d, owner, opts); for (const auto& [bdd_l, form] : exps_rest) { res.insert(bdd_l, form); @@ -315,15 +492,15 @@ namespace spot auto E_i_j_minus = formula::FStar(E, min, max); - auto exp = expansion(E, d, owner); - expansion_type res; + auto exp = expansion(E, d, owner, opts); + ExpansionBuilder res(d); for (const auto& [li, ei] : exp) { res.insert(li, formula::Fusion({ei, E_i_j_minus})); if (ei.accepts_eword() && f.min() != 0) { - for (const auto& [ki, fi] : expansion(E_i_j_minus, d, owner)) + for (const auto& [ki, fi] : expansion(E_i_j_minus, d, owner, opts)) { // FIXME: build bdd once if ((li & ki) != bddfalse) @@ -345,9 +522,9 @@ namespace spot ? formula::unbounded() : (f.max() - 1); - auto exps = expansion(f[0], d, owner); + auto exps = expansion(f[0], d, owner, opts); - expansion_type res; + ExpansionBuilder res(d); for (const auto& [bdd_l, form] : exps) { res.insert(bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})); @@ -360,14 +537,14 @@ namespace spot case op::AndNLM: { formula rewrite = rewrite_and_nlm(f); - return expansion(rewrite, d, owner); + return expansion(rewrite, d, owner, opts); } case op::first_match: { - auto exps = expansion(f[0], d, owner); + auto exps = expansion(f[0], d, owner, opts); - expansion_type ndet_res; + ExpansionBuilder ndet_res(d); for (const auto& [bdd_l, form] : exps) { ndet_res.insert(bdd_l, form); @@ -394,7 +571,7 @@ namespace spot return ndet_res.result(); } - expansion_type res; + ExpansionBuilder res(d); // TODO: extraire en fonction indépendante + lambda choix wrapper std::vector dests; for (bdd l: minterms_of(or_labels, support)) @@ -415,13 +592,13 @@ namespace spot case op::Fusion: { - expansion_type res; + ExpansionBuilder res(d); formula E = f[0]; formula F = f.all_but(0); - expansion_t Ei = expansion(E, d, owner); + expansion_t Ei = expansion(E, d, owner, opts); // TODO: std::option - expansion_t Fj = expansion(F, d, owner); + expansion_t Fj = expansion(F, d, owner, opts); for (const auto& [li, ei] : Ei) { @@ -440,10 +617,10 @@ namespace spot case op::AndRat: { - expansion_type res; + ExpansionBuilder res(d); for (const auto& sub_f : f) { - auto exps = expansion(sub_f, d, owner); + auto exps = expansion(sub_f, d, owner, opts); if (exps.empty()) { @@ -456,12 +633,12 @@ namespace spot if (res.empty()) { - res = expansion_type(std::move(exps)); + res = ExpansionBuilder(std::move(exps), d); res.finalize(); continue; } - expansion_type new_res; + ExpansionBuilder new_res(d); for (const auto& [l_key, l_val] : exps) { for (const auto& [r_key, r_val] : res.result()) @@ -481,16 +658,16 @@ namespace spot case op::OrRat: { - expansion_type res; + ExpansionBuilder res(d); for (const auto& sub_f : f) { - auto exps = expansion(sub_f, d, owner); + auto exps = expansion(sub_f, d, owner, opts); if (exps.empty()) continue; if (res.empty()) { - res = expansion_type(std::move(exps)); + res = ExpansionBuilder(std::move(exps), d); continue; } @@ -509,18 +686,29 @@ namespace spot SPOT_UNIMPLEMENTED(); } - return {}; - } + return {}; + } + + expansion_t + expansion(formula f, const bdd_dict_ptr& d, void *owner, expansion_builder::expand_opt opts) + { + if (opts & expansion_builder::Basic) + return expansion_impl(f, d, owner, opts); + else if (opts & expansion_builder::MergeSuffix) + return expansion_impl(f, d, owner, opts); + else // expansion_builder::Bdd + return expansion_impl(f, d, owner, opts); + } twa_graph_ptr - expand_automaton(formula f, bdd_dict_ptr d) + expand_automaton(formula f, bdd_dict_ptr d, expansion_builder::expand_opt opts) { - auto finite = expand_finite_automaton(f, d); + auto finite = expand_finite_automaton(f, d, opts); return from_finite(finite); } twa_graph_ptr - expand_finite_automaton(formula f, bdd_dict_ptr d) + expand_finite_automaton(formula f, bdd_dict_ptr d, expansion_builder::expand_opt opts) { auto aut = make_twa_graph(d); @@ -575,7 +763,7 @@ namespace spot ? acc_mark : acc_cond::mark_t(); - auto exp = expansion(curr_f, d, aut.get()); + auto exp = expansion(curr_f, d, aut.get(), opts); for (const auto& [letter, suffix] : exp) { diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 8a5e3cb07..eb6d6e60f 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -42,17 +42,23 @@ namespace spot virtual exp_map& result() = 0; virtual bool empty() = 0; virtual void clear() = 0; + enum expand_opt { + Deterministic = 1, + Basic = 2, + MergeSuffix = 4, + Bdd = 8, + }; }; SPOT_API expansion_t - expansion(formula f, const bdd_dict_ptr& d, void *owner); + expansion(formula f, const bdd_dict_ptr& d, void *owner, expansion_builder::expand_opt opts); SPOT_API formula expansion_to_formula(expansion_t e, bdd_dict_ptr& d); SPOT_API twa_graph_ptr - expand_automaton(formula f, bdd_dict_ptr d); + expand_automaton(formula f, bdd_dict_ptr d, expansion_builder::expand_opt opts); SPOT_API twa_graph_ptr - expand_finite_automaton(formula f, bdd_dict_ptr d); + expand_finite_automaton(formula f, bdd_dict_ptr d, expansion_builder::expand_opt opts); } From faaefa74244fe3bc9bdfd3ab783c690e1d7c12d2 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 1 Feb 2023 17:31:22 +0100 Subject: [PATCH 36/66] expansions: fix bdd method --- spot/tl/expansions.cc | 231 +++++++++++++++++++++++++++++------------- spot/tl/expansions.hh | 19 ++-- 2 files changed, 168 insertions(+), 82 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 593bd0c4d..1a966936a 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -29,6 +29,18 @@ namespace spot { namespace { + class expansion_builder + { + public: + using exp_map = std::map; + + virtual void insert(bdd letter, formula suffix) = 0; + virtual void finalize(bool deterministic, std::function wrap = formula_identity) = 0; + virtual exp_map& result() = 0; + virtual bool empty() = 0; + virtual void clear() = 0; + }; + class expansion_basic final : expansion_builder { public: @@ -44,8 +56,7 @@ namespace spot void insert(bdd letter, formula suffix) final; - void finalize() final - {} + void finalize(bool deterministic, std::function wrap = formula_identity) final; exp_map& result() final { @@ -77,6 +88,48 @@ namespace spot } } + void expansion_basic::finalize(bool deterministic, std::function wrap) + { + if (!deterministic) + return; + + bdd or_labels = bddfalse; + bdd support = bddtrue; + bool is_det = true; + for (const auto& [l, _] : bdd2formula_) + { + support &= bdd_support(l); + if (is_det) + is_det = !bdd_have_common_assignment(l, or_labels); + or_labels |= l; + } + + if (is_det) + { + // we don't need to determinize the expansion, it's already + // deterministic + for (auto& [_, dest] : bdd2formula_) + dest = wrap(dest); + return; + } + + exp_map res; + std::vector dests; + for (bdd l: minterms_of(or_labels, support)) + { + for (const auto& [ndet_label, ndet_dest] : bdd2formula_) + { + if (bdd_implies(l, ndet_label)) + dests.push_back(ndet_dest); + } + formula or_dests = formula::OrRat(dests); + res.insert({l, wrap(or_dests)}); + dests.clear(); + } + + bdd2formula_ = std::move(res); + } + class expansion_merge_formulas final : expansion_builder { public: @@ -92,7 +145,7 @@ namespace spot void insert(bdd letter, formula suffix) final; - void finalize() final; + void finalize(bool deterministic, std::function wrap = formula_identity) final; exp_map& result() final { @@ -120,7 +173,7 @@ namespace spot terms_.push_back({letter, suffix}); } - void expansion_merge_formulas::finalize() + void expansion_merge_formulas::finalize(bool deterministic, std::function wrap) { res_.clear(); @@ -162,6 +215,45 @@ namespace spot it->second = formula::OrRat({it->second, suffix}); } } + + if (!deterministic) + return; + + bdd or_labels = bddfalse; + bdd support = bddtrue; + bool is_det = true; + for (const auto& [l, _] : res_) + { + support &= bdd_support(l); + if (is_det) + is_det = !bdd_have_common_assignment(l, or_labels); + or_labels |= l; + } + + if (is_det) + { + // we don't need to determinize the expansion, it's already + // deterministic + for (auto& [_, dest] : res_) + dest = wrap(dest); + return; + } + + exp_map res; + std::vector dests; + for (bdd l: minterms_of(or_labels, support)) + { + for (const auto& [ndet_label, ndet_dest] : res_) + { + if (bdd_implies(l, ndet_label)) + dests.push_back(ndet_dest); + } + formula or_dests = formula::OrRat(dests); + res.insert({l, wrap(or_dests)}); + dests.clear(); + } + + res_ = std::move(res); } class expansion_bdd final : expansion_builder @@ -213,7 +305,7 @@ namespace spot void insert(bdd letter, formula suffix) final; - void finalize() final; + void finalize(bool deterministic, std::function wrap = formula_identity) final; exp_map& result() final { @@ -244,6 +336,7 @@ namespace spot formula var_to_formula(int var); formula conj_bdd_to_sere(bdd b); + formula bdd_to_sere(bdd b); }; formula @@ -320,22 +413,52 @@ namespace spot anon_set_ &= var; exp_ |= letter & var; } + formula + expansion_bdd::bdd_to_sere(bdd f) + { + if (f == bddfalse) + return formula::ff(); - void expansion_bdd::finalize() + std::vector v; + minato_isop isop(f); + bdd cube; + while ((cube = isop.next()) != bddfalse) + v.emplace_back(conj_bdd_to_sere(cube)); + return formula::OrRat(std::move(v)); + } + + void expansion_bdd::finalize(bool deterministic, std::function wrap) { - minato_isop isop(exp_); - bdd cube; - while ((cube = isop.next()) != bddfalse) + if (deterministic) { - bdd letter = bdd_exist(cube, anon_set_); - bdd suffix = bdd_existcomp(cube, anon_set_); - formula dest = conj_bdd_to_sere(suffix); - - auto it = res_.insert({letter, dest}); - if (!it.second) + bdd prop_set = bdd_exist(bdd_support(exp_), anon_set_); + bdd or_labels = bdd_exist(exp_, anon_set_); + for (bdd letter: minterms_of(exp_, prop_set)) { - auto it2 = it.first; - it2->second = formula::OrRat({it2->second, dest}); + bdd dest_bdd = bdd_restrict(exp_, letter); + formula dest = wrap(bdd_to_sere(dest_bdd)); + + auto it = res_.insert({letter, dest}); + assert(it.second); + (void) it; + } + } + else + { + minato_isop isop(exp_); + bdd cube; + while ((cube = isop.next()) != bddfalse) + { + bdd letter = bdd_exist(cube, anon_set_); + bdd suffix = bdd_existcomp(cube, anon_set_); + formula dest = conj_bdd_to_sere(suffix); + + auto it = res_.insert({letter, dest}); + if (!it.second) + { + auto it2 = it.first; + it2->second = formula::OrRat({it2->second, dest}); + } } } } @@ -432,7 +555,7 @@ namespace spot template expansion_t - expansion_impl(formula f, const bdd_dict_ptr& d, void *owner, expansion_builder::expand_opt opts) + expansion_impl(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts) { if (f.is_boolean()) { @@ -474,7 +597,7 @@ namespace spot } } - res.finalize(); + res.finalize(opts & exp_opts::Deterministic); return res.result(); } @@ -511,7 +634,7 @@ namespace spot if (f.min() == 0) res.insert(bddtrue, formula::eword()); - res.finalize(); + res.finalize(opts & exp_opts::Deterministic); return res.result(); } @@ -530,7 +653,7 @@ namespace spot res.insert(bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})); } - res.finalize(); + res.finalize(opts & exp_opts::Deterministic); return res.result(); } @@ -544,49 +667,15 @@ namespace spot { auto exps = expansion(f[0], d, owner, opts); - ExpansionBuilder ndet_res(d); + ExpansionBuilder res(d); for (const auto& [bdd_l, form] : exps) { - ndet_res.insert(bdd_l, form); + res.insert(bdd_l, form); } - bdd or_labels = bddfalse; - bdd support = bddtrue; - bool is_det = true; - ndet_res.finalize(); - for (const auto& [l, _] : ndet_res.result()) - { - support &= bdd_support(l); - if (is_det) - is_det = !bdd_have_common_assignment(l, or_labels); - or_labels |= l; - } - - if (is_det) - { - // we don't need to determinize the expansion, it's already - // deterministic - for (auto& [_, dest] : ndet_res.result()) - dest = formula::first_match(dest); - return ndet_res.result(); - } - - ExpansionBuilder res(d); - // TODO: extraire en fonction indépendante + lambda choix wrapper - std::vector dests; - for (bdd l: minterms_of(or_labels, support)) - { - for (const auto& [ndet_label, ndet_dest] : ndet_res.result()) - { - if (bdd_implies(l, ndet_label)) - dests.push_back(ndet_dest); - } - formula or_dests = formula::OrRat(dests); - res.insert(l, formula::first_match(or_dests)); - dests.clear(); - } - - res.finalize(); + res.finalize(true, [](formula f){ + return formula::first_match(f); + }); return res.result(); } @@ -611,7 +700,7 @@ namespace spot res.insert(li, formula::Fusion({ei, F})); } - res.finalize(); + res.finalize(opts & exp_opts::Deterministic); return res.result(); } @@ -634,7 +723,7 @@ namespace spot if (res.empty()) { res = ExpansionBuilder(std::move(exps), d); - res.finalize(); + res.finalize(false); continue; } @@ -649,10 +738,10 @@ namespace spot } res = std::move(new_res); - res.finalize(); + res.finalize(false); } - res.finalize(); + res.finalize(opts & exp_opts::Deterministic); return res.result(); } @@ -675,7 +764,7 @@ namespace spot res.insert(label, dest); } - res.finalize(); + res.finalize(opts & exp_opts::Deterministic); return res.result(); } @@ -690,25 +779,25 @@ namespace spot } expansion_t - expansion(formula f, const bdd_dict_ptr& d, void *owner, expansion_builder::expand_opt opts) + expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts) { - if (opts & expansion_builder::Basic) + if (opts & exp_opts::Basic) return expansion_impl(f, d, owner, opts); - else if (opts & expansion_builder::MergeSuffix) + else if (opts & exp_opts::MergeSuffix) return expansion_impl(f, d, owner, opts); - else // expansion_builder::Bdd + else // exp_opts::Bdd return expansion_impl(f, d, owner, opts); } twa_graph_ptr - expand_automaton(formula f, bdd_dict_ptr d, expansion_builder::expand_opt opts) + expand_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts) { auto finite = expand_finite_automaton(f, d, opts); return from_finite(finite); } twa_graph_ptr - expand_finite_automaton(formula f, bdd_dict_ptr d, expansion_builder::expand_opt opts) + expand_finite_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts) { auto aut = make_twa_graph(d); diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index eb6d6e60f..4ca15b174 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -32,16 +32,13 @@ namespace spot { using expansion_t = std::map; - class expansion_builder + formula formula_identity(formula f) { - public: - using exp_map = std::map; + return f; + } - virtual void insert(bdd letter, formula suffix) = 0; - virtual void finalize() = 0; - virtual exp_map& result() = 0; - virtual bool empty() = 0; - virtual void clear() = 0; + struct exp_opts + { enum expand_opt { Deterministic = 1, Basic = 2, @@ -51,14 +48,14 @@ namespace spot }; SPOT_API expansion_t - expansion(formula f, const bdd_dict_ptr& d, void *owner, expansion_builder::expand_opt opts); + expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts); SPOT_API formula expansion_to_formula(expansion_t e, bdd_dict_ptr& d); SPOT_API twa_graph_ptr - expand_automaton(formula f, bdd_dict_ptr d, expansion_builder::expand_opt opts); + expand_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts); SPOT_API twa_graph_ptr - expand_finite_automaton(formula f, bdd_dict_ptr d, expansion_builder::expand_opt opts); + expand_finite_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts); } From ce9a94f224cf16c31979ea1c78ef3746106ddee6 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 6 Feb 2023 11:04:47 +0100 Subject: [PATCH 37/66] expansions: determinize only once per state --- spot/tl/expansions.cc | 59 ++++++++++++++++++++++--------------------- spot/tl/expansions.hh | 6 +---- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 1a966936a..40fe68415 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -35,7 +35,7 @@ namespace spot using exp_map = std::map; virtual void insert(bdd letter, formula suffix) = 0; - virtual void finalize(bool deterministic, std::function wrap = formula_identity) = 0; + virtual void finalize(bool deterministic) = 0; virtual exp_map& result() = 0; virtual bool empty() = 0; virtual void clear() = 0; @@ -56,7 +56,7 @@ namespace spot void insert(bdd letter, formula suffix) final; - void finalize(bool deterministic, std::function wrap = formula_identity) final; + void finalize(bool deterministic) final; exp_map& result() final { @@ -88,7 +88,7 @@ namespace spot } } - void expansion_basic::finalize(bool deterministic, std::function wrap) + void expansion_basic::finalize(bool deterministic) { if (!deterministic) return; @@ -108,8 +108,6 @@ namespace spot { // we don't need to determinize the expansion, it's already // deterministic - for (auto& [_, dest] : bdd2formula_) - dest = wrap(dest); return; } @@ -123,7 +121,7 @@ namespace spot dests.push_back(ndet_dest); } formula or_dests = formula::OrRat(dests); - res.insert({l, wrap(or_dests)}); + res.insert({l, or_dests}); dests.clear(); } @@ -145,7 +143,7 @@ namespace spot void insert(bdd letter, formula suffix) final; - void finalize(bool deterministic, std::function wrap = formula_identity) final; + void finalize(bool deterministic) final; exp_map& result() final { @@ -173,7 +171,7 @@ namespace spot terms_.push_back({letter, suffix}); } - void expansion_merge_formulas::finalize(bool deterministic, std::function wrap) + void expansion_merge_formulas::finalize(bool deterministic) { res_.clear(); @@ -234,8 +232,6 @@ namespace spot { // we don't need to determinize the expansion, it's already // deterministic - for (auto& [_, dest] : res_) - dest = wrap(dest); return; } @@ -249,7 +245,7 @@ namespace spot dests.push_back(ndet_dest); } formula or_dests = formula::OrRat(dests); - res.insert({l, wrap(or_dests)}); + res.insert({l, or_dests}); dests.clear(); } @@ -305,7 +301,7 @@ namespace spot void insert(bdd letter, formula suffix) final; - void finalize(bool deterministic, std::function wrap = formula_identity) final; + void finalize(bool deterministic) final; exp_map& result() final { @@ -427,7 +423,7 @@ namespace spot return formula::OrRat(std::move(v)); } - void expansion_bdd::finalize(bool deterministic, std::function wrap) + void expansion_bdd::finalize(bool deterministic) { if (deterministic) { @@ -436,7 +432,7 @@ namespace spot for (bdd letter: minterms_of(exp_, prop_set)) { bdd dest_bdd = bdd_restrict(exp_, letter); - formula dest = wrap(bdd_to_sere(dest_bdd)); + formula dest = bdd_to_sere(dest_bdd); auto it = res_.insert({letter, dest}); assert(it.second); @@ -567,6 +563,10 @@ namespace spot return {{f_bdd, formula::eword()}}; } + auto rec = [&d, owner](formula f){ + return expansion_impl(f, d, owner, exp_opts::None); + }; + switch (f.kind()) { @@ -580,7 +580,7 @@ namespace spot case op::Concat: { - auto exps = expansion(f[0], d, owner, opts); + auto exps = rec(f[0]); ExpansionBuilder res(d); for (const auto& [bdd_l, form] : exps) @@ -590,7 +590,7 @@ namespace spot if (f[0].accepts_eword()) { - auto exps_rest = expansion(f.all_but(0), d, owner, opts); + auto exps_rest = rec(f.all_but(0)); for (const auto& [bdd_l, form] : exps_rest) { res.insert(bdd_l, form); @@ -615,7 +615,7 @@ namespace spot auto E_i_j_minus = formula::FStar(E, min, max); - auto exp = expansion(E, d, owner, opts); + auto exp = rec(E); ExpansionBuilder res(d); for (const auto& [li, ei] : exp) { @@ -623,7 +623,7 @@ namespace spot if (ei.accepts_eword() && f.min() != 0) { - for (const auto& [ki, fi] : expansion(E_i_j_minus, d, owner, opts)) + for (const auto& [ki, fi] : rec(E_i_j_minus)) { // FIXME: build bdd once if ((li & ki) != bddfalse) @@ -645,7 +645,7 @@ namespace spot ? formula::unbounded() : (f.max() - 1); - auto exps = expansion(f[0], d, owner, opts); + auto exps = rec(f[0]); ExpansionBuilder res(d); for (const auto& [bdd_l, form] : exps) @@ -660,12 +660,12 @@ namespace spot case op::AndNLM: { formula rewrite = rewrite_and_nlm(f); - return expansion(rewrite, d, owner, opts); + return rec(rewrite); } case op::first_match: { - auto exps = expansion(f[0], d, owner, opts); + auto exps = rec(f[0]); ExpansionBuilder res(d); for (const auto& [bdd_l, form] : exps) @@ -673,10 +673,11 @@ namespace spot res.insert(bdd_l, form); } - res.finalize(true, [](formula f){ - return formula::first_match(f); - }); - return res.result(); + res.finalize(true); + auto res2 = res.result(); + for (auto& [_, dest] : res2) + dest = formula::first_match(dest); + return res2; } case op::Fusion: @@ -685,9 +686,9 @@ namespace spot formula E = f[0]; formula F = f.all_but(0); - expansion_t Ei = expansion(E, d, owner, opts); + expansion_t Ei = rec(E); // TODO: std::option - expansion_t Fj = expansion(F, d, owner, opts); + expansion_t Fj = rec(F); for (const auto& [li, ei] : Ei) { @@ -709,7 +710,7 @@ namespace spot ExpansionBuilder res(d); for (const auto& sub_f : f) { - auto exps = expansion(sub_f, d, owner, opts); + auto exps = rec(sub_f); if (exps.empty()) { @@ -750,7 +751,7 @@ namespace spot ExpansionBuilder res(d); for (const auto& sub_f : f) { - auto exps = expansion(sub_f, d, owner, opts); + auto exps = rec(sub_f); if (exps.empty()) continue; diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 4ca15b174..c8046a7a4 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -32,14 +32,10 @@ namespace spot { using expansion_t = std::map; - formula formula_identity(formula f) - { - return f; - } - struct exp_opts { enum expand_opt { + None = 0, Deterministic = 1, Basic = 2, MergeSuffix = 4, From 003230ed19d6a977c3144b9529c34786b6a11da4 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 13 Feb 2023 14:35:47 +0100 Subject: [PATCH 38/66] expansions: multimap version --- spot/tl/expansions.cc | 335 ++++++++++++++++++++++++++++++++++++++++++ spot/tl/expansions.hh | 9 ++ 2 files changed, 344 insertions(+) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 40fe68415..20efe4eb2 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -548,6 +548,255 @@ namespace spot return formula::OrRat(res); } + std::multimap + expansion_simple(formula f, const bdd_dict_ptr& d, void *owner) + { + using exp_t = std::multimap; + + if (f.is_boolean()) + { + auto f_bdd = formula_to_bdd(f, d, owner); + + if (f_bdd == bddfalse) + return {}; + + return {{f_bdd, formula::eword()}}; + } + + auto rec = [&d, owner](formula f){ + return expansion_simple(f, d, owner); + }; + + + switch (f.kind()) + { + case op::ff: + case op::tt: + case op::ap: + SPOT_UNREACHABLE(); + + case op::eword: + return {{bddfalse, formula::ff()}}; + + case op::Concat: + { + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, formula::Concat({form, f.all_but(0)})}); + } + + if (f[0].accepts_eword()) + { + auto exps_rest = rec(f.all_but(0)); + for (const auto& [bdd_l, form] : exps_rest) + { + res.insert({bdd_l, form}); + } + } + + return res; + } + + case op::FStar: + { + formula E = f[0]; + + if (f.min() == 0 && f.max() == 0) + return {{bddtrue, formula::eword()}}; + + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto E_i_j_minus = formula::FStar(E, min, max); + + auto exp = rec(E); + exp_t res; + for (const auto& [li, ei] : exp) + { + res.insert({li, formula::Fusion({ei, E_i_j_minus})}); + + if (ei.accepts_eword() && f.min() != 0) + { + for (const auto& [ki, fi] : rec(E_i_j_minus)) + { + // FIXME: build bdd once + if ((li & ki) != bddfalse) + res.insert({li & ki, fi}); + } + } + } + if (f.min() == 0) + res.insert({bddtrue, formula::eword()}); + + return res; + } + + case op::Star: + { + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); + } + + return res; + } + + case op::AndNLM: + { + formula rewrite = rewrite_and_nlm(f); + return rec(rewrite); + } + + case op::first_match: + { + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, form}); + } + + // determinize + bdd or_labels = bddfalse; + bdd support = bddtrue; + bool is_det = true; + for (const auto& [l, _] : res) + { + support &= bdd_support(l); + if (is_det) + is_det = !bdd_have_common_assignment(l, or_labels); + or_labels |= l; + } + + if (is_det) + return res; + + exp_t res_det; + std::vector dests; + for (bdd l: minterms_of(or_labels, support)) + { + for (const auto& [ndet_label, ndet_dest] : res) + { + if (bdd_implies(l, ndet_label)) + dests.push_back(ndet_dest); + } + formula or_dests = formula::OrRat(dests); + res_det.insert({l, or_dests}); + dests.clear(); + } + + for (auto& [_, dest] : res_det) + dest = formula::first_match(dest); + return res_det; + } + + case op::Fusion: + { + exp_t res; + formula E = f[0]; + formula F = f.all_but(0); + + exp_t Ei = rec(E); + // TODO: std::option + exp_t Fj = rec(F); + + for (const auto& [li, ei] : Ei) + { + if (ei.accepts_eword()) + { + for (const auto& [kj, fj] : Fj) + if ((li & kj) != bddfalse) + res.insert({li & kj, fj}); + } + res.insert({li, formula::Fusion({ei, F})}); + } + + return res; + } + + case op::AndRat: + { + exp_t res; + for (const auto& sub_f : f) + { + auto exps = rec(sub_f); + + if (exps.empty()) + { + // op::AndRat: one of the expansions was empty (the only + // edge was `false`), so the AndRat is empty as + // well + res.clear(); + break; + } + + if (res.empty()) + { + res = std::move(exps); + continue; + } + + exp_t new_res; + for (const auto& [l_key, l_val] : exps) + { + for (const auto& [r_key, r_val] : res) + { + if ((l_key & r_key) != bddfalse) + new_res.insert({l_key & r_key, formula::multop(f.kind(), {l_val, r_val})}); + } + } + + res = std::move(new_res); + } + + return res; + } + + case op::OrRat: + { + exp_t res; + for (const auto& sub_f : f) + { + auto exps = rec(sub_f); + if (exps.empty()) + continue; + + if (res.empty()) + { + res = std::move(exps); + continue; + } + + for (const auto& [label, dest] : exps) + res.insert({label, dest}); + } + + return res; + } + + default: + std::cerr << "unimplemented kind " + << static_cast(f.kind()) + << std::endl; + SPOT_UNIMPLEMENTED(); + } + + return {}; + } template expansion_t @@ -875,4 +1124,90 @@ namespace spot aut->merge_edges(); return aut; } + + twa_graph_ptr + expand_simple_automaton(formula f, bdd_dict_ptr d) + { + auto finite = expand_simple_finite_automaton(f, d); + return from_finite(finite); + } + + twa_graph_ptr + expand_simple_finite_automaton(formula f, bdd_dict_ptr d) + { + auto aut = make_twa_graph(d); + + aut->prop_state_acc(true); + const auto acc_mark = aut->set_buchi(); + + auto formula2state = robin_hood::unordered_map(); + + unsigned init_state = aut->new_state(); + aut->set_init_state(init_state); + formula2state.insert({ f, init_state }); + + auto f_aps = formula_aps(f); + for (auto& ap : f_aps) + aut->register_ap(ap); + + auto todo = std::vector>(); + todo.push_back({f, init_state}); + + auto state_names = new std::vector(); + std::ostringstream ss; + ss << f; + state_names->push_back(ss.str()); + + auto find_dst = [&](formula suffix) -> unsigned + { + unsigned dst; + auto it = formula2state.find(suffix); + if (it != formula2state.end()) + { + dst = it->second; + } + else + { + dst = aut->new_state(); + todo.push_back({suffix, dst}); + formula2state.insert({suffix, dst}); + std::ostringstream ss; + ss << suffix; + state_names->push_back(ss.str()); + } + + return dst; + }; + + while (!todo.empty()) + { + auto [curr_f, curr_state] = todo[todo.size() - 1]; + todo.pop_back(); + + auto curr_acc_mark= curr_f.accepts_eword() + ? acc_mark + : acc_cond::mark_t(); + + auto exp = expansion_simple(curr_f, d, aut.get()); + + for (const auto& [letter, suffix] : exp) + { + if (suffix.is(op::ff)) + continue; + + auto dst = find_dst(suffix); + aut->new_edge(curr_state, dst, letter, curr_acc_mark); + } + + // if state has no transitions and should be accepting, create + // artificial transition + if (aut->get_graph().state_storage(curr_state).succ == 0 + && curr_f.accepts_eword()) + aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); + } + + aut->set_named_prop("state-names", state_names); + aut->merge_edges(); + return aut; + } } diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index c8046a7a4..ff6977f9b 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -43,6 +43,15 @@ namespace spot }; }; + SPOT_API twa_graph_ptr + expand_simple_automaton(formula f, bdd_dict_ptr d); + + SPOT_API twa_graph_ptr + expand_simple_finite_automaton(formula f, bdd_dict_ptr d); + + SPOT_API std::multimap + expansion_simple(formula f, const bdd_dict_ptr& d, void *owner); + SPOT_API expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts); From 518c58fe5245b801439c0a7a2bdc88ec3b8e7afb Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 6 Mar 2023 18:37:12 +0100 Subject: [PATCH 39/66] expansions: latest implementation --- spot/tl/expansions.cc | 560 +++++++++++++++++++++++++++++++++++++++++- spot/tl/expansions.hh | 20 ++ 2 files changed, 576 insertions(+), 4 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 20efe4eb2..1d225b603 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -548,6 +548,471 @@ namespace spot return formula::OrRat(res); } + class bdd_finalizer + { + public: + bdd_finalizer(std::multimap& exp, bdd_dict_ptr d) + : anon_set_(bddtrue) + , d_(d) + { + for (const auto& [prefix, suffix] : exp) + { + int anon_var_num; + auto it = formula2bdd_.find(suffix); + if (it != formula2bdd_.end()) + { + anon_var_num = it->second; + } + else + { + anon_var_num = d_->register_anonymous_variables(1, this); + formula2bdd_.insert({suffix, anon_var_num}); + bdd2formula_.insert({anon_var_num, suffix}); + } + + bdd var = bdd_ithvar(anon_var_num); + anon_set_ &= var; + exp_ |= prefix & var; + } + } + + ~bdd_finalizer() + { + d_->unregister_all_my_variables(this); + } + + std::multimap + simplify(exp_opts_new::expand_opt_new opts); + + private: + bdd exp_; + bdd anon_set_; + std::map formula2bdd_; + std::map bdd2formula_; + bdd_dict_ptr d_; + + formula var_to_formula(int var); + formula conj_bdd_to_sere(bdd b); + formula bdd_to_sere(bdd b); + }; + + formula + bdd_finalizer::var_to_formula(int var) + { + formula f = bdd2formula_[var]; + assert(f); + return f; + } + + formula + bdd_finalizer::bdd_to_sere(bdd f) + { + if (f == bddfalse) + return formula::ff(); + + std::vector v; + minato_isop isop(f); + bdd cube; + while ((cube = isop.next()) != bddfalse) + v.emplace_back(conj_bdd_to_sere(cube)); + return formula::OrRat(std::move(v)); + } + + formula + bdd_finalizer::conj_bdd_to_sere(bdd b) + { + if (b == bddtrue) + return formula::tt(); + if (b == bddfalse) + return formula::ff(); + + // Unroll the first loop of the next do/while loop so that we + // do not have to create v when b is not a conjunction. + formula res = var_to_formula(bdd_var(b)); + bdd high = bdd_high(b); + if (high == bddfalse) + { + res = formula::Not(res); + b = bdd_low(b); + } + else + { + assert(bdd_low(b) == bddfalse); + b = high; + } + if (b == bddtrue) + return res; + std::vector v{std::move(res)}; + do + { + res = var_to_formula(bdd_var(b)); + high = bdd_high(b); + if (high == bddfalse) + { + res = formula::Not(res); + b = bdd_low(b); + } + else + { + assert(bdd_low(b) == bddfalse); + b = high; + } + assert(b != bddfalse); + v.emplace_back(std::move(res)); + } + while (b != bddtrue); + return formula::multop(op::AndRat, std::move(v)); + } + + std::multimap + bdd_finalizer::simplify(exp_opts_new::expand_opt_new opts) + { + std::multimap res; + + if (opts & exp_opts_new::expand_opt_new::BddMinterm) + { + bdd prop_set = bdd_exist(bdd_support(exp_), anon_set_); + bdd or_labels = bdd_exist(exp_, anon_set_); + for (bdd letter: minterms_of(exp_, prop_set)) + { + bdd dest_bdd = bdd_restrict(exp_, letter); + formula dest = bdd_to_sere(dest_bdd); + + auto it = res.insert({letter, dest}); + assert(it.second); + (void) it; + } + } + else // BddIsop + { + minato_isop isop(exp_); + bdd cube; + while ((cube = isop.next()) != bddfalse) + { + bdd letter = bdd_exist(cube, anon_set_); + bdd suffix = bdd_existcomp(cube, anon_set_); + formula dest = conj_bdd_to_sere(suffix); + + res.insert({letter, dest}); + } + } + + return res; + } + + void + finalize_new(std::multimap& exp, exp_opts_new::expand_opt_new opts, bdd_dict_ptr d) + { + if (opts & (exp_opts_new::expand_opt_new::BddIsop + | exp_opts_new::expand_opt_new::BddMinterm)) + { + bdd_finalizer bddf(exp, d); + exp = bddf.simplify(opts); + } + + if (opts & exp_opts_new::expand_opt_new::UniqueSuffix) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({suffix, prefix}); + if (!res.second) + { + auto it = res.first; + it->second |= prefix; + } + } + + exp.clear(); + for (const auto [suffix, prefix] : unique_map) + { + exp.insert({prefix, suffix}); + } + } + + if (opts & exp_opts_new::expand_opt_new::UniquePrefix) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({prefix, suffix}); + if (!res.second) + { + auto it = res.first; + it->second = formula::OrRat({it->second, suffix}); + } + } + + exp.clear(); + for (const auto [prefix, suffix] : unique_map) + { + exp.insert({prefix, suffix}); + } + } + } + + std::multimap + expansion_new(formula f, const bdd_dict_ptr& d, void *owner, exp_opts_new::expand_opt_new opts) + { + using exp_t = std::multimap; + + if (f.is_boolean()) + { + auto f_bdd = formula_to_bdd(f, d, owner); + + if (f_bdd == bddfalse) + return {}; + + return {{f_bdd, formula::eword()}}; + } + + auto rec = [&d, owner, opts](formula f){ + return expansion_new(f, d, owner, exp_opts_new::None); + }; + + + switch (f.kind()) + { + case op::ff: + case op::tt: + case op::ap: + SPOT_UNREACHABLE(); + + case op::eword: + return {{bddfalse, formula::ff()}}; + + case op::Concat: + { + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, formula::Concat({form, f.all_but(0)})}); + } + + if (f[0].accepts_eword()) + { + auto exps_rest = rec(f.all_but(0)); + for (const auto& [bdd_l, form] : exps_rest) + { + res.insert({bdd_l, form}); + } + } + + finalize_new(res, opts, d); + return res; + } + + case op::FStar: + { + formula E = f[0]; + + if (f.min() == 0 && f.max() == 0) + return {{bddtrue, formula::eword()}}; + + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto E_i_j_minus = formula::FStar(E, min, max); + + auto exp = rec(E); + exp_t res; + for (const auto& [li, ei] : exp) + { + res.insert({li, formula::Fusion({ei, E_i_j_minus})}); + + if (ei.accepts_eword() && f.min() != 0) + { + for (const auto& [ki, fi] : rec(E_i_j_minus)) + { + // FIXME: build bdd once + if ((li & ki) != bddfalse) + res.insert({li & ki, fi}); + } + } + } + if (f.min() == 0) + res.insert({bddtrue, formula::eword()}); + + finalize_new(res, opts, d); + return res; + } + + case op::Star: + { + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); + } + + finalize_new(res, opts, d); + return res; + } + + case op::AndNLM: + { + formula rewrite = rewrite_and_nlm(f); + auto res = rec(rewrite); + finalize_new(res, opts, d); + return res; + } + + case op::first_match: + { + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.insert({bdd_l, form}); + } + + // determinize + bdd or_labels = bddfalse; + bdd support = bddtrue; + bool is_det = true; + for (const auto& [l, _] : res) + { + support &= bdd_support(l); + if (is_det) + is_det = !bdd_have_common_assignment(l, or_labels); + or_labels |= l; + } + + if (is_det) + { + finalize_new(res, opts, d); + return res; + } + + exp_t res_det; + std::vector dests; + for (bdd l: minterms_of(or_labels, support)) + { + for (const auto& [ndet_label, ndet_dest] : res) + { + if (bdd_implies(l, ndet_label)) + dests.push_back(ndet_dest); + } + formula or_dests = formula::OrRat(dests); + res_det.insert({l, or_dests}); + dests.clear(); + } + + for (auto& [_, dest] : res_det) + dest = formula::first_match(dest); + finalize_new(res_det, opts, d); + return res_det; + } + + case op::Fusion: + { + exp_t res; + formula E = f[0]; + formula F = f.all_but(0); + + exp_t Ei = rec(E); + // TODO: std::option + exp_t Fj = rec(F); + + for (const auto& [li, ei] : Ei) + { + if (ei.accepts_eword()) + { + for (const auto& [kj, fj] : Fj) + if ((li & kj) != bddfalse) + res.insert({li & kj, fj}); + } + res.insert({li, formula::Fusion({ei, F})}); + } + + finalize_new(res, opts, d); + return res; + } + + case op::AndRat: + { + exp_t res; + for (const auto& sub_f : f) + { + auto exps = rec(sub_f); + + if (exps.empty()) + { + // op::AndRat: one of the expansions was empty (the only + // edge was `false`), so the AndRat is empty as + // well + res.clear(); + break; + } + + if (res.empty()) + { + res = std::move(exps); + continue; + } + + exp_t new_res; + for (const auto& [l_key, l_val] : exps) + { + for (const auto& [r_key, r_val] : res) + { + if ((l_key & r_key) != bddfalse) + new_res.insert({l_key & r_key, formula::multop(f.kind(), {l_val, r_val})}); + } + } + + res = std::move(new_res); + } + + finalize_new(res, opts, d); + return res; + } + + case op::OrRat: + { + exp_t res; + for (const auto& sub_f : f) + { + auto exps = rec(sub_f); + if (exps.empty()) + continue; + + if (res.empty()) + { + res = std::move(exps); + continue; + } + + for (const auto& [label, dest] : exps) + res.insert({label, dest}); + } + + finalize_new(res, opts, d); + return res; + } + + default: + std::cerr << "unimplemented kind " + << static_cast(f.kind()) + << std::endl; + SPOT_UNIMPLEMENTED(); + } + + return {}; + } + std::multimap expansion_simple(formula f, const bdd_dict_ptr& d, void *owner) { @@ -1031,12 +1496,13 @@ namespace spot expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts) { - if (opts & exp_opts::Basic) - return expansion_impl(f, d, owner, opts); + + if (opts & exp_opts::Bdd) + return expansion_impl(f, d, owner, opts); else if (opts & exp_opts::MergeSuffix) return expansion_impl(f, d, owner, opts); - else // exp_opts::Bdd - return expansion_impl(f, d, owner, opts); + else // exp_opts::Basic + return expansion_impl(f, d, owner, opts); } twa_graph_ptr @@ -1210,4 +1676,90 @@ namespace spot aut->merge_edges(); return aut; } + + twa_graph_ptr + expand_new_automaton(formula f, bdd_dict_ptr d, exp_opts_new::expand_opt_new opts) + { + auto finite = expand_new_finite_automaton(f, d, opts); + return from_finite(finite); + } + + twa_graph_ptr + expand_new_finite_automaton(formula f, bdd_dict_ptr d, exp_opts_new::expand_opt_new opts) + { + auto aut = make_twa_graph(d); + + aut->prop_state_acc(true); + const auto acc_mark = aut->set_buchi(); + + auto formula2state = robin_hood::unordered_map(); + + unsigned init_state = aut->new_state(); + aut->set_init_state(init_state); + formula2state.insert({ f, init_state }); + + auto f_aps = formula_aps(f); + for (auto& ap : f_aps) + aut->register_ap(ap); + + auto todo = std::vector>(); + todo.push_back({f, init_state}); + + auto state_names = new std::vector(); + std::ostringstream ss; + ss << f; + state_names->push_back(ss.str()); + + auto find_dst = [&](formula suffix) -> unsigned + { + unsigned dst; + auto it = formula2state.find(suffix); + if (it != formula2state.end()) + { + dst = it->second; + } + else + { + dst = aut->new_state(); + todo.push_back({suffix, dst}); + formula2state.insert({suffix, dst}); + std::ostringstream ss; + ss << suffix; + state_names->push_back(ss.str()); + } + + return dst; + }; + + while (!todo.empty()) + { + auto [curr_f, curr_state] = todo[todo.size() - 1]; + todo.pop_back(); + + auto curr_acc_mark= curr_f.accepts_eword() + ? acc_mark + : acc_cond::mark_t(); + + auto exp = expansion_new(curr_f, d, aut.get(), opts); + + for (const auto& [letter, suffix] : exp) + { + if (suffix.is(op::ff)) + continue; + + auto dst = find_dst(suffix); + aut->new_edge(curr_state, dst, letter, curr_acc_mark); + } + + // if state has no transitions and should be accepting, create + // artificial transition + if (aut->get_graph().state_storage(curr_state).succ == 0 + && curr_f.accepts_eword()) + aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); + } + + aut->set_named_prop("state-names", state_names); + aut->merge_edges(); + return aut; + } } diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index ff6977f9b..4286e8fd6 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -43,6 +43,26 @@ namespace spot }; }; + struct exp_opts_new + { + enum expand_opt_new { + None = 0, + UniqueSuffix = 1, + UniquePrefix = 2, + BddIsop = 4, + BddMinterm = 8, + }; + }; + + SPOT_API std::multimap + expansion_new(formula f, const bdd_dict_ptr& d, void *owner, exp_opts_new::expand_opt_new opts); + + SPOT_API twa_graph_ptr + expand_new_automaton(formula f, bdd_dict_ptr d, exp_opts_new::expand_opt_new opts); + + SPOT_API twa_graph_ptr + expand_new_finite_automaton(formula f, bdd_dict_ptr d, exp_opts_new::expand_opt_new opts); + SPOT_API twa_graph_ptr expand_simple_automaton(formula f, bdd_dict_ptr d); From b5f11f7366bf92816cbac5cf6c7b39dbfce17786 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 6 Mar 2023 18:37:28 +0100 Subject: [PATCH 40/66] expansions: allow toggling merge_edges off --- spot/tl/expansions.cc | 6 +++++- spot/tl/expansions.hh | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 1d225b603..f68fb2d9d 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -1759,7 +1759,11 @@ namespace spot } aut->set_named_prop("state-names", state_names); - aut->merge_edges(); + + if ((opts & exp_opts_new::MergeEdges) + && !(opts & exp_opts_new::UniqueSuffix)) + aut->merge_edges(); + return aut; } } diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 4286e8fd6..52e83917f 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -51,6 +51,7 @@ namespace spot UniquePrefix = 2, BddIsop = 4, BddMinterm = 8, + MergeEdges = 16, }; }; From 382c57923c8dfcd4ca00e15d5e830f4d1a3a36e4 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 6 Mar 2023 18:37:44 +0100 Subject: [PATCH 41/66] twaalgos: ltl2tgba_fm: allow disabling SCC trim --- spot/twaalgos/ltl2tgba_fm.cc | 16 ++++++++++++---- spot/twaalgos/ltl2tgba_fm.hh | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/spot/twaalgos/ltl2tgba_fm.cc b/spot/twaalgos/ltl2tgba_fm.cc index 838db28be..dd7bb9182 100644 --- a/spot/twaalgos/ltl2tgba_fm.cc +++ b/spot/twaalgos/ltl2tgba_fm.cc @@ -102,7 +102,7 @@ namespace spot { typedef twa_graph::namer namer; public: - ratexp_to_dfa(translate_dict& dict); + ratexp_to_dfa(translate_dict& dict, bool disable_scc_trimming = false); std::vector> succ(formula f); ~ratexp_to_dfa(); @@ -117,6 +117,7 @@ namespace spot typedef robin_hood::unordered_node_map f2a_t; std::vector automata_; f2a_t f2a_; + bool disable_scc_trimming_; }; // Helper dictionary. We represent formulae using BDDs to @@ -902,8 +903,9 @@ namespace spot } - ratexp_to_dfa::ratexp_to_dfa(translate_dict& dict) + ratexp_to_dfa::ratexp_to_dfa(translate_dict& dict, bool disable_scc_trimming) : dict_(dict) + , disable_scc_trimming_(disable_scc_trimming) { } @@ -956,6 +958,12 @@ namespace spot } } + if (disable_scc_trimming_) + { + automata_.emplace_back(a, namer); + return labelled_aut(a, namer); + } + // The following code trims the automaton in a crude way by // eliminating SCCs that are not coaccessible. It does not // actually remove the states, it simply marks the corresponding @@ -2181,7 +2189,7 @@ namespace spot } twa_graph_ptr - sere_to_tgba(formula f, const bdd_dict_ptr& dict) + sere_to_tgba(formula f, const bdd_dict_ptr& dict, bool disable_scc_trimming) { f = negative_normal_form(f); @@ -2189,7 +2197,7 @@ namespace spot twa_graph_ptr a = make_twa_graph(dict); translate_dict d(a, s, false, false, false); - ratexp_to_dfa sere2dfa(d); + ratexp_to_dfa sere2dfa(d, disable_scc_trimming); auto [dfa, namer, state] = sere2dfa.succ(f); diff --git a/spot/twaalgos/ltl2tgba_fm.hh b/spot/twaalgos/ltl2tgba_fm.hh index 7dba4aee0..51de038e1 100644 --- a/spot/twaalgos/ltl2tgba_fm.hh +++ b/spot/twaalgos/ltl2tgba_fm.hh @@ -91,5 +91,5 @@ namespace spot bool label_with_ltl = false); SPOT_API twa_graph_ptr - sere_to_tgba(formula f, const bdd_dict_ptr& dict); + sere_to_tgba(formula f, const bdd_dict_ptr& dict, bool disable_scc_trimming = false); } From 77d25d87a1b1404d5a60eba3d89c1bef73c9968e Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 9 Mar 2023 10:26:50 +0100 Subject: [PATCH 42/66] expansions: fix first_match case --- spot/tl/expansions.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index f68fb2d9d..e92a74621 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -892,6 +892,8 @@ namespace spot if (is_det) { + for (auto& [_, dest] : res) + dest = formula::first_match(dest); finalize_new(res, opts, d); return res; } From a4091ffc371dceed08f73f218cb2c4e250539309 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 16 Mar 2023 07:39:34 +0100 Subject: [PATCH 43/66] expansions: remove multiple old implementations --- spot/tl/expansions.cc | 1419 +++++------------------------------------ spot/tl/expansions.hh | 37 +- 2 files changed, 167 insertions(+), 1289 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index e92a74621..4a6e6ca0c 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -29,436 +29,6 @@ namespace spot { namespace { - class expansion_builder - { - public: - using exp_map = std::map; - - virtual void insert(bdd letter, formula suffix) = 0; - virtual void finalize(bool deterministic) = 0; - virtual exp_map& result() = 0; - virtual bool empty() = 0; - virtual void clear() = 0; - }; - - class expansion_basic final : expansion_builder - { - public: - using exp_map = expansion_builder::exp_map; - - expansion_basic(bdd_dict_ptr d) - {} - - expansion_basic(exp_map&& m, bdd_dict_ptr d) - : bdd2formula_(m) - , formula2bdd_() - {} - - void insert(bdd letter, formula suffix) final; - - void finalize(bool deterministic) final; - - exp_map& result() final - { - return bdd2formula_; - } - - bool empty() final - { - return bdd2formula_.empty(); - } - - void clear() final - { - bdd2formula_.clear(); - } - - private: - exp_map bdd2formula_; - std::map formula2bdd_; - }; - - void expansion_basic::insert(bdd letter, formula suffix) - { - auto res = bdd2formula_.insert({letter, suffix}); - if (!res.second) - { - auto it = res.first; - it->second = formula::OrRat({it->second, suffix}); - } - } - - void expansion_basic::finalize(bool deterministic) - { - if (!deterministic) - return; - - bdd or_labels = bddfalse; - bdd support = bddtrue; - bool is_det = true; - for (const auto& [l, _] : bdd2formula_) - { - support &= bdd_support(l); - if (is_det) - is_det = !bdd_have_common_assignment(l, or_labels); - or_labels |= l; - } - - if (is_det) - { - // we don't need to determinize the expansion, it's already - // deterministic - return; - } - - exp_map res; - std::vector dests; - for (bdd l: minterms_of(or_labels, support)) - { - for (const auto& [ndet_label, ndet_dest] : bdd2formula_) - { - if (bdd_implies(l, ndet_label)) - dests.push_back(ndet_dest); - } - formula or_dests = formula::OrRat(dests); - res.insert({l, or_dests}); - dests.clear(); - } - - bdd2formula_ = std::move(res); - } - - class expansion_merge_formulas final : expansion_builder - { - public: - using exp_map = expansion_builder::exp_map; - - expansion_merge_formulas(bdd_dict_ptr d) - {} - - expansion_merge_formulas(exp_map&& m, bdd_dict_ptr d) - : res_() - , terms_(m.begin(), m.end()) - {} - - void insert(bdd letter, formula suffix) final; - - void finalize(bool deterministic) final; - - exp_map& result() final - { - return res_; - } - - bool empty() final - { - return terms_.empty(); - } - - void clear() final - { - terms_.clear(); - res_.clear(); - } - - private: - std::vector> terms_; - exp_map res_; - }; - - void expansion_merge_formulas::insert(bdd letter, formula suffix) - { - terms_.push_back({letter, suffix}); - } - - void expansion_merge_formulas::finalize(bool deterministic) - { - res_.clear(); - - // Given such terms: - // - // - a . ϕ1 - // - a . ϕ2 - // - b . ϕ1 - // - // Merge them by suffix: - // - // - (a ∨ b) . ϕ1 - // - a . ϕ2 - std::map suffix2letter; - for (const auto& [letter, suffix]: terms_) - { - auto res = suffix2letter.insert({suffix, letter}); - if (!res.second) - { - auto it = res.first; - it->second |= letter; - } - } - - // Given such terms: - // - // - a . ϕ1 - // - a . ϕ2 - // - // Merge them by letter: - // - // - a . (ϕ1 ∨ ϕ2) - for (const auto& [suffix, letter]: suffix2letter) - { - auto res = res_.insert({letter, suffix}); - if (!res.second) - { - auto it = res.first; - it->second = formula::OrRat({it->second, suffix}); - } - } - - if (!deterministic) - return; - - bdd or_labels = bddfalse; - bdd support = bddtrue; - bool is_det = true; - for (const auto& [l, _] : res_) - { - support &= bdd_support(l); - if (is_det) - is_det = !bdd_have_common_assignment(l, or_labels); - or_labels |= l; - } - - if (is_det) - { - // we don't need to determinize the expansion, it's already - // deterministic - return; - } - - exp_map res; - std::vector dests; - for (bdd l: minterms_of(or_labels, support)) - { - for (const auto& [ndet_label, ndet_dest] : res_) - { - if (bdd_implies(l, ndet_label)) - dests.push_back(ndet_dest); - } - formula or_dests = formula::OrRat(dests); - res.insert({l, or_dests}); - dests.clear(); - } - - res_ = std::move(res); - } - - class expansion_bdd final : expansion_builder - { - public: - using exp_map = expansion_builder::exp_map; - - expansion_bdd(bdd_dict_ptr d) - : anon_set_(bddtrue) - , d_(d) - {} - - expansion_bdd(exp_map&& m, bdd_dict_ptr d) - : anon_set_(bddtrue) - , d_(d) - { - for (const auto& [letter, suffix] : m) - { - insert(letter, suffix); - } - } - - expansion_bdd(const expansion_bdd&) = delete; - - expansion_bdd& - operator=(const expansion_bdd& other) = delete; - - expansion_bdd& - operator=(const expansion_bdd&& other) - { - d_->unregister_all_my_variables(this); - - anon_set_ = std::move(other.anon_set_); - exp_ = std::move(other.exp_); - res_ = std::move(other.res_); - formula2bdd_ = std::move(other.formula2bdd_); - bdd2formula_ = std::move(other.bdd2formula_); - - d_ = other.d_; - d_->register_all_variables_of(&other, this); - - return *this; - } - - ~expansion_bdd() - { - d_->unregister_all_my_variables(this); - } - - void insert(bdd letter, formula suffix) final; - - void finalize(bool deterministic) final; - - exp_map& result() final - { - return res_; - } - - bool empty() final - { - return formula2bdd_.empty(); - } - - void clear() final - { - formula2bdd_.clear(); - bdd2formula_.clear(); - exp_ = bddfalse; - anon_set_ = bddtrue; - res_.clear(); - } - - private: - bdd exp_; - bdd anon_set_; - std::map formula2bdd_; - std::map bdd2formula_; - exp_map res_; - bdd_dict_ptr d_; - - formula var_to_formula(int var); - formula conj_bdd_to_sere(bdd b); - formula bdd_to_sere(bdd b); - }; - - formula - expansion_bdd::var_to_formula(int var) - { - formula f = bdd2formula_[var]; - assert(f); - return f; - } - - formula - expansion_bdd::conj_bdd_to_sere(bdd b) - { - if (b == bddtrue) - return formula::tt(); - if (b == bddfalse) - return formula::ff(); - - // Unroll the first loop of the next do/while loop so that we - // do not have to create v when b is not a conjunction. - formula res = var_to_formula(bdd_var(b)); - bdd high = bdd_high(b); - if (high == bddfalse) - { - res = formula::Not(res); - b = bdd_low(b); - } - else - { - assert(bdd_low(b) == bddfalse); - b = high; - } - if (b == bddtrue) - return res; - std::vector v{std::move(res)}; - do - { - res = var_to_formula(bdd_var(b)); - high = bdd_high(b); - if (high == bddfalse) - { - res = formula::Not(res); - b = bdd_low(b); - } - else - { - assert(bdd_low(b) == bddfalse); - b = high; - } - assert(b != bddfalse); - v.emplace_back(std::move(res)); - } - while (b != bddtrue); - return formula::multop(op::AndRat, std::move(v)); - } - - void expansion_bdd::insert(bdd letter, formula suffix) - { - - int anon_var_num; - auto it = formula2bdd_.find(suffix); - if (it != formula2bdd_.end()) - { - anon_var_num = it->second; - } - else - { - anon_var_num = d_->register_anonymous_variables(1, this); - formula2bdd_.insert({suffix, anon_var_num}); - bdd2formula_.insert({anon_var_num, suffix}); - } - - bdd var = bdd_ithvar(anon_var_num); - anon_set_ &= var; - exp_ |= letter & var; - } - formula - expansion_bdd::bdd_to_sere(bdd f) - { - if (f == bddfalse) - return formula::ff(); - - std::vector v; - minato_isop isop(f); - bdd cube; - while ((cube = isop.next()) != bddfalse) - v.emplace_back(conj_bdd_to_sere(cube)); - return formula::OrRat(std::move(v)); - } - - void expansion_bdd::finalize(bool deterministic) - { - if (deterministic) - { - bdd prop_set = bdd_exist(bdd_support(exp_), anon_set_); - bdd or_labels = bdd_exist(exp_, anon_set_); - for (bdd letter: minterms_of(exp_, prop_set)) - { - bdd dest_bdd = bdd_restrict(exp_, letter); - formula dest = bdd_to_sere(dest_bdd); - - auto it = res_.insert({letter, dest}); - assert(it.second); - (void) it; - } - } - else - { - minato_isop isop(exp_); - bdd cube; - while ((cube = isop.next()) != bddfalse) - { - bdd letter = bdd_exist(cube, anon_set_); - bdd suffix = bdd_existcomp(cube, anon_set_); - formula dest = conj_bdd_to_sere(suffix); - - auto it = res_.insert({letter, dest}); - if (!it.second) - { - auto it2 = it.first; - it2->second = formula::OrRat({it2->second, dest}); - } - } - } - } - // FIXME: could probably just return a map directly static std::vector formula_aps(formula f) @@ -479,8 +49,8 @@ namespace spot return std::vector(res.begin(), res.end()); } - formula - rewrite_and_nlm(formula f) + formula + rewrite_and_nlm(formula f) { unsigned s = f.size(); std::vector final; @@ -532,30 +102,15 @@ namespace spot } return formula::OrRat(std::move(disj)); } - } - formula - expansion_to_formula(expansion_t e, bdd_dict_ptr& d) - { - std::vector res; - - for (const auto& [key, val] : e) - { - formula prefix = bdd_to_formula(key, d); - res.push_back(formula::Concat({prefix, val})); - } - - return formula::OrRat(res); - } - - class bdd_finalizer - { - public: - bdd_finalizer(std::multimap& exp, bdd_dict_ptr d) - : anon_set_(bddtrue) - , d_(d) - { - for (const auto& [prefix, suffix] : exp) + class bdd_finalizer + { + public: + bdd_finalizer(std::multimap& exp, bdd_dict_ptr d) + : anon_set_(bddtrue) + , d_(d) + { + for (const auto& [prefix, suffix] : exp) { int anon_var_num; auto it = formula2bdd_.find(suffix); @@ -574,185 +129,201 @@ namespace spot anon_set_ &= var; exp_ |= prefix & var; } - } + } - ~bdd_finalizer() - { - d_->unregister_all_my_variables(this); - } + ~bdd_finalizer() + { + d_->unregister_all_my_variables(this); + } - std::multimap - simplify(exp_opts_new::expand_opt_new opts); + expansion_t + simplify(exp_opts::expand_opt opts); - private: - bdd exp_; - bdd anon_set_; - std::map formula2bdd_; - std::map bdd2formula_; - bdd_dict_ptr d_; + private: + bdd exp_; + bdd anon_set_; + std::map formula2bdd_; + std::map bdd2formula_; + bdd_dict_ptr d_; - formula var_to_formula(int var); - formula conj_bdd_to_sere(bdd b); - formula bdd_to_sere(bdd b); - }; + formula var_to_formula(int var); + formula conj_bdd_to_sere(bdd b); + formula bdd_to_sere(bdd b); + }; - formula - bdd_finalizer::var_to_formula(int var) - { - formula f = bdd2formula_[var]; - assert(f); - return f; - } + formula + bdd_finalizer::var_to_formula(int var) + { + formula f = bdd2formula_[var]; + assert(f); + return f; + } - formula - bdd_finalizer::bdd_to_sere(bdd f) - { - if (f == bddfalse) - return formula::ff(); + formula + bdd_finalizer::bdd_to_sere(bdd f) + { + if (f == bddfalse) + return formula::ff(); - std::vector v; - minato_isop isop(f); - bdd cube; - while ((cube = isop.next()) != bddfalse) - v.emplace_back(conj_bdd_to_sere(cube)); - return formula::OrRat(std::move(v)); - } + std::vector v; + minato_isop isop(f); + bdd cube; + while ((cube = isop.next()) != bddfalse) + v.emplace_back(conj_bdd_to_sere(cube)); + return formula::OrRat(std::move(v)); + } - formula - bdd_finalizer::conj_bdd_to_sere(bdd b) - { - if (b == bddtrue) - return formula::tt(); - if (b == bddfalse) - return formula::ff(); + formula + bdd_finalizer::conj_bdd_to_sere(bdd b) + { + if (b == bddtrue) + return formula::tt(); + if (b == bddfalse) + return formula::ff(); - // Unroll the first loop of the next do/while loop so that we - // do not have to create v when b is not a conjunction. - formula res = var_to_formula(bdd_var(b)); - bdd high = bdd_high(b); - if (high == bddfalse) + // Unroll the first loop of the next do/while loop so that we + // do not have to create v when b is not a conjunction. + formula res = var_to_formula(bdd_var(b)); + bdd high = bdd_high(b); + if (high == bddfalse) { res = formula::Not(res); b = bdd_low(b); } - else + else { assert(bdd_low(b) == bddfalse); b = high; } - if (b == bddtrue) - return res; - std::vector v{std::move(res)}; - do + if (b == bddtrue) + return res; + std::vector v{std::move(res)}; + do { res = var_to_formula(bdd_var(b)); high = bdd_high(b); if (high == bddfalse) - { - res = formula::Not(res); - b = bdd_low(b); - } + { + res = formula::Not(res); + b = bdd_low(b); + } else - { - assert(bdd_low(b) == bddfalse); - b = high; - } + { + assert(bdd_low(b) == bddfalse); + b = high; + } assert(b != bddfalse); v.emplace_back(std::move(res)); } - while (b != bddtrue); - return formula::multop(op::AndRat, std::move(v)); - } - - std::multimap - bdd_finalizer::simplify(exp_opts_new::expand_opt_new opts) - { - std::multimap res; - - if (opts & exp_opts_new::expand_opt_new::BddMinterm) - { - bdd prop_set = bdd_exist(bdd_support(exp_), anon_set_); - bdd or_labels = bdd_exist(exp_, anon_set_); - for (bdd letter: minterms_of(exp_, prop_set)) - { - bdd dest_bdd = bdd_restrict(exp_, letter); - formula dest = bdd_to_sere(dest_bdd); - - auto it = res.insert({letter, dest}); - assert(it.second); - (void) it; - } - } - else // BddIsop - { - minato_isop isop(exp_); - bdd cube; - while ((cube = isop.next()) != bddfalse) - { - bdd letter = bdd_exist(cube, anon_set_); - bdd suffix = bdd_existcomp(cube, anon_set_); - formula dest = conj_bdd_to_sere(suffix); - - res.insert({letter, dest}); - } + while (b != bddtrue); + return formula::multop(op::AndRat, std::move(v)); } - return res; - } + expansion_t + bdd_finalizer::simplify(exp_opts::expand_opt opts) + { + expansion_t res; - void - finalize_new(std::multimap& exp, exp_opts_new::expand_opt_new opts, bdd_dict_ptr d) - { - if (opts & (exp_opts_new::expand_opt_new::BddIsop - | exp_opts_new::expand_opt_new::BddMinterm)) + if (opts & exp_opts::expand_opt::BddMinterm) + { + bdd prop_set = bdd_exist(bdd_support(exp_), anon_set_); + bdd or_labels = bdd_exist(exp_, anon_set_); + for (bdd letter: minterms_of(exp_, prop_set)) + { + bdd dest_bdd = bdd_restrict(exp_, letter); + formula dest = bdd_to_sere(dest_bdd); + + auto it = res.insert({letter, dest}); + assert(it.second); + (void) it; + } + } + else // BddIsop + { + minato_isop isop(exp_); + bdd cube; + while ((cube = isop.next()) != bddfalse) + { + bdd letter = bdd_exist(cube, anon_set_); + bdd suffix = bdd_existcomp(cube, anon_set_); + formula dest = conj_bdd_to_sere(suffix); + + res.insert({letter, dest}); + } + } + + return res; + } + + void + finalize(expansion_t& exp, exp_opts::expand_opt opts, bdd_dict_ptr d) + { + if (opts & (exp_opts::expand_opt::BddIsop + | exp_opts::expand_opt::BddMinterm)) { bdd_finalizer bddf(exp, d); exp = bddf.simplify(opts); } - if (opts & exp_opts_new::expand_opt_new::UniqueSuffix) + if (opts & exp_opts::expand_opt::UniqueSuffix) { std::map unique_map; for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({suffix, prefix}); + if (!res.second) { - auto res = unique_map.insert({suffix, prefix}); - if (!res.second) - { - auto it = res.first; - it->second |= prefix; - } + auto it = res.first; + it->second |= prefix; } + } exp.clear(); for (const auto [suffix, prefix] : unique_map) - { - exp.insert({prefix, suffix}); - } + { + exp.insert({prefix, suffix}); + } } - if (opts & exp_opts_new::expand_opt_new::UniquePrefix) + if (opts & exp_opts::expand_opt::UniquePrefix) { std::map unique_map; for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({prefix, suffix}); + if (!res.second) { - auto res = unique_map.insert({prefix, suffix}); - if (!res.second) - { - auto it = res.first; - it->second = formula::OrRat({it->second, suffix}); - } + auto it = res.first; + it->second = formula::OrRat({it->second, suffix}); } + } exp.clear(); for (const auto [prefix, suffix] : unique_map) - { - exp.insert({prefix, suffix}); - } + { + exp.insert({prefix, suffix}); + } } + } } - std::multimap - expansion_new(formula f, const bdd_dict_ptr& d, void *owner, exp_opts_new::expand_opt_new opts) + formula + expansion_to_formula(expansion_t e, bdd_dict_ptr& d) + { + std::vector res; + + for (const auto& [key, val] : e) + { + formula prefix = bdd_to_formula(key, d); + res.push_back(formula::Concat({prefix, val})); + } + + return formula::OrRat(res); + } + + + expansion_t + expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts) { using exp_t = std::multimap; @@ -767,7 +338,7 @@ namespace spot } auto rec = [&d, owner, opts](formula f){ - return expansion_new(f, d, owner, exp_opts_new::None); + return expansion(f, d, owner, exp_opts::None); }; @@ -800,7 +371,7 @@ namespace spot } } - finalize_new(res, opts, d); + finalize(res, opts, d); return res; } @@ -837,7 +408,7 @@ namespace spot if (f.min() == 0) res.insert({bddtrue, formula::eword()}); - finalize_new(res, opts, d); + finalize(res, opts, d); return res; } @@ -856,7 +427,7 @@ namespace spot res.insert({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); } - finalize_new(res, opts, d); + finalize(res, opts, d); return res; } @@ -864,7 +435,7 @@ namespace spot { formula rewrite = rewrite_and_nlm(f); auto res = rec(rewrite); - finalize_new(res, opts, d); + finalize(res, opts, d); return res; } @@ -894,7 +465,7 @@ namespace spot { for (auto& [_, dest] : res) dest = formula::first_match(dest); - finalize_new(res, opts, d); + finalize(res, opts, d); return res; } @@ -914,7 +485,7 @@ namespace spot for (auto& [_, dest] : res_det) dest = formula::first_match(dest); - finalize_new(res_det, opts, d); + finalize(res_det, opts, d); return res_det; } @@ -939,7 +510,7 @@ namespace spot res.insert({li, formula::Fusion({ei, F})}); } - finalize_new(res, opts, d); + finalize(res, opts, d); return res; } @@ -978,7 +549,7 @@ namespace spot res = std::move(new_res); } - finalize_new(res, opts, d); + finalize(res, opts, d); return res; } @@ -1001,7 +572,7 @@ namespace spot res.insert({label, dest}); } - finalize_new(res, opts, d); + finalize(res, opts, d); return res; } @@ -1015,498 +586,6 @@ namespace spot return {}; } - std::multimap - expansion_simple(formula f, const bdd_dict_ptr& d, void *owner) - { - using exp_t = std::multimap; - - if (f.is_boolean()) - { - auto f_bdd = formula_to_bdd(f, d, owner); - - if (f_bdd == bddfalse) - return {}; - - return {{f_bdd, formula::eword()}}; - } - - auto rec = [&d, owner](formula f){ - return expansion_simple(f, d, owner); - }; - - - switch (f.kind()) - { - case op::ff: - case op::tt: - case op::ap: - SPOT_UNREACHABLE(); - - case op::eword: - return {{bddfalse, formula::ff()}}; - - case op::Concat: - { - auto exps = rec(f[0]); - - exp_t res; - for (const auto& [bdd_l, form] : exps) - { - res.insert({bdd_l, formula::Concat({form, f.all_but(0)})}); - } - - if (f[0].accepts_eword()) - { - auto exps_rest = rec(f.all_but(0)); - for (const auto& [bdd_l, form] : exps_rest) - { - res.insert({bdd_l, form}); - } - } - - return res; - } - - case op::FStar: - { - formula E = f[0]; - - if (f.min() == 0 && f.max() == 0) - return {{bddtrue, formula::eword()}}; - - auto min = f.min() == 0 ? 0 : (f.min() - 1); - auto max = f.max() == formula::unbounded() - ? formula::unbounded() - : (f.max() - 1); - - auto E_i_j_minus = formula::FStar(E, min, max); - - auto exp = rec(E); - exp_t res; - for (const auto& [li, ei] : exp) - { - res.insert({li, formula::Fusion({ei, E_i_j_minus})}); - - if (ei.accepts_eword() && f.min() != 0) - { - for (const auto& [ki, fi] : rec(E_i_j_minus)) - { - // FIXME: build bdd once - if ((li & ki) != bddfalse) - res.insert({li & ki, fi}); - } - } - } - if (f.min() == 0) - res.insert({bddtrue, formula::eword()}); - - return res; - } - - case op::Star: - { - auto min = f.min() == 0 ? 0 : (f.min() - 1); - auto max = f.max() == formula::unbounded() - ? formula::unbounded() - : (f.max() - 1); - - auto exps = rec(f[0]); - - exp_t res; - for (const auto& [bdd_l, form] : exps) - { - res.insert({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); - } - - return res; - } - - case op::AndNLM: - { - formula rewrite = rewrite_and_nlm(f); - return rec(rewrite); - } - - case op::first_match: - { - auto exps = rec(f[0]); - - exp_t res; - for (const auto& [bdd_l, form] : exps) - { - res.insert({bdd_l, form}); - } - - // determinize - bdd or_labels = bddfalse; - bdd support = bddtrue; - bool is_det = true; - for (const auto& [l, _] : res) - { - support &= bdd_support(l); - if (is_det) - is_det = !bdd_have_common_assignment(l, or_labels); - or_labels |= l; - } - - if (is_det) - return res; - - exp_t res_det; - std::vector dests; - for (bdd l: minterms_of(or_labels, support)) - { - for (const auto& [ndet_label, ndet_dest] : res) - { - if (bdd_implies(l, ndet_label)) - dests.push_back(ndet_dest); - } - formula or_dests = formula::OrRat(dests); - res_det.insert({l, or_dests}); - dests.clear(); - } - - for (auto& [_, dest] : res_det) - dest = formula::first_match(dest); - return res_det; - } - - case op::Fusion: - { - exp_t res; - formula E = f[0]; - formula F = f.all_but(0); - - exp_t Ei = rec(E); - // TODO: std::option - exp_t Fj = rec(F); - - for (const auto& [li, ei] : Ei) - { - if (ei.accepts_eword()) - { - for (const auto& [kj, fj] : Fj) - if ((li & kj) != bddfalse) - res.insert({li & kj, fj}); - } - res.insert({li, formula::Fusion({ei, F})}); - } - - return res; - } - - case op::AndRat: - { - exp_t res; - for (const auto& sub_f : f) - { - auto exps = rec(sub_f); - - if (exps.empty()) - { - // op::AndRat: one of the expansions was empty (the only - // edge was `false`), so the AndRat is empty as - // well - res.clear(); - break; - } - - if (res.empty()) - { - res = std::move(exps); - continue; - } - - exp_t new_res; - for (const auto& [l_key, l_val] : exps) - { - for (const auto& [r_key, r_val] : res) - { - if ((l_key & r_key) != bddfalse) - new_res.insert({l_key & r_key, formula::multop(f.kind(), {l_val, r_val})}); - } - } - - res = std::move(new_res); - } - - return res; - } - - case op::OrRat: - { - exp_t res; - for (const auto& sub_f : f) - { - auto exps = rec(sub_f); - if (exps.empty()) - continue; - - if (res.empty()) - { - res = std::move(exps); - continue; - } - - for (const auto& [label, dest] : exps) - res.insert({label, dest}); - } - - return res; - } - - default: - std::cerr << "unimplemented kind " - << static_cast(f.kind()) - << std::endl; - SPOT_UNIMPLEMENTED(); - } - - return {}; - } - - template - expansion_t - expansion_impl(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts) - { - if (f.is_boolean()) - { - auto f_bdd = formula_to_bdd(f, d, owner); - - if (f_bdd == bddfalse) - return {}; - - return {{f_bdd, formula::eword()}}; - } - - auto rec = [&d, owner](formula f){ - return expansion_impl(f, d, owner, exp_opts::None); - }; - - - switch (f.kind()) - { - case op::ff: - case op::tt: - case op::ap: - SPOT_UNREACHABLE(); - - case op::eword: - return {{bddfalse, formula::ff()}}; - - case op::Concat: - { - auto exps = rec(f[0]); - - ExpansionBuilder res(d); - for (const auto& [bdd_l, form] : exps) - { - res.insert(bdd_l, formula::Concat({form, f.all_but(0)})); - } - - if (f[0].accepts_eword()) - { - auto exps_rest = rec(f.all_but(0)); - for (const auto& [bdd_l, form] : exps_rest) - { - res.insert(bdd_l, form); - } - } - - res.finalize(opts & exp_opts::Deterministic); - return res.result(); - } - - case op::FStar: - { - formula E = f[0]; - - if (f.min() == 0 && f.max() == 0) - return {{bddtrue, formula::eword()}}; - - auto min = f.min() == 0 ? 0 : (f.min() - 1); - auto max = f.max() == formula::unbounded() - ? formula::unbounded() - : (f.max() - 1); - - auto E_i_j_minus = formula::FStar(E, min, max); - - auto exp = rec(E); - ExpansionBuilder res(d); - for (const auto& [li, ei] : exp) - { - res.insert(li, formula::Fusion({ei, E_i_j_minus})); - - if (ei.accepts_eword() && f.min() != 0) - { - for (const auto& [ki, fi] : rec(E_i_j_minus)) - { - // FIXME: build bdd once - if ((li & ki) != bddfalse) - res.insert(li & ki, fi); - } - } - } - if (f.min() == 0) - res.insert(bddtrue, formula::eword()); - - res.finalize(opts & exp_opts::Deterministic); - return res.result(); - } - - case op::Star: - { - auto min = f.min() == 0 ? 0 : (f.min() - 1); - auto max = f.max() == formula::unbounded() - ? formula::unbounded() - : (f.max() - 1); - - auto exps = rec(f[0]); - - ExpansionBuilder res(d); - for (const auto& [bdd_l, form] : exps) - { - res.insert(bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})); - } - - res.finalize(opts & exp_opts::Deterministic); - return res.result(); - } - - case op::AndNLM: - { - formula rewrite = rewrite_and_nlm(f); - return rec(rewrite); - } - - case op::first_match: - { - auto exps = rec(f[0]); - - ExpansionBuilder res(d); - for (const auto& [bdd_l, form] : exps) - { - res.insert(bdd_l, form); - } - - res.finalize(true); - auto res2 = res.result(); - for (auto& [_, dest] : res2) - dest = formula::first_match(dest); - return res2; - } - - case op::Fusion: - { - ExpansionBuilder res(d); - formula E = f[0]; - formula F = f.all_but(0); - - expansion_t Ei = rec(E); - // TODO: std::option - expansion_t Fj = rec(F); - - for (const auto& [li, ei] : Ei) - { - if (ei.accepts_eword()) - { - for (const auto& [kj, fj] : Fj) - if ((li & kj) != bddfalse) - res.insert(li & kj, fj); - } - res.insert(li, formula::Fusion({ei, F})); - } - - res.finalize(opts & exp_opts::Deterministic); - return res.result(); - } - - case op::AndRat: - { - ExpansionBuilder res(d); - for (const auto& sub_f : f) - { - auto exps = rec(sub_f); - - if (exps.empty()) - { - // op::AndRat: one of the expansions was empty (the only - // edge was `false`), so the AndRat is empty as - // well - res.clear(); - break; - } - - if (res.empty()) - { - res = ExpansionBuilder(std::move(exps), d); - res.finalize(false); - continue; - } - - ExpansionBuilder new_res(d); - for (const auto& [l_key, l_val] : exps) - { - for (const auto& [r_key, r_val] : res.result()) - { - if ((l_key & r_key) != bddfalse) - new_res.insert(l_key & r_key, formula::multop(f.kind(), {l_val, r_val})); - } - } - - res = std::move(new_res); - res.finalize(false); - } - - res.finalize(opts & exp_opts::Deterministic); - return res.result(); - } - - case op::OrRat: - { - ExpansionBuilder res(d); - for (const auto& sub_f : f) - { - auto exps = rec(sub_f); - if (exps.empty()) - continue; - - if (res.empty()) - { - res = ExpansionBuilder(std::move(exps), d); - continue; - } - - for (const auto& [label, dest] : exps) - res.insert(label, dest); - } - - res.finalize(opts & exp_opts::Deterministic); - return res.result(); - } - - default: - std::cerr << "unimplemented kind " - << static_cast(f.kind()) - << std::endl; - SPOT_UNIMPLEMENTED(); - } - - return {}; - } - - expansion_t - expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts) - { - - if (opts & exp_opts::Bdd) - return expansion_impl(f, d, owner, opts); - else if (opts & exp_opts::MergeSuffix) - return expansion_impl(f, d, owner, opts); - else // exp_opts::Basic - return expansion_impl(f, d, owner, opts); - } - twa_graph_ptr expand_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts) { @@ -1589,181 +668,9 @@ namespace spot } aut->set_named_prop("state-names", state_names); - aut->merge_edges(); - return aut; - } - twa_graph_ptr - expand_simple_automaton(formula f, bdd_dict_ptr d) - { - auto finite = expand_simple_finite_automaton(f, d); - return from_finite(finite); - } - - twa_graph_ptr - expand_simple_finite_automaton(formula f, bdd_dict_ptr d) - { - auto aut = make_twa_graph(d); - - aut->prop_state_acc(true); - const auto acc_mark = aut->set_buchi(); - - auto formula2state = robin_hood::unordered_map(); - - unsigned init_state = aut->new_state(); - aut->set_init_state(init_state); - formula2state.insert({ f, init_state }); - - auto f_aps = formula_aps(f); - for (auto& ap : f_aps) - aut->register_ap(ap); - - auto todo = std::vector>(); - todo.push_back({f, init_state}); - - auto state_names = new std::vector(); - std::ostringstream ss; - ss << f; - state_names->push_back(ss.str()); - - auto find_dst = [&](formula suffix) -> unsigned - { - unsigned dst; - auto it = formula2state.find(suffix); - if (it != formula2state.end()) - { - dst = it->second; - } - else - { - dst = aut->new_state(); - todo.push_back({suffix, dst}); - formula2state.insert({suffix, dst}); - std::ostringstream ss; - ss << suffix; - state_names->push_back(ss.str()); - } - - return dst; - }; - - while (!todo.empty()) - { - auto [curr_f, curr_state] = todo[todo.size() - 1]; - todo.pop_back(); - - auto curr_acc_mark= curr_f.accepts_eword() - ? acc_mark - : acc_cond::mark_t(); - - auto exp = expansion_simple(curr_f, d, aut.get()); - - for (const auto& [letter, suffix] : exp) - { - if (suffix.is(op::ff)) - continue; - - auto dst = find_dst(suffix); - aut->new_edge(curr_state, dst, letter, curr_acc_mark); - } - - // if state has no transitions and should be accepting, create - // artificial transition - if (aut->get_graph().state_storage(curr_state).succ == 0 - && curr_f.accepts_eword()) - aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); - } - - aut->set_named_prop("state-names", state_names); - aut->merge_edges(); - return aut; - } - - twa_graph_ptr - expand_new_automaton(formula f, bdd_dict_ptr d, exp_opts_new::expand_opt_new opts) - { - auto finite = expand_new_finite_automaton(f, d, opts); - return from_finite(finite); - } - - twa_graph_ptr - expand_new_finite_automaton(formula f, bdd_dict_ptr d, exp_opts_new::expand_opt_new opts) - { - auto aut = make_twa_graph(d); - - aut->prop_state_acc(true); - const auto acc_mark = aut->set_buchi(); - - auto formula2state = robin_hood::unordered_map(); - - unsigned init_state = aut->new_state(); - aut->set_init_state(init_state); - formula2state.insert({ f, init_state }); - - auto f_aps = formula_aps(f); - for (auto& ap : f_aps) - aut->register_ap(ap); - - auto todo = std::vector>(); - todo.push_back({f, init_state}); - - auto state_names = new std::vector(); - std::ostringstream ss; - ss << f; - state_names->push_back(ss.str()); - - auto find_dst = [&](formula suffix) -> unsigned - { - unsigned dst; - auto it = formula2state.find(suffix); - if (it != formula2state.end()) - { - dst = it->second; - } - else - { - dst = aut->new_state(); - todo.push_back({suffix, dst}); - formula2state.insert({suffix, dst}); - std::ostringstream ss; - ss << suffix; - state_names->push_back(ss.str()); - } - - return dst; - }; - - while (!todo.empty()) - { - auto [curr_f, curr_state] = todo[todo.size() - 1]; - todo.pop_back(); - - auto curr_acc_mark= curr_f.accepts_eword() - ? acc_mark - : acc_cond::mark_t(); - - auto exp = expansion_new(curr_f, d, aut.get(), opts); - - for (const auto& [letter, suffix] : exp) - { - if (suffix.is(op::ff)) - continue; - - auto dst = find_dst(suffix); - aut->new_edge(curr_state, dst, letter, curr_acc_mark); - } - - // if state has no transitions and should be accepting, create - // artificial transition - if (aut->get_graph().state_storage(curr_state).succ == 0 - && curr_f.accepts_eword()) - aut->new_edge(curr_state, curr_state, bddfalse, acc_mark); - } - - aut->set_named_prop("state-names", state_names); - - if ((opts & exp_opts_new::MergeEdges) - && !(opts & exp_opts_new::UniqueSuffix)) + if ((opts & exp_opts::MergeEdges) + && !(opts & exp_opts::UniqueSuffix)) aut->merge_edges(); return aut; diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 52e83917f..43a51e721 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -30,22 +30,11 @@ namespace spot { - using expansion_t = std::map; + using expansion_t = std::multimap; struct exp_opts { enum expand_opt { - None = 0, - Deterministic = 1, - Basic = 2, - MergeSuffix = 4, - Bdd = 8, - }; - }; - - struct exp_opts_new - { - enum expand_opt_new { None = 0, UniqueSuffix = 1, UniquePrefix = 2, @@ -55,33 +44,15 @@ namespace spot }; }; - SPOT_API std::multimap - expansion_new(formula f, const bdd_dict_ptr& d, void *owner, exp_opts_new::expand_opt_new opts); - - SPOT_API twa_graph_ptr - expand_new_automaton(formula f, bdd_dict_ptr d, exp_opts_new::expand_opt_new opts); - - SPOT_API twa_graph_ptr - expand_new_finite_automaton(formula f, bdd_dict_ptr d, exp_opts_new::expand_opt_new opts); - - SPOT_API twa_graph_ptr - expand_simple_automaton(formula f, bdd_dict_ptr d); - - SPOT_API twa_graph_ptr - expand_simple_finite_automaton(formula f, bdd_dict_ptr d); - - SPOT_API std::multimap - expansion_simple(formula f, const bdd_dict_ptr& d, void *owner); - SPOT_API expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts); - SPOT_API formula - expansion_to_formula(expansion_t e, bdd_dict_ptr& d); - SPOT_API twa_graph_ptr expand_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts); SPOT_API twa_graph_ptr expand_finite_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts); + + SPOT_API formula + expansion_to_formula(expansion_t e, bdd_dict_ptr& d); } From bbbcdc331af136253cc1574620063ad92ec200ad Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 4 Apr 2023 14:52:14 +0200 Subject: [PATCH 44/66] expansions: optimize sigma star encoding --- spot/tl/expansions.cc | 31 ++++++++++++++++++++++++++----- spot/tl/expansions.hh | 3 ++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 4a6e6ca0c..39e7b0303 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -106,9 +106,10 @@ namespace spot class bdd_finalizer { public: - bdd_finalizer(std::multimap& exp, bdd_dict_ptr d) + bdd_finalizer(std::multimap& exp, bdd_dict_ptr d, bool opt_sigma_star) : anon_set_(bddtrue) , d_(d) + , opt_sigma_star_(opt_sigma_star) { for (const auto& [prefix, suffix] : exp) { @@ -120,12 +121,25 @@ namespace spot } else { - anon_var_num = d_->register_anonymous_variables(1, this); + if (opt_sigma_star_ && (suffix.is(op::Star) + && suffix[0].is(op::tt) + && suffix.min() == 0 + && suffix.max() == formula::unbounded())) + { + anon_var_num = -1; + } + else + { + anon_var_num = d_->register_anonymous_variables(1, this); + } + formula2bdd_.insert({suffix, anon_var_num}); bdd2formula_.insert({anon_var_num, suffix}); } - bdd var = bdd_ithvar(anon_var_num); + bdd var = bddtrue; + if (anon_var_num != -1) + var = bdd_ithvar(anon_var_num); anon_set_ &= var; exp_ |= prefix & var; } @@ -145,6 +159,7 @@ namespace spot std::map formula2bdd_; std::map bdd2formula_; bdd_dict_ptr d_; + bool opt_sigma_star_; formula var_to_formula(int var); formula conj_bdd_to_sere(bdd b); @@ -177,7 +192,13 @@ namespace spot bdd_finalizer::conj_bdd_to_sere(bdd b) { if (b == bddtrue) - return formula::tt(); + { + if (opt_sigma_star_){ + return formula::Star(formula::tt(), 0, formula::unbounded()); + } else { + return formula::tt(); + } + } if (b == bddfalse) return formula::ff(); @@ -261,7 +282,7 @@ namespace spot if (opts & (exp_opts::expand_opt::BddIsop | exp_opts::expand_opt::BddMinterm)) { - bdd_finalizer bddf(exp, d); + bdd_finalizer bddf(exp, d, opts & exp_opts::expand_opt::BddSigmaStar); exp = bddf.simplify(opts); } diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 43a51e721..1d2fbedba 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -40,7 +40,8 @@ namespace spot UniquePrefix = 2, BddIsop = 4, BddMinterm = 8, - MergeEdges = 16, + BddSigmaStar = 16, + MergeEdges = 32, }; }; From 931d39e73998a40a618abb9bbca4562b3de95d50 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 12 Apr 2023 15:15:36 +0200 Subject: [PATCH 45/66] expansions: signature merge impl --- spot/tl/expansions.cc | 40 ++++++++++++++++++++++++++++++++++++++++ spot/tl/expansions.hh | 1 + 2 files changed, 41 insertions(+) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 39e7b0303..dc0e09182 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -614,20 +614,44 @@ namespace spot return from_finite(finite); } + struct signature_hash + { + std::size_t + operator() (const std::pair>& sig) const + { + size_t hash = std::hash()(sig.first); + + for (const auto& keyvalue : sig.second) + { + hash ^= (bdd_hash()(keyvalue.first) ^ std::hash()(keyvalue.second)) + + 0x9e3779b9 + (hash << 6) + (hash >> 2); + } + + return hash; + } + }; + twa_graph_ptr expand_finite_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts) { + bool signature_merge = opts & exp_opts::expand_opt::SignatureMerge; + auto aut = make_twa_graph(d); aut->prop_state_acc(true); const auto acc_mark = aut->set_buchi(); auto formula2state = robin_hood::unordered_map(); + auto signature2state = std::unordered_map, unsigned, signature_hash>(); unsigned init_state = aut->new_state(); aut->set_init_state(init_state); formula2state.insert({ f, init_state }); + if (signature_merge) + signature2state.insert({ {f.accepts_eword(), expansion(f, d, aut.get(), opts)}, init_state}); + + auto f_aps = formula_aps(f); for (auto& ap : f_aps) aut->register_ap(ap); @@ -650,9 +674,25 @@ namespace spot } else { + if (signature_merge) + { + auto exp = expansion(suffix, d, aut.get(), opts); + bool accepting = suffix.accepts_eword(); + auto it2 = signature2state.find({accepting, exp}); + if (it2 != signature2state.end()) + { + formula2state.insert({suffix, it2->second}); + return it2->second; + } + } + dst = aut->new_state(); todo.push_back({suffix, dst}); + formula2state.insert({suffix, dst}); + if (signature_merge) + signature2state.insert({{suffix.accepts_eword(), expansion(suffix, d, aut.get(), opts)}, dst}); + std::ostringstream ss; ss << suffix; state_names->push_back(ss.str()); diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 1d2fbedba..0aec0a106 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -42,6 +42,7 @@ namespace spot BddMinterm = 8, BddSigmaStar = 16, MergeEdges = 32, + SignatureMerge = 64, }; }; From f09c1dd7f32e018be75e636889c5d92868eb4059 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 12 May 2023 08:45:59 +0200 Subject: [PATCH 46/66] expansions: simple determinization --- spot/tl/expansions.cc | 23 +++++++++++++++++++++++ spot/tl/expansions.hh | 6 ++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index dc0e09182..5fb13c0de 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -325,6 +325,29 @@ namespace spot exp.insert({prefix, suffix}); } } + + if (opts & exp_opts::expand_opt::Determinize) + { + std::multimap exp_new; + + bdd props = bddtrue; + for (const auto& [prefix, _] : exp) + props &= bdd_support(prefix); + + std::vector dests; + for (bdd letter : minterms_of(bddtrue, props)) + { + for (const auto& [prefix, suffix] : exp) + { + if (bdd_implies(letter, prefix)) + dests.push_back(suffix); + } + formula or_dests = formula::OrRat(dests); + exp_new.insert({letter, or_dests}); + dests.clear(); + } + exp = exp_new; + } } } diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 0aec0a106..949b25e29 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -41,8 +41,10 @@ namespace spot BddIsop = 4, BddMinterm = 8, BddSigmaStar = 16, - MergeEdges = 32, - SignatureMerge = 64, + BddEncode = 32, + MergeEdges = 64, + SignatureMerge = 128, + Determinize = 256, }; }; From 29e0b22c2a898522346f74ffedb3ccfba1ac32f1 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 4 Jul 2023 07:21:20 +0200 Subject: [PATCH 47/66] expansions: fixes + BDD encode changes + printer --- spot/tl/expansions.cc | 98 +++++++++++++++++++++++++++++++------------ spot/tl/expansions.hh | 3 ++ 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 5fb13c0de..1086b0f67 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -106,41 +106,63 @@ namespace spot class bdd_finalizer { public: - bdd_finalizer(std::multimap& exp, bdd_dict_ptr d, bool opt_sigma_star) - : anon_set_(bddtrue) - , d_(d) - , opt_sigma_star_(opt_sigma_star) - { - for (const auto& [prefix, suffix] : exp) + int encode(formula f) { - int anon_var_num; - auto it = formula2bdd_.find(suffix); + bool is_anon = false; + int var_num; + auto it = formula2bdd_.find(f); if (it != formula2bdd_.end()) { - anon_var_num = it->second; + var_num = it->second; } else { - if (opt_sigma_star_ && (suffix.is(op::Star) - && suffix[0].is(op::tt) - && suffix.min() == 0 - && suffix.max() == formula::unbounded())) + if (opt_sigma_star_ && (f.is(op::Star) + && f[0].is(op::tt) + && f.min() == 0 + && f.max() == formula::unbounded())) { - anon_var_num = -1; + var_num = bddtrue.id(); + } + else if (opt_bdd_encode_ && (f.is(op::AndRat) || f.is(op::OrRat))) + { + bdd var = f.is(op::AndRat) ? bdd(bddtrue) : bdd(bddfalse); + for (const auto& sub_f : f) + { + int bddid = encode(sub_f); + bdd subvar = bdd_ithvar(bddid); + var = f.is(op::AndRat) ? var & subvar : var | subvar; + } + var_num = var.id(); } else { - anon_var_num = d_->register_anonymous_variables(1, this); + var_num = d_->register_anonymous_variables(1, this); + is_anon = true; } - formula2bdd_.insert({suffix, anon_var_num}); - bdd2formula_.insert({anon_var_num, suffix}); + formula2bdd_.insert({f, var_num}); + bdd2formula_.insert({var_num, f}); } - bdd var = bddtrue; - if (anon_var_num != -1) - var = bdd_ithvar(anon_var_num); - anon_set_ &= var; + bdd var = bdd_ithvar(var_num); + + if (is_anon) + anon_set_ &= var; + + return var_num; + } + + bdd_finalizer(std::multimap& exp, bdd_dict_ptr d, bool opt_sigma_star, bool opt_bdd_encode) + : anon_set_(bddtrue) + , d_(d) + , opt_sigma_star_(opt_sigma_star) + , opt_bdd_encode_(opt_bdd_encode) + { + for (const auto& [prefix, suffix] : exp) + { + int var_num = encode(suffix); + bdd var = bdd_ithvar(var_num); exp_ |= prefix & var; } } @@ -160,6 +182,7 @@ namespace spot std::map bdd2formula_; bdd_dict_ptr d_; bool opt_sigma_star_; + bool opt_bdd_encode_; formula var_to_formula(int var); formula conj_bdd_to_sere(bdd b); @@ -249,7 +272,8 @@ namespace spot { bdd prop_set = bdd_exist(bdd_support(exp_), anon_set_); bdd or_labels = bdd_exist(exp_, anon_set_); - for (bdd letter: minterms_of(exp_, prop_set)) + // TODO: check are_equivalent avec or_labels/exp_ en premier argument + for (bdd letter: minterms_of(or_labels, prop_set)) { bdd dest_bdd = bdd_restrict(exp_, letter); formula dest = bdd_to_sere(dest_bdd); @@ -282,7 +306,7 @@ namespace spot if (opts & (exp_opts::expand_opt::BddIsop | exp_opts::expand_opt::BddMinterm)) { - bdd_finalizer bddf(exp, d, opts & exp_opts::expand_opt::BddSigmaStar); + bdd_finalizer bddf(exp, d, opts & exp_opts::expand_opt::BddSigmaStar, opts & exp_opts::expand_opt::BddEncode); exp = bddf.simplify(opts); } @@ -365,6 +389,14 @@ namespace spot return formula::OrRat(res); } + void print_expansion(const expansion_t& exp, const bdd_dict_ptr& dict) + { + for (const auto& [prefix, suffix] : exp) + { + std::cout << bdd_to_formula(prefix, dict) << ": " << suffix << std::endl; + } + } + expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts) @@ -581,15 +613,26 @@ namespace spot } exp_t new_res; + bool inserted = false; for (const auto& [l_key, l_val] : exps) { for (const auto& [r_key, r_val] : res) { if ((l_key & r_key) != bddfalse) - new_res.insert({l_key & r_key, formula::multop(f.kind(), {l_val, r_val})}); + { + new_res.insert({l_key & r_key, formula::multop(f.kind(), {l_val, r_val})}); + inserted = true; + } } } + if (!inserted) + { + // all prefix conjuctions led to bddfalse, And is empty + res.clear(); + break; + } + res = std::move(new_res); } @@ -671,14 +714,15 @@ namespace spot aut->set_init_state(init_state); formula2state.insert({ f, init_state }); - if (signature_merge) - signature2state.insert({ {f.accepts_eword(), expansion(f, d, aut.get(), opts)}, init_state}); - auto f_aps = formula_aps(f); for (auto& ap : f_aps) aut->register_ap(ap); + if (signature_merge) + signature2state.insert({ {f.accepts_eword(), expansion(f, d, aut.get(), opts)}, init_state}); + + auto todo = std::vector>(); todo.push_back({f, init_state}); diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 949b25e29..eba71db9e 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -59,4 +59,7 @@ namespace spot SPOT_API formula expansion_to_formula(expansion_t e, bdd_dict_ptr& d); + + SPOT_API void + print_expansion(const expansion_t& exp, const bdd_dict_ptr& dict); } From e50be0692d65f9fbb59c33e9729789001f12f0c6 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 20 Sep 2023 17:52:18 +0200 Subject: [PATCH 48/66] expansions: UniquePrefixSeenOpt --- spot/tl/expansions.cc | 71 +++++++++++++++++++++++++++++++------------ spot/tl/expansions.hh | 3 +- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 1086b0f67..8b0309246 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -301,7 +301,7 @@ namespace spot } void - finalize(expansion_t& exp, exp_opts::expand_opt opts, bdd_dict_ptr d) + finalize(expansion_t& exp, exp_opts::expand_opt opts, bdd_dict_ptr d, std::unordered_set* seen) { if (opts & (exp_opts::expand_opt::BddIsop | exp_opts::expand_opt::BddMinterm)) @@ -344,9 +344,37 @@ namespace spot } exp.clear(); + for (const auto [prefix, suffix] : unique_map) { - exp.insert({prefix, suffix}); + if ((opts & exp_opts::expand_opt::UniquePrefixSeenOpt) + && suffix.is(op::OrRat)) + { + std::vector merge; + std::vector single; + + for (const auto& sub_f : suffix) + { + if (seen->find(sub_f) != seen->end()) + { + single.push_back(sub_f); + } + else + { + merge.push_back(sub_f); + } + } + + for (const auto& sub_f : single) + exp.insert({prefix, sub_f}); + + if (!merge.empty()) + exp.insert({prefix, formula::OrRat(merge)}); + } + else + { + exp.insert({prefix, suffix}); + } } } @@ -399,7 +427,7 @@ namespace spot expansion_t - expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts) + expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts, std::unordered_set* seen) { using exp_t = std::multimap; @@ -413,8 +441,8 @@ namespace spot return {{f_bdd, formula::eword()}}; } - auto rec = [&d, owner, opts](formula f){ - return expansion(f, d, owner, exp_opts::None); + auto rec = [&d, owner, seen](formula f){ + return expansion(f, d, owner, exp_opts::None, seen); }; @@ -426,7 +454,8 @@ namespace spot SPOT_UNREACHABLE(); case op::eword: - return {{bddfalse, formula::ff()}}; + // return {{bddfalse, formula::ff()}}; + return {}; case op::Concat: { @@ -447,7 +476,7 @@ namespace spot } } - finalize(res, opts, d); + finalize(res, opts, d, seen); return res; } @@ -484,7 +513,7 @@ namespace spot if (f.min() == 0) res.insert({bddtrue, formula::eword()}); - finalize(res, opts, d); + finalize(res, opts, d, seen); return res; } @@ -503,7 +532,7 @@ namespace spot res.insert({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); } - finalize(res, opts, d); + finalize(res, opts, d, seen); return res; } @@ -511,7 +540,7 @@ namespace spot { formula rewrite = rewrite_and_nlm(f); auto res = rec(rewrite); - finalize(res, opts, d); + finalize(res, opts, d, seen); return res; } @@ -541,7 +570,7 @@ namespace spot { for (auto& [_, dest] : res) dest = formula::first_match(dest); - finalize(res, opts, d); + finalize(res, opts, d, seen); return res; } @@ -561,7 +590,7 @@ namespace spot for (auto& [_, dest] : res_det) dest = formula::first_match(dest); - finalize(res_det, opts, d); + finalize(res_det, opts, d, seen); return res_det; } @@ -586,7 +615,7 @@ namespace spot res.insert({li, formula::Fusion({ei, F})}); } - finalize(res, opts, d); + finalize(res, opts, d, seen); return res; } @@ -636,7 +665,7 @@ namespace spot res = std::move(new_res); } - finalize(res, opts, d); + finalize(res, opts, d, seen); return res; } @@ -659,7 +688,7 @@ namespace spot res.insert({label, dest}); } - finalize(res, opts, d); + finalize(res, opts, d, seen); return res; } @@ -709,6 +738,8 @@ namespace spot auto formula2state = robin_hood::unordered_map(); auto signature2state = std::unordered_map, unsigned, signature_hash>(); + auto seen = std::unordered_set(); + seen.insert(f); unsigned init_state = aut->new_state(); aut->set_init_state(init_state); @@ -720,7 +751,7 @@ namespace spot aut->register_ap(ap); if (signature_merge) - signature2state.insert({ {f.accepts_eword(), expansion(f, d, aut.get(), opts)}, init_state}); + signature2state.insert({ {f.accepts_eword(), expansion(f, d, aut.get(), opts, &seen)}, init_state}); auto todo = std::vector>(); @@ -743,7 +774,7 @@ namespace spot { if (signature_merge) { - auto exp = expansion(suffix, d, aut.get(), opts); + auto exp = expansion(suffix, d, aut.get(), opts, &seen); bool accepting = suffix.accepts_eword(); auto it2 = signature2state.find({accepting, exp}); if (it2 != signature2state.end()) @@ -755,10 +786,11 @@ namespace spot dst = aut->new_state(); todo.push_back({suffix, dst}); + seen.insert(suffix); formula2state.insert({suffix, dst}); if (signature_merge) - signature2state.insert({{suffix.accepts_eword(), expansion(suffix, d, aut.get(), opts)}, dst}); + signature2state.insert({{suffix.accepts_eword(), expansion(suffix, d, aut.get(), opts, &seen)}, dst}); std::ostringstream ss; ss << suffix; @@ -777,11 +809,12 @@ namespace spot ? acc_mark : acc_cond::mark_t(); - auto exp = expansion(curr_f, d, aut.get(), opts); + auto exp = expansion(curr_f, d, aut.get(), opts, &seen); for (const auto& [letter, suffix] : exp) { if (suffix.is(op::ff)) + // TODO ASSERT NOT continue; auto dst = find_dst(suffix); diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index eba71db9e..36476bd31 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -45,11 +45,12 @@ namespace spot MergeEdges = 64, SignatureMerge = 128, Determinize = 256, + UniquePrefixSeenOpt = 512, }; }; SPOT_API expansion_t - expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts); + expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts, std::unordered_set* seen = nullptr); SPOT_API twa_graph_ptr expand_automaton(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts); From d760d2cb3b65f094fb507585ad32255a2845a151 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 27 Sep 2023 11:36:17 +0200 Subject: [PATCH 49/66] expansions: US order in pipeline configurable --- spot/tl/expansions.cc | 24 ++++++++++++++++++++++-- spot/tl/expansions.hh | 3 ++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 8b0309246..564468452 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -310,7 +310,7 @@ namespace spot exp = bddf.simplify(opts); } - if (opts & exp_opts::expand_opt::UniqueSuffix) + if (opts & exp_opts::expand_opt::UniqueSuffixPre) { std::map unique_map; for (const auto& [prefix, suffix] : exp) @@ -378,6 +378,26 @@ namespace spot } } + if (opts & exp_opts::expand_opt::UniqueSuffixPost) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({suffix, prefix}); + if (!res.second) + { + auto it = res.first; + it->second |= prefix; + } + } + + exp.clear(); + for (const auto [suffix, prefix] : unique_map) + { + exp.insert({prefix, suffix}); + } + } + if (opts & exp_opts::expand_opt::Determinize) { std::multimap exp_new; @@ -831,7 +851,7 @@ namespace spot aut->set_named_prop("state-names", state_names); if ((opts & exp_opts::MergeEdges) - && !(opts & exp_opts::UniqueSuffix)) + && !(opts & exp_opts::UniqueSuffixPre || opts & exp_opts::UniqueSuffixPost)) aut->merge_edges(); return aut; diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 36476bd31..2418b1103 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -36,7 +36,7 @@ namespace spot { enum expand_opt { None = 0, - UniqueSuffix = 1, + UniqueSuffixPre = 1, UniquePrefix = 2, BddIsop = 4, BddMinterm = 8, @@ -46,6 +46,7 @@ namespace spot SignatureMerge = 128, Determinize = 256, UniquePrefixSeenOpt = 512, + UniqueSuffixPost = 1024, }; }; From 90ea02d42a59b8b6edb24538dcad05bf2f718032 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 12 Oct 2023 15:04:06 +0200 Subject: [PATCH 50/66] expansions: store as vector of pairs --- spot/tl/expansions.cc | 56 +++++++++++++++++++++++-------------------- spot/tl/expansions.hh | 2 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 564468452..ea1ec4b95 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -153,7 +153,7 @@ namespace spot return var_num; } - bdd_finalizer(std::multimap& exp, bdd_dict_ptr d, bool opt_sigma_star, bool opt_bdd_encode) + bdd_finalizer(expansion_t& exp, bdd_dict_ptr d, bool opt_sigma_star, bool opt_bdd_encode) : anon_set_(bddtrue) , d_(d) , opt_sigma_star_(opt_sigma_star) @@ -278,9 +278,13 @@ namespace spot bdd dest_bdd = bdd_restrict(exp_, letter); formula dest = bdd_to_sere(dest_bdd); - auto it = res.insert({letter, dest}); - assert(it.second); - (void) it; + #ifndef NDEBUG + // make sure it didn't exist before + auto it = std::find(res.begin(), res.end(), {letter, dest}); + SPOT_ASSERT(it == res.end()); + #endif + + res.push_back({letter, dest}); } } else // BddIsop @@ -293,7 +297,7 @@ namespace spot bdd suffix = bdd_existcomp(cube, anon_set_); formula dest = conj_bdd_to_sere(suffix); - res.insert({letter, dest}); + res.push_back({letter, dest}); } } @@ -326,7 +330,7 @@ namespace spot exp.clear(); for (const auto [suffix, prefix] : unique_map) { - exp.insert({prefix, suffix}); + exp.push_back({prefix, suffix}); } } @@ -366,14 +370,14 @@ namespace spot } for (const auto& sub_f : single) - exp.insert({prefix, sub_f}); + exp.push_back({prefix, sub_f}); if (!merge.empty()) - exp.insert({prefix, formula::OrRat(merge)}); + exp.push_back({prefix, formula::OrRat(merge)}); } else { - exp.insert({prefix, suffix}); + exp.push_back({prefix, suffix}); } } } @@ -394,13 +398,13 @@ namespace spot exp.clear(); for (const auto [suffix, prefix] : unique_map) { - exp.insert({prefix, suffix}); + exp.push_back({prefix, suffix}); } } if (opts & exp_opts::expand_opt::Determinize) { - std::multimap exp_new; + expansion_t exp_new; bdd props = bddtrue; for (const auto& [prefix, _] : exp) @@ -415,7 +419,7 @@ namespace spot dests.push_back(suffix); } formula or_dests = formula::OrRat(dests); - exp_new.insert({letter, or_dests}); + exp_new.push_back({letter, or_dests}); dests.clear(); } exp = exp_new; @@ -449,7 +453,7 @@ namespace spot expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts, std::unordered_set* seen) { - using exp_t = std::multimap; + using exp_t = expansion_t; if (f.is_boolean()) { @@ -484,7 +488,7 @@ namespace spot exp_t res; for (const auto& [bdd_l, form] : exps) { - res.insert({bdd_l, formula::Concat({form, f.all_but(0)})}); + res.push_back({bdd_l, formula::Concat({form, f.all_but(0)})}); } if (f[0].accepts_eword()) @@ -492,7 +496,7 @@ namespace spot auto exps_rest = rec(f.all_but(0)); for (const auto& [bdd_l, form] : exps_rest) { - res.insert({bdd_l, form}); + res.push_back({bdd_l, form}); } } @@ -518,7 +522,7 @@ namespace spot exp_t res; for (const auto& [li, ei] : exp) { - res.insert({li, formula::Fusion({ei, E_i_j_minus})}); + res.push_back({li, formula::Fusion({ei, E_i_j_minus})}); if (ei.accepts_eword() && f.min() != 0) { @@ -526,12 +530,12 @@ namespace spot { // FIXME: build bdd once if ((li & ki) != bddfalse) - res.insert({li & ki, fi}); + res.push_back({li & ki, fi}); } } } if (f.min() == 0) - res.insert({bddtrue, formula::eword()}); + res.push_back({bddtrue, formula::eword()}); finalize(res, opts, d, seen); return res; @@ -549,7 +553,7 @@ namespace spot exp_t res; for (const auto& [bdd_l, form] : exps) { - res.insert({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); + res.push_back({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); } finalize(res, opts, d, seen); @@ -571,7 +575,7 @@ namespace spot exp_t res; for (const auto& [bdd_l, form] : exps) { - res.insert({bdd_l, form}); + res.push_back({bdd_l, form}); } // determinize @@ -604,7 +608,7 @@ namespace spot dests.push_back(ndet_dest); } formula or_dests = formula::OrRat(dests); - res_det.insert({l, or_dests}); + res_det.push_back({l, or_dests}); dests.clear(); } @@ -630,9 +634,9 @@ namespace spot { for (const auto& [kj, fj] : Fj) if ((li & kj) != bddfalse) - res.insert({li & kj, fj}); + res.push_back({li & kj, fj}); } - res.insert({li, formula::Fusion({ei, F})}); + res.push_back({li, formula::Fusion({ei, F})}); } finalize(res, opts, d, seen); @@ -669,7 +673,7 @@ namespace spot { if ((l_key & r_key) != bddfalse) { - new_res.insert({l_key & r_key, formula::multop(f.kind(), {l_val, r_val})}); + new_res.push_back({l_key & r_key, formula::multop(f.kind(), {l_val, r_val})}); inserted = true; } } @@ -705,7 +709,7 @@ namespace spot } for (const auto& [label, dest] : exps) - res.insert({label, dest}); + res.push_back({label, dest}); } finalize(res, opts, d, seen); @@ -732,7 +736,7 @@ namespace spot struct signature_hash { std::size_t - operator() (const std::pair>& sig) const + operator() (const std::pair& sig) const { size_t hash = std::hash()(sig.first); diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 2418b1103..9a350dbb8 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -30,7 +30,7 @@ namespace spot { - using expansion_t = std::multimap; + using expansion_t = std::vector>; struct exp_opts { From ed3d1ef4aab0abde62f25ba4a0225afffd087164 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 12 Oct 2023 15:04:40 +0200 Subject: [PATCH 51/66] expansions: expose easy expansion in python --- python/spot/impl.i | 2 ++ spot/tl/expansions.cc | 15 +++++++++++++++ spot/tl/expansions.hh | 3 +++ 3 files changed, 20 insertions(+) diff --git a/python/spot/impl.i b/python/spot/impl.i index 725655c08..ee2c6a81e 100644 --- a/python/spot/impl.i +++ b/python/spot/impl.i @@ -547,6 +547,8 @@ namespace std { %template(vectorofvectorofformulas) vector>; %template(setunsigned) set; %template(relabeling_map) map; + %template(pair_formula) pair; + %template(vector_pair_formula) vector>; } %include diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index ea1ec4b95..263f9a182 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -449,6 +449,21 @@ namespace spot } } + std::vector> + expansion_simple(formula f) + { + int owner = 42; + auto d = make_bdd_dict(); + + auto exp = expansion(f, d, &owner, exp_opts::None); + + std::vector> res; + for (const auto& [bdd, f] : exp) + res.push_back({bdd_to_formula(bdd, d), f}); + + d->unregister_all_my_variables(&owner); + return res; + } expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts, std::unordered_set* seen) diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 9a350dbb8..036ac945a 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -50,6 +50,9 @@ namespace spot }; }; + SPOT_API std::vector> + expansion_simple(formula f); + SPOT_API expansion_t expansion(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts, std::unordered_set* seen = nullptr); From b15c0818c533087fd9cb3c55fa7d3f74dd018fb2 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 8 Feb 2024 13:39:11 +0100 Subject: [PATCH 52/66] expansions: up variants --- spot/tl/expansions.cc | 154 ++++++++++++++++++++++++++++++------------ 1 file changed, 110 insertions(+), 44 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 263f9a182..756af4082 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -304,6 +304,113 @@ namespace spot return res; } + expansion_t + unique_prefix(const expansion_t& exp) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({prefix, suffix}); + if (!res.second) + { + auto it = res.first; + it->second = formula::OrRat({it->second, suffix}); + } + } + + expansion_t res(unique_map.begin(), unique_map.end()); + return res; + } + + expansion_t + unique_prefix_seen(const expansion_t& exp, std::unordered_set* seen) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({prefix, suffix}); + if (!res.second) + { + auto it = res.first; + it->second = formula::OrRat({it->second, suffix}); + } + } + + expansion_t res; + + for (const auto [prefix, suffix] : unique_map) + { + if (!suffix.is(op::OrRat)) + { + res.push_back({prefix, suffix}); + continue; + } + + std::vector merge; + std::vector single; + + for (const auto& sub_f : suffix) + { + if (seen->find(sub_f) != seen->end()) + { + single.push_back(sub_f); + } + else + { + merge.push_back(sub_f); + } + } + + for (const auto& sub_f : single) + res.push_back({prefix, sub_f}); + + if (!merge.empty()) + res.push_back({prefix, formula::OrRat(merge)}); + } + + return res; + } + + size_t count_new(const expansion_t& exp, std::unordered_set* seen) + { + size_t count = 0; + for (const auto& [_, suffix] : exp) + { + if (seen->find(suffix) == seen->end()) + count++; + } + return count; + } + + const expansion_t& + find_smallest(const expansion_t& left, + const expansion_t& right, + std::unordered_set* seen) + { + size_t left_new = count_new(left, seen); + size_t right_new = count_new(right, seen); + + if (left_new < right_new) + return left; + + if (left_new == right_new && left.size() > right.size()) + return right; + + return right; + } + + expansion_t + unique_prefix_count(const expansion_t& exp, std::unordered_set* seen) + { + expansion_t up = unique_prefix(exp); + expansion_t up_seen = unique_prefix_seen(exp, seen); + + const expansion_t& maybe_smallest = find_smallest(exp, up, seen); + const expansion_t& smallest = find_smallest(maybe_smallest, up_seen, seen); + + return smallest; + } + void finalize(expansion_t& exp, exp_opts::expand_opt opts, bdd_dict_ptr d, std::unordered_set* seen) { @@ -336,50 +443,9 @@ namespace spot if (opts & exp_opts::expand_opt::UniquePrefix) { - std::map unique_map; - for (const auto& [prefix, suffix] : exp) - { - auto res = unique_map.insert({prefix, suffix}); - if (!res.second) - { - auto it = res.first; - it->second = formula::OrRat({it->second, suffix}); - } - } - - exp.clear(); - - for (const auto [prefix, suffix] : unique_map) - { - if ((opts & exp_opts::expand_opt::UniquePrefixSeenOpt) - && suffix.is(op::OrRat)) - { - std::vector merge; - std::vector single; - - for (const auto& sub_f : suffix) - { - if (seen->find(sub_f) != seen->end()) - { - single.push_back(sub_f); - } - else - { - merge.push_back(sub_f); - } - } - - for (const auto& sub_f : single) - exp.push_back({prefix, sub_f}); - - if (!merge.empty()) - exp.push_back({prefix, formula::OrRat(merge)}); - } - else - { - exp.push_back({prefix, suffix}); - } - } + exp = unique_prefix(exp); + //exp = unique_prefix_seen(exp, seen); + //exp = unique_prefix_count(exp, seen); } if (opts & exp_opts::expand_opt::UniqueSuffixPost) From 7cbf544d3384ff1167dfc06e74ad42ac350b3161 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 19 Sep 2024 08:41:04 +0200 Subject: [PATCH 53/66] expansions: split --- python/spot/impl.i | 2 + spot/tl/Makefile.am | 3 +- spot/tl/expansions.cc | 31 +- spot/tl/expansions.hh | 2 + spot/tl/expansions2.cc | 930 +++++++++++++++++++++++++++++++++++++++++ spot/tl/expansions2.hh | 45 ++ tests/Makefile.am | 2 - 7 files changed, 999 insertions(+), 16 deletions(-) create mode 100644 spot/tl/expansions2.cc create mode 100644 spot/tl/expansions2.hh diff --git a/python/spot/impl.i b/python/spot/impl.i index ee2c6a81e..a435758f6 100644 --- a/python/spot/impl.i +++ b/python/spot/impl.i @@ -89,6 +89,7 @@ #include #include #include +#include #include #include #include @@ -638,6 +639,7 @@ namespace std { %include %include %include +%include %include %include %include diff --git a/spot/tl/Makefile.am b/spot/tl/Makefile.am index abb431267..f2ff0fcad 100644 --- a/spot/tl/Makefile.am +++ b/spot/tl/Makefile.am @@ -32,7 +32,7 @@ tl_HEADERS = \ dot.hh \ environment.hh \ exclusive.hh \ - expansions.hh \ + expansions2.hh \ formula.hh \ hierarchy.hh \ length.hh \ @@ -60,6 +60,7 @@ libtl_la_SOURCES = \ dot.cc \ exclusive.cc \ expansions.cc \ + expansions2.cc \ formula.cc \ hierarchy.cc \ length.cc \ diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 756af4082..c09aec083 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -278,11 +278,11 @@ namespace spot bdd dest_bdd = bdd_restrict(exp_, letter); formula dest = bdd_to_sere(dest_bdd); - #ifndef NDEBUG - // make sure it didn't exist before - auto it = std::find(res.begin(), res.end(), {letter, dest}); - SPOT_ASSERT(it == res.end()); - #endif + // #ifndef NDEBUG + // // make sure it didn't exist before + // auto it = std::find(res.begin(), res.end(), {letter, dest}); + // SPOT_ASSERT(it == res.end()); + // #endif res.push_back({letter, dest}); } @@ -338,7 +338,7 @@ namespace spot expansion_t res; - for (const auto [prefix, suffix] : unique_map) + for (const auto& [prefix, suffix] : unique_map) { if (!suffix.is(op::OrRat)) { @@ -435,17 +435,22 @@ namespace spot } exp.clear(); - for (const auto [suffix, prefix] : unique_map) + for (const auto& [suffix, prefix] : unique_map) { exp.push_back({prefix, suffix}); } } - if (opts & exp_opts::expand_opt::UniquePrefix) + if (opts & exp_opts::expand_opt::UniquePrefix + || opts & exp_opts::expand_opt::UniquePrefixSeenOpt + || opts & exp_opts::expand_opt::UniquePrefixSeenCountOpt) { - exp = unique_prefix(exp); - //exp = unique_prefix_seen(exp, seen); - //exp = unique_prefix_count(exp, seen); + if (opts & exp_opts::expand_opt::UniquePrefixSeenOpt) + exp = unique_prefix_seen(exp, seen); + else if (opts & exp_opts::expand_opt::UniquePrefixSeenCountOpt) + exp = unique_prefix_count(exp, seen); + else + exp = unique_prefix(exp); } if (opts & exp_opts::expand_opt::UniqueSuffixPost) @@ -462,7 +467,7 @@ namespace spot } exp.clear(); - for (const auto [suffix, prefix] : unique_map) + for (const auto& [suffix, prefix] : unique_map) { exp.push_back({prefix, suffix}); } @@ -817,7 +822,7 @@ namespace spot struct signature_hash { std::size_t - operator() (const std::pair& sig) const + operator() (const std::pair& sig) const noexcept { size_t hash = std::hash()(sig.first); diff --git a/spot/tl/expansions.hh b/spot/tl/expansions.hh index 036ac945a..9db8d2c8d 100644 --- a/spot/tl/expansions.hh +++ b/spot/tl/expansions.hh @@ -47,6 +47,8 @@ namespace spot Determinize = 256, UniquePrefixSeenOpt = 512, UniqueSuffixPost = 1024, + UniquePrefixSeenCountOpt = 2048, + TransitionBased = 4096, }; }; diff --git a/spot/tl/expansions2.cc b/spot/tl/expansions2.cc new file mode 100644 index 000000000..012ac11e8 --- /dev/null +++ b/spot/tl/expansions2.cc @@ -0,0 +1,930 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2021 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 +#include + +namespace spot +{ + namespace + { + // FIXME: could probably just return a map directly + static std::vector + formula_aps(formula f) + { + auto res = std::unordered_set(); + + f.traverse([&res](formula f) + { + if (f.is(op::ap)) + { + res.insert(f.ap_name()); + return true; + } + + return false; + }); + + return std::vector(res.begin(), res.end()); + } + + formula + rewrite_and_nlm(formula f) + { + unsigned s = f.size(); + std::vector final; + std::vector non_final; + + for (auto g: f) + if (g.accepts_eword()) + final.emplace_back(g); + else + non_final.emplace_back(g); + + if (non_final.empty()) + // (a* & b*);c = (a*|b*);c + return formula::OrRat(std::move(final)); + if (!final.empty()) + { + // let F_i be final formulae + // N_i be non final formula + // (F_1 & ... & F_n & N_1 & ... & N_m) + // = (F_1 | ... | F_n);[*] && (N_1 & ... & N_m) + // | (F_1 | ... | F_n) && (N_1 & ... & N_m);[*] + formula f = formula::OrRat(std::move(final)); + formula n = formula::AndNLM(std::move(non_final)); + formula t = formula::one_star(); + formula ft = formula::Concat({f, t}); + formula nt = formula::Concat({n, t}); + formula ftn = formula::AndRat({ft, n}); + formula fnt = formula::AndRat({f, nt}); + return formula::OrRat({ftn, fnt}); + } + // No final formula. + // Translate N_1 & N_2 & ... & N_n into + // N_1 && (N_2;[*]) && ... && (N_n;[*]) + // | (N_1;[*]) && N_2 && ... && (N_n;[*]) + // | (N_1;[*]) && (N_2;[*]) && ... && N_n + formula star = formula::one_star(); + std::vector disj; + for (unsigned n = 0; n < s; ++n) + { + std::vector conj; + for (unsigned m = 0; m < s; ++m) + { + formula g = f[m]; + if (n != m) + g = formula::Concat({g, star}); + conj.emplace_back(g); + } + disj.emplace_back(formula::AndRat(std::move(conj))); + } + return formula::OrRat(std::move(disj)); + } + + class bdd_finalizer + { + public: + int encode(formula f) + { + bool is_anon = false; + int var_num; + auto it = formula2bdd_.find(f); + if (it != formula2bdd_.end()) + { + var_num = it->second; + } + else + { + if (opt_sigma_star_ && (f.is(op::Star) + && f[0].is(op::tt) + && f.min() == 0 + && f.max() == formula::unbounded())) + { + var_num = bddtrue.id(); + } + else if (opt_bdd_encode_ && (f.is(op::AndRat) || f.is(op::OrRat))) + { + bdd var = f.is(op::AndRat) ? bdd(bddtrue) : bdd(bddfalse); + for (const auto& sub_f : f) + { + int bddid = encode(sub_f); + bdd subvar = bdd_ithvar(bddid); + var = f.is(op::AndRat) ? var & subvar : var | subvar; + } + var_num = var.id(); + } + else + { + var_num = d_->register_anonymous_variables(1, this); + is_anon = true; + } + + formula2bdd_.insert({f, var_num}); + bdd2formula_.insert({var_num, f}); + } + + bdd var = bdd_ithvar(var_num); + + if (is_anon) + anon_set_ &= var; + + return var_num; + } + + bdd_finalizer(expansion_t& exp, bdd_dict_ptr d, bool opt_sigma_star, bool opt_bdd_encode) + : anon_set_(bddtrue) + , d_(d) + , opt_sigma_star_(opt_sigma_star) + , opt_bdd_encode_(opt_bdd_encode) + { + for (const auto& [prefix, suffix] : exp) + { + int var_num = encode(suffix); + bdd var = bdd_ithvar(var_num); + exp_ |= prefix & var; + } + } + + ~bdd_finalizer() + { + d_->unregister_all_my_variables(this); + } + + expansion_t + simplify(exp_opts::expand_opt opts); + + private: + bdd exp_; + bdd anon_set_; + std::map formula2bdd_; + std::map bdd2formula_; + bdd_dict_ptr d_; + bool opt_sigma_star_; + bool opt_bdd_encode_; + + formula var_to_formula(int var); + formula conj_bdd_to_sere(bdd b); + formula bdd_to_sere(bdd b); + }; + + formula + bdd_finalizer::var_to_formula(int var) + { + formula f = bdd2formula_[var]; + assert(f); + return f; + } + + formula + bdd_finalizer::bdd_to_sere(bdd f) + { + if (f == bddfalse) + return formula::ff(); + + std::vector v; + minato_isop isop(f); + bdd cube; + while ((cube = isop.next()) != bddfalse) + v.emplace_back(conj_bdd_to_sere(cube)); + return formula::OrRat(std::move(v)); + } + + formula + bdd_finalizer::conj_bdd_to_sere(bdd b) + { + if (b == bddtrue) + { + if (opt_sigma_star_){ + return formula::Star(formula::tt(), 0, formula::unbounded()); + } else { + return formula::tt(); + } + } + if (b == bddfalse) + return formula::ff(); + + // Unroll the first loop of the next do/while loop so that we + // do not have to create v when b is not a conjunction. + formula res = var_to_formula(bdd_var(b)); + bdd high = bdd_high(b); + if (high == bddfalse) + { + res = formula::Not(res); + b = bdd_low(b); + } + else + { + assert(bdd_low(b) == bddfalse); + b = high; + } + if (b == bddtrue) + return res; + std::vector v{std::move(res)}; + do + { + res = var_to_formula(bdd_var(b)); + high = bdd_high(b); + if (high == bddfalse) + { + res = formula::Not(res); + b = bdd_low(b); + } + else + { + assert(bdd_low(b) == bddfalse); + b = high; + } + assert(b != bddfalse); + v.emplace_back(std::move(res)); + } + while (b != bddtrue); + return formula::multop(op::AndRat, std::move(v)); + } + + expansion_t + bdd_finalizer::simplify(exp_opts::expand_opt opts) + { + expansion_t res; + + if (opts & exp_opts::expand_opt::BddMinterm) + { + bdd prop_set = bdd_exist(bdd_support(exp_), anon_set_); + bdd or_labels = bdd_exist(exp_, anon_set_); + // TODO: check are_equivalent avec or_labels/exp_ en premier argument + for (bdd letter: minterms_of(or_labels, prop_set)) + { + bdd dest_bdd = bdd_restrict(exp_, letter); + formula dest = bdd_to_sere(dest_bdd); + + // #ifndef NDEBUG + // // make sure it didn't exist before + // auto it = std::find(res.begin(), res.end(), {letter, dest}); + // SPOT_ASSERT(it == res.end()); + // #endif + + res.push_back({letter, dest}); + } + } + else // BddIsop + { + minato_isop isop(exp_); + bdd cube; + while ((cube = isop.next()) != bddfalse) + { + bdd letter = bdd_exist(cube, anon_set_); + bdd suffix = bdd_existcomp(cube, anon_set_); + formula dest = conj_bdd_to_sere(suffix); + + res.push_back({letter, dest}); + } + } + + return res; + } + + expansion_t + unique_prefix(const expansion_t& exp) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({prefix, suffix}); + if (!res.second) + { + auto it = res.first; + it->second = formula::OrRat({it->second, suffix}); + } + } + + expansion_t res(unique_map.begin(), unique_map.end()); + return res; + } + + expansion_t + unique_prefix_seen(const expansion_t& exp, std::unordered_set* seen) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({prefix, suffix}); + if (!res.second) + { + auto it = res.first; + it->second = formula::OrRat({it->second, suffix}); + } + } + + expansion_t res; + + for (const auto& [prefix, suffix] : unique_map) + { + if (!suffix.is(op::OrRat)) + { + res.push_back({prefix, suffix}); + continue; + } + + std::vector merge; + std::vector single; + + for (const auto& sub_f : suffix) + { + if (seen->find(sub_f) != seen->end()) + { + single.push_back(sub_f); + } + else + { + merge.push_back(sub_f); + } + } + + for (const auto& sub_f : single) + res.push_back({prefix, sub_f}); + + if (!merge.empty()) + res.push_back({prefix, formula::OrRat(merge)}); + } + + return res; + } + + size_t count_new(const expansion_t& exp, std::unordered_set* seen) + { + size_t count = 0; + for (const auto& [_, suffix] : exp) + { + if (seen->find(suffix) == seen->end()) + count++; + } + return count; + } + + const expansion_t& + find_smallest(const expansion_t& left, + const expansion_t& right, + std::unordered_set* seen) + { + size_t left_new = count_new(left, seen); + size_t right_new = count_new(right, seen); + + if (left_new < right_new) + return left; + + if (left_new == right_new && left.size() > right.size()) + return right; + + return right; + } + + expansion_t + unique_prefix_count(const expansion_t& exp, std::unordered_set* seen) + { + expansion_t up = unique_prefix(exp); + expansion_t up_seen = unique_prefix_seen(exp, seen); + + const expansion_t& maybe_smallest = find_smallest(exp, up, seen); + const expansion_t& smallest = find_smallest(maybe_smallest, up_seen, seen); + + return smallest; + } + + void + finalize(expansion_t& exp, exp_opts::expand_opt opts, bdd_dict_ptr d, std::unordered_set* seen) + { + if (opts & (exp_opts::expand_opt::BddIsop + | exp_opts::expand_opt::BddMinterm)) + { + bdd_finalizer bddf(exp, d, opts & exp_opts::expand_opt::BddSigmaStar, opts & exp_opts::expand_opt::BddEncode); + exp = bddf.simplify(opts); + } + + if (opts & exp_opts::expand_opt::UniqueSuffixPre) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({suffix, prefix}); + if (!res.second) + { + auto it = res.first; + it->second |= prefix; + } + } + + exp.clear(); + for (const auto& [suffix, prefix] : unique_map) + { + exp.push_back({prefix, suffix}); + } + } + + if (opts & exp_opts::expand_opt::UniquePrefix + || opts & exp_opts::expand_opt::UniquePrefixSeenOpt + || opts & exp_opts::expand_opt::UniquePrefixSeenCountOpt) + { + if (opts & exp_opts::expand_opt::UniquePrefixSeenOpt) + exp = unique_prefix_seen(exp, seen); + else if (opts & exp_opts::expand_opt::UniquePrefixSeenCountOpt) + exp = unique_prefix_count(exp, seen); + else + exp = unique_prefix(exp); + } + + if (opts & exp_opts::expand_opt::UniqueSuffixPost) + { + std::map unique_map; + for (const auto& [prefix, suffix] : exp) + { + auto res = unique_map.insert({suffix, prefix}); + if (!res.second) + { + auto it = res.first; + it->second |= prefix; + } + } + + exp.clear(); + for (const auto& [suffix, prefix] : unique_map) + { + exp.push_back({prefix, suffix}); + } + } + + if (opts & exp_opts::expand_opt::Determinize) + { + expansion_t exp_new; + + bdd props = bddtrue; + for (const auto& [prefix, _] : exp) + props &= bdd_support(prefix); + + std::vector dests; + for (bdd letter : minterms_of(bddtrue, props)) + { + for (const auto& [prefix, suffix] : exp) + { + if (bdd_implies(letter, prefix)) + dests.push_back(suffix); + } + formula or_dests = formula::OrRat(dests); + exp_new.push_back({letter, or_dests}); + dests.clear(); + } + exp = exp_new; + } + } + } + + formula + expansion_to_formula2(expansion_t e, bdd_dict_ptr& d) + { + std::vector res; + + for (const auto& [key, val] : e) + { + formula prefix = bdd_to_formula(key, d); + res.push_back(formula::Concat({prefix, val})); + } + + return formula::OrRat(res); + } + + expansion_t + expansion2(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts, std::unordered_set* seen) + { + using exp_t = expansion_t; + + if (f.is_boolean()) + { + auto f_bdd = formula_to_bdd(f, d, owner); + + if (f_bdd == bddfalse) + return {}; + + return {{f_bdd, formula::eword()}}; + } + + auto rec = [&d, owner, opts, seen](formula f){ + return expansion2(f, d, owner, exp_opts::None, seen); + }; + + + switch (f.kind()) + { + case op::ff: + case op::tt: + case op::ap: + SPOT_UNREACHABLE(); + + case op::eword: + // return {{bddfalse, formula::ff()}}; + return {}; + + case op::Concat: + { + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.push_back({bdd_l, formula::Concat({form, f.all_but(0)})}); + } + + if (f[0].accepts_eword()) + { + auto exps_rest = rec(f.all_but(0)); + for (const auto& [bdd_l, form] : exps_rest) + { + res.push_back({bdd_l, form}); + } + } + + finalize(res, opts, d, seen); + return res; + } + + case op::FStar: + { + formula E = f[0]; + + if (f.min() == 0 && f.max() == 0) + return {{bddtrue, formula::eword()}}; + + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto E_i_j_minus = formula::FStar(E, min, max); + + auto exp = rec(E); + exp_t res; + for (const auto& [li, ei] : exp) + { + res.push_back({li, formula::Fusion({ei, E_i_j_minus})}); + + if (ei.accepts_eword() && f.min() != 0) + { + for (const auto& [ki, fi] : rec(E_i_j_minus)) + { + // FIXME: build bdd once + if ((li & ki) != bddfalse) + res.push_back({li & ki, fi}); + } + } + } + if (f.min() == 0) + res.push_back({bddtrue, formula::eword()}); + + finalize(res, opts, d, seen); + return res; + } + + case op::Star: + { + auto min = f.min() == 0 ? 0 : (f.min() - 1); + auto max = f.max() == formula::unbounded() + ? formula::unbounded() + : (f.max() - 1); + + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.push_back({bdd_l, formula::Concat({form, formula::Star(f[0], min, max)})}); + } + + finalize(res, opts, d, seen); + return res; + } + + case op::AndNLM: + { + formula rewrite = rewrite_and_nlm(f); + auto res = rec(rewrite); + finalize(res, opts, d, seen); + return res; + } + + case op::first_match: + { + auto exps = rec(f[0]); + + exp_t res; + for (const auto& [bdd_l, form] : exps) + { + res.push_back({bdd_l, form}); + } + + // determinize + bdd or_labels = bddfalse; + bdd support = bddtrue; + bool is_det = true; + for (const auto& [l, _] : res) + { + support &= bdd_support(l); + if (is_det) + is_det = !bdd_have_common_assignment(l, or_labels); + or_labels |= l; + } + + if (is_det) + { + for (auto& [_, dest] : res) + dest = formula::first_match(dest); + finalize(res, opts, d, seen); + return res; + } + + exp_t res_det; + std::vector dests; + for (bdd l: minterms_of(or_labels, support)) + { + for (const auto& [ndet_label, ndet_dest] : res) + { + if (bdd_implies(l, ndet_label)) + dests.push_back(ndet_dest); + } + formula or_dests = formula::OrRat(dests); + res_det.push_back({l, or_dests}); + dests.clear(); + } + + for (auto& [_, dest] : res_det) + dest = formula::first_match(dest); + finalize(res_det, opts, d, seen); + return res_det; + } + + case op::Fusion: + { + exp_t res; + formula E = f[0]; + formula F = f.all_but(0); + + exp_t Ei = rec(E); + // TODO: std::option + exp_t Fj = rec(F); + + for (const auto& [li, ei] : Ei) + { + if (ei.accepts_eword()) + { + for (const auto& [kj, fj] : Fj) + if ((li & kj) != bddfalse) + res.push_back({li & kj, fj}); + } + res.push_back({li, formula::Fusion({ei, F})}); + } + + finalize(res, opts, d, seen); + return res; + } + + case op::AndRat: + { + exp_t res; + for (const auto& sub_f : f) + { + auto exps = rec(sub_f); + + if (exps.empty()) + { + // op::AndRat: one of the expansions was empty (the only + // edge was `false`), so the AndRat is empty as + // well + res.clear(); + break; + } + + if (res.empty()) + { + res = std::move(exps); + continue; + } + + exp_t new_res; + bool inserted = false; + for (const auto& [l_key, l_val] : exps) + { + for (const auto& [r_key, r_val] : res) + { + if ((l_key & r_key) != bddfalse) + { + new_res.push_back({l_key & r_key, formula::multop(f.kind(), {l_val, r_val})}); + inserted = true; + } + } + } + + if (!inserted) + { + // all prefix conjuctions led to bddfalse, And is empty + res.clear(); + break; + } + + res = std::move(new_res); + } + + finalize(res, opts, d, seen); + return res; + } + + case op::OrRat: + { + exp_t res; + for (const auto& sub_f : f) + { + auto exps = rec(sub_f); + if (exps.empty()) + continue; + + if (res.empty()) + { + res = std::move(exps); + continue; + } + + for (const auto& [label, dest] : exps) + res.push_back({label, dest}); + } + + finalize(res, opts, d, seen); + return res; + } + + default: + std::cerr << "unimplemented kind " + << static_cast(f.kind()) + << std::endl; + SPOT_UNIMPLEMENTED(); + } + + return {}; + } + + twa_graph_ptr + expand_automaton2(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts) + { + auto finite = expand_finite_automaton2(f, d, opts); + return from_finite(finite); + } + + struct signature_hash + { + std::size_t + operator() (const expansion_t& sig) const noexcept + { + size_t hash = 0; + + for (const auto& keyvalue : sig) + { + hash ^= (bdd_hash()(keyvalue.first) ^ std::hash()(keyvalue.second)) + + 0x9e3779b9 + (hash << 6) + (hash >> 2); + } + + return hash; + } + }; + + twa_graph_ptr + expand_finite_automaton2(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts) + { + bool signature_merge = opts & exp_opts::expand_opt::SignatureMerge; + + auto aut = make_twa_graph(d); + + aut->prop_state_acc(false); + const auto acc_mark = aut->set_buchi(); + + auto formula2state = robin_hood::unordered_map(); + auto signature2state = std::unordered_map(); + auto seen = std::unordered_set(); + seen.insert(f); + + unsigned init_state = aut->new_state(); + aut->set_init_state(init_state); + formula2state.insert({ f, init_state }); + + + auto f_aps = formula_aps(f); + for (auto& ap : f_aps) + aut->register_ap(ap); + + auto formula2signature = robin_hood::unordered_map(); + auto get_signature = [&](const formula& form) -> expansion_t + { + auto it = formula2signature.find(form); + if (it != formula2signature.end()) + { + return it->second; + } + auto exp = expansion2(form, d, aut.get(), opts, &seen); + formula2signature.insert({form, exp}); + return exp; + }; + + if (signature_merge) + signature2state.insert({ get_signature(f), init_state}); + + auto todo = std::vector>(); + todo.push_back({f, init_state}); + + auto state_names = new std::vector(); + std::ostringstream ss; + ss << f; + state_names->push_back(ss.str()); + + auto find_dst = [&](formula suffix) -> unsigned + { + unsigned dst; + auto it = formula2state.find(suffix); + if (it != formula2state.end()) + { + dst = it->second; + } + else + { + if (signature_merge) + { + auto exp = get_signature(suffix); + + auto it2 = signature2state.find(exp); + if (it2 != signature2state.end()) + { + formula2state.insert({suffix, it2->second}); + return it2->second; + } + } + + dst = aut->new_state(); + todo.push_back({suffix, dst}); + seen.insert(suffix); + + formula2state.insert({suffix, dst}); + if (signature_merge) + signature2state.insert({get_signature(suffix), dst}); + + std::ostringstream ss; + ss << suffix; + state_names->push_back(ss.str()); + } + + return dst; + }; + + while (!todo.empty()) + { + auto [curr_f, curr_state] = todo[todo.size() - 1]; + todo.pop_back(); + + + auto exp = get_signature(curr_f); + + for (const auto& [letter, suffix] : exp) + { + if (suffix.is(op::ff)) + // TODO ASSERT NOT + continue; + + auto dst = find_dst(suffix); + + auto curr_acc_mark = suffix.accepts_eword() ? acc_mark : acc_cond::mark_t(); + aut->new_edge(curr_state, dst, letter, curr_acc_mark); + } + } + + aut->set_named_prop("state-names", state_names); + + if ((opts & exp_opts::MergeEdges) + && !(opts & exp_opts::UniqueSuffixPre || opts & exp_opts::UniqueSuffixPost)) + aut->merge_edges(); + + return aut; + } +} diff --git a/spot/tl/expansions2.hh b/spot/tl/expansions2.hh new file mode 100644 index 000000000..e517bb87a --- /dev/null +++ b/spot/tl/expansions2.hh @@ -0,0 +1,45 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2021 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 . + +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include + +namespace spot +{ + SPOT_API expansion_t + expansion2(formula f, const bdd_dict_ptr& d, void *owner, exp_opts::expand_opt opts, std::unordered_set* seen = nullptr); + + SPOT_API twa_graph_ptr + expand_automaton2(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts); + + SPOT_API twa_graph_ptr + expand_finite_automaton2(formula f, bdd_dict_ptr d, exp_opts::expand_opt opts); + + SPOT_API formula + expansion_to_formula2(expansion_t e, bdd_dict_ptr& d); +} diff --git a/tests/Makefile.am b/tests/Makefile.am index a061ba23d..9bf0cef73 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -69,7 +69,6 @@ check_PROGRAMS = \ core/cube \ core/emptchk \ core/equals \ - core/expand \ core/graph \ core/kind \ core/length \ @@ -112,7 +111,6 @@ core_bricks_SOURCES = core/bricks.cc core_checkpsl_SOURCES = core/checkpsl.cc core_checkta_SOURCES = core/checkta.cc core_emptchk_SOURCES = core/emptchk.cc -core_expand_SOURCES = core/expand.cc core_graph_SOURCES = core/graph.cc core_ikwiad_SOURCES = core/ikwiad.cc core_intvcomp_SOURCES = core/intvcomp.cc From f687ef7bbb07d0c3600e40a91f22815007e2c3d7 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 24 Sep 2024 11:36:17 +0200 Subject: [PATCH 54/66] ltl2tgba_fm: switch for expansions --- spot/twaalgos/ltl2tgba_fm.cc | 59 ++++++++++++++++++++++++++++++++---- spot/twaalgos/ltl2tgba_fm.hh | 2 ++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/spot/twaalgos/ltl2tgba_fm.cc b/spot/twaalgos/ltl2tgba_fm.cc index dd7bb9182..fc296c919 100644 --- a/spot/twaalgos/ltl2tgba_fm.cc +++ b/spot/twaalgos/ltl2tgba_fm.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -101,15 +102,14 @@ namespace spot class ratexp_to_dfa final { typedef twa_graph::namer namer; + // Use robin_hood::pair because std::pair is not no-throw constructible + typedef robin_hood::pair labelled_aut; public: ratexp_to_dfa(translate_dict& dict, bool disable_scc_trimming = false); std::vector> succ(formula f); ~ratexp_to_dfa(); - protected: - // Use robin_hood::pair because std::pair is not no-throw constructible - typedef robin_hood::pair labelled_aut; labelled_aut translate(formula f); private: @@ -887,8 +887,25 @@ namespace spot bdd res; if (!f.is_boolean()) { - ratexp_trad_visitor v(dict, to_concat); - res = v.visit(f); + if (sere_translation_options() == 0) + { + ratexp_trad_visitor v(dict, to_concat); + res = v.visit(f); + } + else // version expansion + { + // auto d = make_bdd_dict(); + res = bddfalse; + for (auto [label, succ]: expansion(f, dict.dict, nullptr, exp_opts::expand_opt::None, nullptr)) + { + // std::cout << label << ' ' << succ << std::endl; + if (to_concat) + succ = formula::Concat({succ, to_concat}); + // std::cout << succ << std::endl; + int x = dict.register_next_variable(succ); + res |= label & bdd_ithvar(x); + } + } } else { @@ -899,6 +916,7 @@ namespace spot int x = dict.register_next_variable(to_concat); res &= bdd_ithvar(x); } + // std::cout << res << std::endl; return res; } @@ -2199,7 +2217,8 @@ namespace spot translate_dict d(a, s, false, false, false); ratexp_to_dfa sere2dfa(d, disable_scc_trimming); - auto [dfa, namer, state] = sere2dfa.succ(f); + auto [dfa, namer] = sere2dfa.translate(f); + auto state = dfa->state_from_number(namer->get_state(f)); // language was empty, build an automaton with one non accepting state if (dfa == nullptr) @@ -2247,4 +2266,32 @@ namespace spot return res; } + + int sere_translation_options(const char* version) + { + static int pref = -1; + const char *env = nullptr; + if (!version && pref < 0) + version = env = getenv("SPOT_SERE_TRANSLATE_OPT"); + if (version) + { + if (!strcasecmp(version, "bdd")) + pref = 0; + else if (!strcasecmp(version, "expansion")) + pref = 1; + else + { + const char* err = ("sere_translation_options(): argument" + " should be one of {bdd,expansion}"); + if (env) + err = "SPOT_SERE_TRANSLATE_OPT should be one of {bdd,expansion}"; + throw std::runtime_error(err); + } + } + else if (pref < 0) + { + pref = 0; + } + return pref; + } } diff --git a/spot/twaalgos/ltl2tgba_fm.hh b/spot/twaalgos/ltl2tgba_fm.hh index 51de038e1..7b536d6fe 100644 --- a/spot/twaalgos/ltl2tgba_fm.hh +++ b/spot/twaalgos/ltl2tgba_fm.hh @@ -92,4 +92,6 @@ namespace spot SPOT_API twa_graph_ptr sere_to_tgba(formula f, const bdd_dict_ptr& dict, bool disable_scc_trimming = false); + + SPOT_API int sere_translation_options(const char* version = nullptr); } From dfa828739b0374b083d78a107ecea066dc478b8e Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 23 Oct 2024 13:56:00 +0200 Subject: [PATCH 55/66] translate_aa: setup translation choice --- spot/twaalgos/translate_aa.cc | 70 +++++++++++++++++++++++++++++++++-- spot/twaalgos/translate_aa.hh | 2 + 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index c18570d41..095f92c5a 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -18,9 +18,14 @@ // along with this program. If not, see . #include "config.h" + +#include + #include +#include #include #include +#include #include #include @@ -176,7 +181,6 @@ namespace spot { unsigned init_state = aut_->new_state(); - outedge_combiner oe(aut_, accepting_sink_); bdd comb = bddtrue; for (const auto& sub_formula : f) { @@ -285,7 +289,22 @@ namespace spot { unsigned rhs_init = recurse(f[1]); const auto& dict = aut_->get_dict(); - twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0], dict); + twa_graph_ptr sere_aut; + if (sere_aa_translation_options() == 0) + { + // old bdd method + sere_aut = sere_to_tgba(f[0], dict, true); + } + else if (sere_aa_translation_options() == 1) + { + // derivation + sere_aut = derive_finite_automaton_with_first(f[0], dict); + } + else + { + // linear form + sere_aut = expand_finite_automaton(f[0], dict, exp_opts::expand_opt::None); + } // TODO: this should be a std::vector ! std::vector acc_states; @@ -326,7 +345,22 @@ namespace spot { unsigned rhs_init = recurse(f[1]); const auto& dict = aut_->get_dict(); - twa_graph_ptr sere_aut = derive_finite_automaton_with_first(f[0], dict); + twa_graph_ptr sere_aut; + if (sere_aa_translation_options() == 0) + { + // old bdd method + sere_aut = sere_to_tgba(f[0], dict, true); + } + else if (sere_aa_translation_options() == 1) + { + // derivation + sere_aut = derive_finite_automaton_with_first(f[0], dict); + } + else + { + // linear form + sere_aut = expand_finite_automaton(f[0], dict, exp_opts::expand_opt::None); + } // DFA recognizes the empty language, so {0} []-> rhs is always true unsigned ns = sere_aut->num_states(); @@ -482,4 +516,34 @@ namespace spot return aut; } + + int sere_aa_translation_options(const char* version) + { + static int pref = -1; + const char *env = nullptr; + if (!version && pref < 0) + version = env = getenv("SPOT_SERE_AA_TRANSLATE_OPT"); + if (version) + { + if (!strcasecmp(version, "bdd")) + pref = 0; + else if (!strcasecmp(version, "derive")) + pref = 1; + else if (!strcasecmp(version, "expansion")) + pref = 2; + else + { + const char* err = ("sere_aa_translation_options(): argument" + " should be one of {bdd,derive,expansion}"); + if (env) + err = "SPOT_SERE_AA_TRANSLATE_OPT should be one of {bdd,derive,expansion}"; + throw std::runtime_error(err); + } + } + else if (pref < 0) + { + pref = 0; + } + return pref; + } } diff --git a/spot/twaalgos/translate_aa.hh b/spot/twaalgos/translate_aa.hh index 9a8760072..aedcf07d4 100644 --- a/spot/twaalgos/translate_aa.hh +++ b/spot/twaalgos/translate_aa.hh @@ -29,4 +29,6 @@ namespace spot { SPOT_API twa_graph_ptr ltl_to_aa(formula f, bdd_dict_ptr& dict, bool purge_dead_states = false); + + SPOT_API int sere_aa_translation_options(const char* version = nullptr); } From a32431c34152debdb4865cc931fecea59981a1e4 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 23 Oct 2024 13:59:51 +0200 Subject: [PATCH 56/66] ltl2tgba_fm: setup switch between bdd and exp --- spot/twaalgos/ltl2tgba_fm.cc | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/spot/twaalgos/ltl2tgba_fm.cc b/spot/twaalgos/ltl2tgba_fm.cc index fc296c919..c5a8c9d02 100644 --- a/spot/twaalgos/ltl2tgba_fm.cc +++ b/spot/twaalgos/ltl2tgba_fm.cc @@ -224,6 +224,7 @@ namespace spot int register_proposition(formula f) { + // TODO: call this in expansions int num = dict->register_proposition(f, this); var_set &= bdd_ithvar(num); return num; @@ -894,14 +895,11 @@ namespace spot } else // version expansion { - // auto d = make_bdd_dict(); res = bddfalse; - for (auto [label, succ]: expansion(f, dict.dict, nullptr, exp_opts::expand_opt::None, nullptr)) + for (auto [label, succ]: expansion(f, dict.dict, &dict, exp_opts::expand_opt::None, nullptr)) { - // std::cout << label << ' ' << succ << std::endl; if (to_concat) succ = formula::Concat({succ, to_concat}); - // std::cout << succ << std::endl; int x = dict.register_next_variable(succ); res |= label & bdd_ithvar(x); } @@ -916,7 +914,6 @@ namespace spot int x = dict.register_next_variable(to_concat); res &= bdd_ithvar(x); } - // std::cout << res << std::endl; return res; } @@ -2209,6 +2206,10 @@ namespace spot twa_graph_ptr sere_to_tgba(formula f, const bdd_dict_ptr& dict, bool disable_scc_trimming) { + // make sure we use bdd translation in this case + auto old_opt = sere_translation_options(); + sere_translation_options("bdd"); + f = negative_normal_form(f); tl_simplifier* s = new tl_simplifier(dict); @@ -2218,7 +2219,6 @@ namespace spot ratexp_to_dfa sere2dfa(d, disable_scc_trimming); auto [dfa, namer] = sere2dfa.translate(f); - auto state = dfa->state_from_number(namer->get_state(f)); // language was empty, build an automaton with one non accepting state if (dfa == nullptr) @@ -2264,6 +2264,10 @@ namespace spot res->set_named_prop("state-names", names); + // restore previous option + if (old_opt != 0) + sere_translation_options("expansion"); + return res; } From 7fa19736136956cc3e0c9ca830e1750b740b832d Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 23 Oct 2024 14:00:15 +0200 Subject: [PATCH 57/66] expansions: fusion can produce false let's discard the result if it's false --- spot/tl/expansions.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index c09aec083..9978d925d 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -722,7 +722,10 @@ namespace spot if ((li & kj) != bddfalse) res.push_back({li & kj, fj}); } - res.push_back({li, formula::Fusion({ei, F})}); + + formula ei_fusion_F = formula::Fusion({ei, F}); + if (!ei_fusion_F.is(op::ff)) + res.push_back({li, ei_fusion_F}); } finalize(res, opts, d, seen); From 37b814c75092b34580154769a32c409bbc93fb48 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 4 Mar 2025 19:20:50 +0100 Subject: [PATCH 58/66] expansions: make signature canonical Linear forms are now sorted and duplicates are removed --- spot/tl/expansions.cc | 18 ++++++++++++++++++ spot/tl/expansions2.cc | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 9978d925d..0896dd6b5 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -18,9 +18,11 @@ // along with this program. If not, see . #include "config.h" +#include #include #include #include +#include #include #include #include @@ -495,6 +497,22 @@ namespace spot } exp = exp_new; } + + // sort and remove duplicates from expansion to canonicalize it for + // eventual signature use + if (exp.size() >= 2) + { + std::sort(exp.begin(), exp.end(), + [](const auto& lhs, const auto& rhs) { + bdd_less_than_stable blt; + // first sort by label, then by suffix + if (blt(lhs.first, rhs.first)) + return true; + formula_ptr_less_than_bool_first flt; + return flt(lhs.second, rhs.second); + }); + exp.erase(std::unique(exp.begin(), exp.end()), exp.end()); + } } } diff --git a/spot/tl/expansions2.cc b/spot/tl/expansions2.cc index 012ac11e8..ecd98f5cb 100644 --- a/spot/tl/expansions2.cc +++ b/spot/tl/expansions2.cc @@ -18,9 +18,11 @@ // along with this program. If not, see . #include "config.h" +#include #include #include #include +#include #include #include #include @@ -495,6 +497,21 @@ namespace spot } exp = exp_new; } + + // sort expansion to canonicalize it for eventual signature use + if (exp.size() >= 2) + { + std::sort(exp.begin(), exp.end(), + [](const auto& lhs, const auto& rhs) { + bdd_less_than_stable blt; + // first sort by label, then by suffix + if (blt(lhs.first, rhs.first)) + return true; + formula_ptr_less_than_bool_first flt; + return flt(lhs.second, rhs.second); + }); + exp.erase(std::unique(exp.begin(), exp.end()), exp.end()); + } } } From 939942af304846192902e94ba9eb067d05667fb4 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 7 Mar 2025 11:24:00 +0100 Subject: [PATCH 59/66] expansions: fix sort behavior The previous implementation was wrong and led to segfaults when sorting large expansions --- spot/tl/expansions.cc | 10 +++------- spot/tl/expansions2.cc | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/spot/tl/expansions.cc b/spot/tl/expansions.cc index 0896dd6b5..402236ea6 100644 --- a/spot/tl/expansions.cc +++ b/spot/tl/expansions.cc @@ -504,13 +504,9 @@ namespace spot { std::sort(exp.begin(), exp.end(), [](const auto& lhs, const auto& rhs) { - bdd_less_than_stable blt; - // first sort by label, then by suffix - if (blt(lhs.first, rhs.first)) - return true; - formula_ptr_less_than_bool_first flt; - return flt(lhs.second, rhs.second); - }); + return std::make_pair(lhs.first.id(), lhs.second.id()) + < std::make_pair(rhs.first.id(), rhs.second.id()); + }); exp.erase(std::unique(exp.begin(), exp.end()), exp.end()); } } diff --git a/spot/tl/expansions2.cc b/spot/tl/expansions2.cc index ecd98f5cb..d80e5ffa3 100644 --- a/spot/tl/expansions2.cc +++ b/spot/tl/expansions2.cc @@ -503,13 +503,9 @@ namespace spot { std::sort(exp.begin(), exp.end(), [](const auto& lhs, const auto& rhs) { - bdd_less_than_stable blt; - // first sort by label, then by suffix - if (blt(lhs.first, rhs.first)) - return true; - formula_ptr_less_than_bool_first flt; - return flt(lhs.second, rhs.second); - }); + return std::make_pair(lhs.first.id(), lhs.second.id()) + < std::make_pair(rhs.first.id(), rhs.second.id()); + }); exp.erase(std::unique(exp.begin(), exp.end()), exp.end()); } } From 3d3f311733bbb83b6cf90ec6e13288cdee182860 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 13 Mar 2025 08:47:41 +0100 Subject: [PATCH 60/66] expansions: remove unused lambda capture --- spot/tl/expansions2.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spot/tl/expansions2.cc b/spot/tl/expansions2.cc index d80e5ffa3..96f970a94 100644 --- a/spot/tl/expansions2.cc +++ b/spot/tl/expansions2.cc @@ -540,7 +540,7 @@ namespace spot return {{f_bdd, formula::eword()}}; } - auto rec = [&d, owner, opts, seen](formula f){ + auto rec = [&d, owner, seen](formula f){ return expansion2(f, d, owner, exp_opts::None, seen); }; From c5746ef5cf1d9d939b5ddd1c987550c22c1c23df Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 13 Mar 2025 08:47:41 +0100 Subject: [PATCH 61/66] expansions: fix bogus false pairs in linear forms --- spot/tl/expansions2.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spot/tl/expansions2.cc b/spot/tl/expansions2.cc index 96f970a94..c40298d7f 100644 --- a/spot/tl/expansions2.cc +++ b/spot/tl/expansions2.cc @@ -711,7 +711,10 @@ namespace spot if ((li & kj) != bddfalse) res.push_back({li & kj, fj}); } - res.push_back({li, formula::Fusion({ei, F})}); + + formula ei_fusion_F = formula::Fusion({ei, F}); + if (!ei_fusion_F.is(op::ff)) + res.push_back({li, ei_fusion_F}); } finalize(res, opts, d, seen); From 59cfd6ed17a18c3371b23dd07b7aad1b5bc9752e Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 10 Oct 2025 16:49:34 +0200 Subject: [PATCH 62/66] sonf: fix recursion of rewriting, was only called on operand * spot/tl/sonf.cc: Here. --- spot/tl/sonf.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spot/tl/sonf.cc b/spot/tl/sonf.cc index 29b613eaa..0b70ab5e3 100644 --- a/spot/tl/sonf.cc +++ b/spot/tl/sonf.cc @@ -131,7 +131,7 @@ namespace spot { // recurse into rhs first (_ []-> rhs) formula rhs = - f[1].map(extractor, extracted, extractor, false, false); + extractor(f[1], extracted, extractor, false, false); f = formula::binop(kind, f[0], rhs); formula ap = formula::ap(new_ap_name()); From be3597cb467ff41c789754e1dbbb3c67dbac20cc Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 10 Oct 2025 16:49:34 +0200 Subject: [PATCH 63/66] translate_aa: rename expansion option to lf * spot/twaalgos/translate_aa.cc: Here. --- spot/twaalgos/translate_aa.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 095f92c5a..15633b4ec 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -529,12 +529,12 @@ namespace spot pref = 0; else if (!strcasecmp(version, "derive")) pref = 1; - else if (!strcasecmp(version, "expansion")) + else if (!strcasecmp(version, "lf")) pref = 2; else { const char* err = ("sere_aa_translation_options(): argument" - " should be one of {bdd,derive,expansion}"); + " should be one of {bdd,derive,lf}"); if (env) err = "SPOT_SERE_AA_TRANSLATE_OPT should be one of {bdd,derive,expansion}"; throw std::runtime_error(err); From 5156ac12865b9bd5b52691fdee63294a9991ae80 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 10 Oct 2025 16:49:34 +0200 Subject: [PATCH 64/66] translate_aa: Factorize sere translation choice * spot/twaalgos/translate_aa.cc: Here. --- spot/twaalgos/translate_aa.cc | 48 ++++++++++++----------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 15633b4ec..799a7e6f3 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -34,6 +34,20 @@ namespace spot { namespace { + twa_graph_ptr sere_aa_translate(formula f, const bdd_dict_ptr& dict) + { + // old bdd method + if (sere_aa_translation_options() == 0) + return sere_to_tgba(f, dict, true); + + // derivation + if (sere_aa_translation_options() == 1) + return derive_finite_automaton_with_first(f, dict); + + // linear form + return expand_finite_automaton(f, dict, exp_opts::expand_opt::None); + } + struct ltl_to_aa_builder { ltl_to_aa_builder(twa_graph_ptr aut, unsigned accepting_sink) @@ -289,22 +303,7 @@ namespace spot { unsigned rhs_init = recurse(f[1]); const auto& dict = aut_->get_dict(); - twa_graph_ptr sere_aut; - if (sere_aa_translation_options() == 0) - { - // old bdd method - sere_aut = sere_to_tgba(f[0], dict, true); - } - else if (sere_aa_translation_options() == 1) - { - // derivation - sere_aut = derive_finite_automaton_with_first(f[0], dict); - } - else - { - // linear form - sere_aut = expand_finite_automaton(f[0], dict, exp_opts::expand_opt::None); - } + twa_graph_ptr sere_aut = sere_aa_translate(f[0], dict); // TODO: this should be a std::vector ! std::vector acc_states; @@ -345,22 +344,7 @@ namespace spot { unsigned rhs_init = recurse(f[1]); const auto& dict = aut_->get_dict(); - twa_graph_ptr sere_aut; - if (sere_aa_translation_options() == 0) - { - // old bdd method - sere_aut = sere_to_tgba(f[0], dict, true); - } - else if (sere_aa_translation_options() == 1) - { - // derivation - sere_aut = derive_finite_automaton_with_first(f[0], dict); - } - else - { - // linear form - sere_aut = expand_finite_automaton(f[0], dict, exp_opts::expand_opt::None); - } + twa_graph_ptr sere_aut = sere_aa_translate(f[0], dict); // DFA recognizes the empty language, so {0} []-> rhs is always true unsigned ns = sere_aut->num_states(); From 8c6b1d90c62e5beec976505a2f927300f16bcb98 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 15 Oct 2025 14:24:08 +0200 Subject: [PATCH 65/66] translate_aa: expose lf-trans option * spot/twaalgos/translate_aa.cc: --- spot/twaalgos/translate_aa.cc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 799a7e6f3..163afaf3b 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -45,7 +46,11 @@ namespace spot return derive_finite_automaton_with_first(f, dict); // linear form - return expand_finite_automaton(f, dict, exp_opts::expand_opt::None); + if (sere_aa_translation_options() == 2) + return expand_finite_automaton(f, dict, exp_opts::expand_opt::None); + + // linear form - trans-based + return expand_finite_automaton2(f, dict, exp_opts::expand_opt::None); } struct ltl_to_aa_builder @@ -515,12 +520,14 @@ namespace spot pref = 1; else if (!strcasecmp(version, "lf")) pref = 2; + else if (!strcasecmp(version, "lft")) + pref = 3; else { const char* err = ("sere_aa_translation_options(): argument" - " should be one of {bdd,derive,lf}"); + " should be one of {bdd,derive,lf,lft}"); if (env) - err = "SPOT_SERE_AA_TRANSLATE_OPT should be one of {bdd,derive,expansion}"; + err = "SPOT_SERE_AA_TRANSLATE_OPT should be one of {bdd,derive,lf,lft}"; throw std::runtime_error(err); } } From bb33c5120fdcb778253627df6bf72ff96794ac5e Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 15 Oct 2025 14:24:08 +0200 Subject: [PATCH 66/66] translate_aa: fix construction for transition based acc * spot/twaalgos/translate_aa.cc: Here. --- spot/twaalgos/alternation.cc | 19 +++++++- spot/twaalgos/alternation.hh | 1 + spot/twaalgos/translate_aa.cc | 86 +++++++++++++++++++++++++---------- 3 files changed, 81 insertions(+), 25 deletions(-) diff --git a/spot/twaalgos/alternation.cc b/spot/twaalgos/alternation.cc index fb95fd6f3..8de30af15 100644 --- a/spot/twaalgos/alternation.cc +++ b/spot/twaalgos/alternation.cc @@ -39,6 +39,7 @@ namespace spot } bdd outedge_combiner::operator()(unsigned st, const std::vector& dst_filter, + const std::vector>& edge_filter, bool remove_original_edges) { const auto& dict = aut_->get_dict(); @@ -49,7 +50,23 @@ namespace spot for (auto& e: aut_->out(d1)) { // handle edge filtering - if (!dst_filter.empty()) + if (!edge_filter.empty()) + { + // Trying all univ dests for e, find if there was at least one + // compatible edge that was accepting in the original TFA + auto univ_dests = aut_->univ_dests(e.dst); + if (std::all_of(univ_dests.begin(), univ_dests.end(), + [&](unsigned dst) { + for (const auto& acc_e : edge_filter) + if(std::get<0>(acc_e) == e.src + && std::get<2>(acc_e) == dst + && bdd_implies(e.cond, std::get<1>(acc_e))) + return false; // false because we don't want to skip it + return true; + })) + continue; + } + else if (!dst_filter.empty()) // same for state-based acc { // if any edge destination is an accepting state in the SERE // automaton, handle the edge, otherwise skip it diff --git a/spot/twaalgos/alternation.hh b/spot/twaalgos/alternation.hh index 8d1027e8b..e2e719e1d 100644 --- a/spot/twaalgos/alternation.hh +++ b/spot/twaalgos/alternation.hh @@ -54,6 +54,7 @@ namespace spot outedge_combiner(const twa_graph_ptr& aut, unsigned sink = -1u); ~outedge_combiner(); bdd operator()(unsigned st, const std::vector& dst_filter = std::vector(), + const std::vector>& edge_filter = std::vector>(), bool remove_original_edges = false); void new_dests(unsigned st, bdd out) const; }; diff --git a/spot/twaalgos/translate_aa.cc b/spot/twaalgos/translate_aa.cc index 163afaf3b..6be7c4f85 100644 --- a/spot/twaalgos/translate_aa.cc +++ b/spot/twaalgos/translate_aa.cc @@ -97,10 +97,11 @@ namespace spot unsigned copy_sere_aut_to_res(twa_graph_ptr sere_aut, std::map& old_to_new, - std::vector* acc_states = nullptr, + std::vector* acc_edges = nullptr, bool use_accepting_sink = true) { unsigned ns = sere_aut->num_states(); + bool trans_based = sere_aut->prop_state_acc().is_false(); // TODO: create all new states at once, keeping an initial offset (the // number of states already present in aut_) @@ -111,8 +112,6 @@ namespace spot { unsigned new_st = aut_->new_state(); p.first->second = new_st; - if (acc_states != nullptr && sere_aut->state_is_accepting(st)) - acc_states->push_back(new_st); } return p.first->second; }; @@ -122,10 +121,22 @@ namespace spot unsigned new_st = register_state(st); for (const auto& e : sere_aut->out(st)) { - if (use_accepting_sink && sere_aut->state_is_accepting(e.dst)) - aut_->new_edge(new_st, accepting_sink_, e.cond); - else - aut_->new_edge(new_st, register_state(e.dst), e.cond); + bool edge_is_acc = ((trans_based && e.acc) + || (!trans_based && sere_aut->state_is_accepting(e.dst))); + + if (edge_is_acc) + { + // point to accepting sink instead of original dst if asked + if (use_accepting_sink) + aut_->new_edge(new_st, accepting_sink_, e.cond); + else + { + unsigned new_e = aut_->new_edge(new_st, register_state(e.dst), e.cond); + // remember if old edges were accepting + if (acc_edges != nullptr) + acc_edges->push_back(new_e); + } + } } } @@ -311,25 +322,19 @@ namespace spot twa_graph_ptr sere_aut = sere_aa_translate(f[0], dict); // TODO: this should be a std::vector ! - std::vector acc_states; - std::map old_to_new; - copy_sere_aut_to_res(sere_aut, old_to_new, &acc_states, false); - std::vector acc_edges; + std::map old_to_new; + copy_sere_aut_to_res(sere_aut, old_to_new, &acc_edges, false); + + // mark all edges from NFA in new automaton unsigned ns = sere_aut->num_states(); for (unsigned st = 0; st < ns; ++st) { auto it = old_to_new.find(st); assert(it != old_to_new.end()); unsigned new_st = it->second; - for (auto& e : aut_->out(new_st)) - { - e.acc = acc_cond::mark_t{0}; - if (std::find(acc_states.begin(), acc_states.end(), e.dst) - != acc_states.end()) - acc_edges.push_back(aut_->edge_number(e)); - } + e.acc = acc_cond::mark_t{0}; } for (unsigned i : acc_edges) @@ -350,13 +355,26 @@ namespace spot unsigned rhs_init = recurse(f[1]); const auto& dict = aut_->get_dict(); twa_graph_ptr sere_aut = sere_aa_translate(f[0], dict); + bool trans_based = sere_aut->prop_state_acc().is_false(); // DFA recognizes the empty language, so {0} []-> rhs is always true unsigned ns = sere_aut->num_states(); - bool has_accepting_state = false; - for (unsigned st = 0; st < ns && !has_accepting_state; ++st) - has_accepting_state = sere_aut->state_is_accepting(st); - if (!has_accepting_state) + bool accepts = false; + for (unsigned st = 0; st < ns && !accepts; ++st) + { + if (trans_based) + { + for (const auto& e : sere_aut->out(st)) + if (e.acc) + { + accepts = true; + break; + } + } + else + accepts = sere_aut->state_is_accepting(st); + } + if (!accepts) return accepting_sink_; std::map old_to_new; @@ -367,6 +385,8 @@ namespace spot std::vector univ_dest; // TODO: this should be a std::vector ! std::vector acc_states; + // any edge compatible with that should be considered accepting + std::vector> acc_edges; // registers a state in various maps and returns the index of the // anonymous bdd var representing that state @@ -381,7 +401,7 @@ namespace spot old_to_new.emplace(st, new_st); var_to_state.emplace(v, new_st); - if (sere_aut->state_is_accepting(st)) + if (!trans_based && sere_aut->state_is_accepting(st)) acc_states.push_back(new_st); vars &= bdd_ithvar(v); @@ -390,6 +410,15 @@ namespace spot return p.first->second; }; + // FIXME: this code handles dualization, but we cannot dualize if + // this situation arises: + // + // State: 0 + // [a] 1 + // [a] 2 {0} + // + // The quick fix is to simply determinize the NFA before dualizing, + // which removes any existentialism. aut_->copy_ap_of(sere_aut); for (unsigned st = 0; st < ns; ++st) { @@ -400,6 +429,15 @@ namespace spot { int st_bddi = register_state(e.dst); sig |= e.cond & bdd_ithvar(st_bddi); + + // register edge that was accepting in TFA + if (trans_based && e.acc) + { + unsigned new_src = old_to_new[e.src]; + unsigned new_dst = old_to_new[e.dst]; + + acc_edges.push_back({new_src, e.cond, new_dst}); + } } for (bdd cond : minterms_of(bddtrue, aps)) @@ -435,7 +473,7 @@ namespace spot unsigned new_st = it->second; bdd comb = bddtrue; - comb &= oe_(new_st, acc_states, true); + comb &= oe_(new_st, acc_states, acc_edges, true); if (comb != bddtrue) { comb &= oe_(rhs_init);