hoa: add option 'b' to build an alias-based basis for all labels

Related to issue #563.

* spot/twaalgos/hoa.hh (create_alias_basis): New function.
* spot/twaalgos/hoa.cc (create_alias_basis): New function.
(print_hoa): Add support for option 'b' and create_alias_basis
in this case.
* bin/common_aoutput.cc, NEWS: Document -Hb.
* tests/core/readsave.test, tests/python/aliases.py: Add test cases.
This commit is contained in:
Alexandre Duret-Lutz 2024-03-22 14:41:42 +01:00
parent 03a4f01184
commit 7e228e86ee
6 changed files with 129 additions and 12 deletions

7
NEWS
View file

@ -12,6 +12,13 @@ New in spot 2.11.6.dev (not yet released)
autfilt input.hoa -o output-%l.hoa autfilt input.hoa -o output-%l.hoa
- For tools that produce automata, using -Hb or --hoa=b will produce
an HOA file in which aliases are used to form a basis for the
whole set of labels. Those aliases are only used when more than
one atomic proposition is used (otherwise, the atomic proposition
and its negation is already a basis). This can help reducing the
size of large HOA files.
- ltlfilt has a new option --relabel-overlapping-bool=abc|pnn that - ltlfilt has a new option --relabel-overlapping-bool=abc|pnn that
will replace boolean subformulas by fresh atomic propositions even will replace boolean subformulas by fresh atomic propositions even
if those subformulas share atomic propositions. if those subformulas share atomic propositions.

View file

@ -132,6 +132,7 @@ static const argp_option options[] =
{ "hoaf", 'H', "1.1|i|k|l|m|s|t|v", OPTION_ARG_OPTIONAL, { "hoaf", 'H', "1.1|i|k|l|m|s|t|v", OPTION_ARG_OPTIONAL,
"Output the automaton in HOA format (default). Add letters to select " "Output the automaton in HOA format (default). Add letters to select "
"(1.1) version 1.1 of the format, " "(1.1) version 1.1 of the format, "
"(b) create an alias basis if >=2 AP are used, "
"(i) use implicit labels for complete deterministic automata, " "(i) use implicit labels for complete deterministic automata, "
"(s) prefer state-based acceptance when possible [default], " "(s) prefer state-based acceptance when possible [default], "
"(t) force transition-based acceptance, " "(t) force transition-based acceptance, "

View file

@ -20,6 +20,7 @@
#include <ostream> #include <ostream>
#include <sstream> #include <sstream>
#include <cstring> #include <cstring>
#include <algorithm>
#include <map> #include <map>
#include <spot/twa/twa.hh> #include <spot/twa/twa.hh>
#include <spot/twa/twagraph.hh> #include <spot/twa/twagraph.hh>
@ -30,6 +31,7 @@
#include <spot/tl/formula.hh> #include <spot/tl/formula.hh>
#include <spot/kripke/fairkripke.hh> #include <spot/kripke/fairkripke.hh>
#include <spot/kripke/kripkegraph.hh> #include <spot/kripke/kripkegraph.hh>
#include <spot/twaalgos/split.hh>
using namespace std::string_literals; using namespace std::string_literals;
@ -70,7 +72,8 @@ namespace spot
if (bdd_is_cube(a)) if (bdd_is_cube(a))
alias_cubes_.emplace_back(a, i); alias_cubes_.emplace_back(a, i);
bdd neg = !a; bdd neg = !a;
aliases_map_[neg.id()] = i; // do not overwrite an existing alias with a negation
aliases_map_.emplace(neg.id(), i);
if (bdd_is_cube(neg)) if (bdd_is_cube(neg))
alias_cubes_.emplace_back(neg, i); alias_cubes_.emplace_back(neg, i);
} }
@ -464,6 +467,7 @@ namespace spot
bool verbose = false; bool verbose = false;
bool state_labels = false; bool state_labels = false;
bool v1_1 = false; bool v1_1 = false;
bool alias_basis = false;
if (opt) if (opt)
while (*opt) while (*opt)
@ -486,6 +490,9 @@ namespace spot
v1_1 = false; v1_1 = false;
} }
break; break;
case 'b':
alias_basis = true;
break;
case 'i': case 'i':
implicit_labels = true; implicit_labels = true;
break; break;
@ -520,6 +527,27 @@ namespace spot
throw std::runtime_error("print_hoa(): automaton is declared not weak, " throw std::runtime_error("print_hoa(): automaton is declared not weak, "
"but the acceptance makes this impossible"); "but the acceptance makes this impossible");
// If we were asked to create an alias basis, make sure we save
// existing aliases, so we can restore it before we exit this
// function.
std::vector<std::pair<std::string, bdd>> old_aliases;
if (aut->ap().size() <= 1)
alias_basis = false;
if (alias_basis)
{
if (auto* aliases = get_aliases(aut))
old_aliases = *aliases;
create_alias_basis(std::const_pointer_cast<twa_graph>(aut));
}
// restore the old aliases using a unique_ptr-based scope guard,
// because there are too many ways to exit this function.
auto restore_aliases = [&old_aliases, alias_basis, aut](void*) {
if (alias_basis)
set_aliases(std::const_pointer_cast<twa_graph>(aut), old_aliases);
};
std::unique_ptr<void, decltype(restore_aliases)>
restore_aliases_guard((void*)1, restore_aliases);
metadata md(aut, implicit_labels, state_labels); metadata md(aut, implicit_labels, state_labels);
if (acceptance == Hoa_Acceptance_States && !md.has_state_acc) if (acceptance == Hoa_Acceptance_States && !md.has_state_acc)
@ -1013,7 +1041,8 @@ namespace spot
} }
void void
set_aliases(twa_ptr g, std::vector<std::pair<std::string, bdd>> aliases) set_aliases(twa_ptr g,
const std::vector<std::pair<std::string, bdd>>& aliases)
{ {
if (aliases.empty()) if (aliases.empty())
{ {
@ -1027,4 +1056,17 @@ namespace spot
} }
} }
void
create_alias_basis(const twa_graph_ptr& aut)
{
edge_separator es;
es.add_to_basis(aut);
std::vector<std::pair<std::string, bdd>> aliases;
unsigned n = 0;
for (bdd b: es.basis())
aliases.emplace_back(std::to_string(n++), b);
std::reverse(aliases.begin(), aliases.end());
set_aliases(aut, aliases);
}
} }

View file

@ -35,7 +35,8 @@ namespace spot
/// \param os The output stream to print on. /// \param os The output stream to print on.
/// \param g The automaton to output. /// \param g The automaton to output.
/// \param opt a set of characters each corresponding to a possible /// \param opt a set of characters each corresponding to a possible
/// option: (i) implicit labels for complete and /// option: (b) create an alias basis if more >=2 AP
/// are used, (i) implicit labels for complete and
/// deterministic automata, (k) state labels when possible, /// deterministic automata, (k) state labels when possible,
/// (s) state-based acceptance when possible, (t) /// (s) state-based acceptance when possible, (t)
/// transition-based acceptance, (m) mixed acceptance, (l) /// transition-based acceptance, (m) mixed acceptance, (l)
@ -62,7 +63,8 @@ namespace spot
/// ///
/// Pass an empty vector to remove existing aliases. /// Pass an empty vector to remove existing aliases.
SPOT_API void SPOT_API void
set_aliases(twa_ptr g, std::vector<std::pair<std::string, bdd>> aliases); set_aliases(twa_ptr g,
const std::vector<std::pair<std::string, bdd>>& aliases);
/// \ingroup twa_io /// \ingroup twa_io
/// \brief Help printing BDDs as text, using aliases. /// \brief Help printing BDDs as text, using aliases.
@ -164,4 +166,18 @@ namespace spot
} }
}; };
/// \ingroup twa_io
/// \brief Create an alias basis
///
/// This use spot::edge_separator to build a set of alias that can
/// be used as a basis for all labels of the automaton.
///
/// Such a basis can be used to shorten the size of an output file
/// when printing in HOA format (actually, calling print_hoa() with
/// option 'b' will call this function). Such a basis may also be
/// useful to help visualize an automaton (using spot::print_dot's
/// `@` option) when its labels are too large.
SPOT_API void
create_alias_basis(const twa_graph_ptr& aut);
} }

View file

@ -685,7 +685,7 @@ EOF
diff output2 expect2 diff output2 expect2
SPOT_DEFAULT_FORMAT=hoa=k autfilt expect2 >output2b SPOT_DEFAULT_FORMAT=hoa=kb autfilt expect2 >output2b
cat >expect2b <<EOF cat >expect2b <<EOF
HOA: v1 HOA: v1
@ -695,12 +695,15 @@ AP: 2 "p0" "p1"
acc-name: all acc-name: all
Acceptance: 0 t Acceptance: 0 t
properties: state-labels explicit-labels state-acc deterministic weak properties: state-labels explicit-labels state-acc deterministic weak
Alias: @0 !0&1 | 0&!1
Alias: @1 !0&!1
Alias: @2 !@1&!@0
--BODY-- --BODY--
State: [!0&!1] 0 State: [@1] 0
1 1
State: [0&1] 1 State: [@2] 1
2 2
State: [!0&!1] 2 State: [@1] 2
0 0
--END-- --END--
EOF EOF
@ -732,7 +735,7 @@ State: 5
EOF EOF
autfilt -H --remove-unreach input >output3 autfilt -H --remove-unreach input >output3
autfilt -H --remove-dead input >>output3 autfilt -Hb --remove-dead input >>output3
cat >expect3 <<EOF cat >expect3 <<EOF
HOA: v1 HOA: v1
@ -759,13 +762,16 @@ AP: 2 "p0" "p1"
acc-name: all acc-name: all
Acceptance: 0 t Acceptance: 0 t
properties: trans-labels explicit-labels state-acc deterministic weak properties: trans-labels explicit-labels state-acc deterministic weak
Alias: @0 !0&1 | 0&!1
Alias: @1 !0&!1
Alias: @2 !@1&!@0
--BODY-- --BODY--
State: 0 State: 0
[!0&!1] 1 [@1] 1
State: 1 State: 1
[0&1] 2 [@2] 2
State: 2 State: 2
[!0&!1] 0 [@1] 0
--END-- --END--
EOF EOF

View file

@ -108,6 +108,51 @@ State: 0
[@p0&2 | @p1&2] 0 [@p0&2 | @p1&2] 0
--END--""") --END--""")
s2b = aut.to_str('hoa', 'b')
tc.assertTrue(spot.are_equivalent(aut, spot.automaton(s2b)))
tc.assertEqual(s2b, """HOA: v1
States: 1
Start: 0
AP: 3 "x" "y" "z"
acc-name: all
Acceptance: 0 t
properties: trans-labels explicit-labels state-acc complete very-weak
Alias: @0 !0&!1&!2
Alias: @1 0&!1&!2
Alias: @2 !0&1&!2
Alias: @3 0&1&!2
Alias: @4 !0&!1&2
Alias: @5 0&1&2
Alias: @6 0&!1&2
Alias: @7 !@6&!@5&!@4&!@3&!@2&!@1&!@0
--BODY--
State: 0
[@6 | @5 | @3 | @1] 0
[@7 | @4 | @2 | @0] 0
[@7 | @5 | @3 | @2] 0
[@6 | @4 | @1 | @0] 0
[@4 | @0] 0
[@7 | @6 | @5 | @3 | @2 | @1] 0
[@7 | @2] 0
[@6 | @5 | @4 | @3 | @1 | @0] 0
[@6 | @1] 0
[@7 | @5 | @4 | @3 | @2 | @0] 0
[@7 | @4 | @2 | @0] 0
[@6 | @4 | @1 | @0] 0
[@7 | @6 | @2 | @1] 0
[@7 | @6 | @4 | @2 | @1 | @0] 0
[t] 0
[t] 0
[t] 0
[@5 | @3] 0
[@6 | @5 | @3 | @1] 0
[@7 | @5 | @3 | @2] 0
[@5 | @4 | @3 | @0] 0
[@5 | @4] 0
[@4] 0
[@7 | @6 | @5] 0
--END--""")
# Check what happens to aliases when an AP has been removed, but # Check what happens to aliases when an AP has been removed, but
# the aliases have been preserved... # the aliases have been preserved...
rem = spot.remove_ap() rem = spot.remove_ap()