gf_guarantee_to_ba: save states using histories
This improves gf_guarantee_to_ba() on formulas GF(φ) where the automaton for F(φ) as several leading transiant SCCs. E.g., GF(a <-> XXXa) where we know get results that are as good as those of delag without loosing on the cases where delag's technique would actually produce two big automata. * spot/twaalgos/gfguarantee.cc: Implement this. * spot/twaalgos/gfguarantee.hh, NEWS: Document it. * tests/core/ltl2tgba2.test, tests/core/ltl3ba.test: Add test cases.
This commit is contained in:
parent
730c6bbca2
commit
7e9325866f
5 changed files with 221 additions and 26 deletions
28
NEWS
28
NEWS
|
|
@ -94,17 +94,27 @@ New in spot 2.5.3.dev (not yet released)
|
||||||
simplified to {1} or {SERE} depending on whether SERE accepts
|
simplified to {1} or {SERE} depending on whether SERE accepts
|
||||||
the empty word or not.
|
the empty word or not.
|
||||||
|
|
||||||
- gf_guarantee_to_ba() is a specialized construction for
|
- gf_guarantee_to_ba() is a specialized construction for translating
|
||||||
translating formulas of the form GF(guarantee) to BA or DBA,
|
formulas of the form GF(guarantee) to BA or DBA, and
|
||||||
and fg_safety_to_dca() is a specialized construction for
|
fg_safety_to_dca() is a specialized construction for translating
|
||||||
translating formulas of the form FG(safety) to DCA. These
|
formulas of the form FG(safety) to DCA. These are generalizations
|
||||||
are slight generalizations of some constructions proposed
|
of some constructions proposed by J. Esparza, J. Křentínský, and
|
||||||
by J. Esparza, J. Křentínský, and S. Sickert (LICS'18).
|
S. Sickert (LICS'18).
|
||||||
|
|
||||||
These are now used by the main translation routine, and can be
|
These are now used by the main translation routine, and can be
|
||||||
disabled by passing -x '!gf-guarantee' to ltl2tgba. As an
|
disabled by passing -x '!gf-guarantee' to ltl2tgba. For example,
|
||||||
example, the translation of GF(a <-> XXb) to transition-based
|
here are the size of deterministic transition-based Büchi automata
|
||||||
Büchi went from 9 to 5 states using that construction.
|
constructed from four GF(guarantee) formulas with two versions of
|
||||||
|
Spot, and converted to other types of deterministic automata by
|
||||||
|
other tools.
|
||||||
|
|
||||||
|
ltl2tgba -D rabinizer 4
|
||||||
|
2.5 2.6 delag ltl2dra
|
||||||
|
------------------------- ----------- -------------
|
||||||
|
GF(a <-> XXa) 9 4 4 9
|
||||||
|
GF(a <-> XXXa) 27 8 8 25
|
||||||
|
GF(((a & Xb) | XXc) & Xd) 6 4 16 5
|
||||||
|
GF((b | Fa) & (b R Xb)) 6 2 3 3
|
||||||
|
|
||||||
- Slightly improved log output for the SAT-based minimization
|
- Slightly improved log output for the SAT-based minimization
|
||||||
functions. The CSV log files now include an additional column
|
functions. The CSV log files now include an additional column
|
||||||
|
|
|
||||||
|
|
@ -75,13 +75,113 @@ namespace spot
|
||||||
if (!state_based)
|
if (!state_based)
|
||||||
{
|
{
|
||||||
bool is_det = is_deterministic(aut);
|
bool is_det = is_deterministic(aut);
|
||||||
bool initial_state_reachable = false;
|
|
||||||
for (auto& e: aut->edges())
|
// If the initial state is a trivial SCC, we will be able to
|
||||||
if (e.dst == init)
|
// remove it by combining the transitions leading to the
|
||||||
|
// terminal state on the transitions leaving the initial
|
||||||
|
// state. However if some successor of the initial state is
|
||||||
|
// also trivial, we might be able to remove it as well if we
|
||||||
|
// are able to replay two step from the initial state: this
|
||||||
|
// can be generalized to more depth and require computing some
|
||||||
|
// history for each state (i.e., a common suffix to all finite
|
||||||
|
// words leading to this state).
|
||||||
|
std::vector<std::vector<bdd>> histories;
|
||||||
|
bool initial_state_trivial = si.is_trivial(si.initial());
|
||||||
|
|
||||||
|
if (is_det && initial_state_trivial)
|
||||||
|
{
|
||||||
|
// Compute the max number of steps we want to keep in
|
||||||
|
// the history of each state. This is the largest
|
||||||
|
// number of trivial SCCs you can chain from the initial
|
||||||
|
// state.
|
||||||
|
unsigned max_histories = 1;
|
||||||
{
|
{
|
||||||
initial_state_reachable = true;
|
std::vector<unsigned> depths(ns, 0U);
|
||||||
break;
|
for (unsigned d: si.succ(ns - 1))
|
||||||
|
depths[d] = 2;
|
||||||
|
for (int scc = ns - 2; scc >= 0; --scc)
|
||||||
|
{
|
||||||
|
if (!si.is_trivial(scc))
|
||||||
|
continue;
|
||||||
|
unsigned sccd = depths[scc];
|
||||||
|
max_histories = std::max(max_histories, sccd);
|
||||||
|
++sccd;
|
||||||
|
for (unsigned d: si.succ(scc))
|
||||||
|
depths[d] = std::max(depths[d], sccd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned numstates = aut->num_states();
|
||||||
|
histories.resize(numstates);
|
||||||
|
// Compute the one-letter history of each state. If all
|
||||||
|
// transition entering a state have the same label, the history
|
||||||
|
// is that label. Otherwise set it to bddfalse.
|
||||||
|
for (auto& e: aut->edges())
|
||||||
|
{
|
||||||
|
std::vector<bdd>& hd = histories[e.dst];
|
||||||
|
if (hd.empty())
|
||||||
|
hd.push_back(e.cond);
|
||||||
|
else if (hd[0] != e.cond)
|
||||||
|
hd[0] = bddfalse;
|
||||||
|
}
|
||||||
|
// remove all bddfalse, and check if there is a chance to build
|
||||||
|
// a larger history.
|
||||||
|
bool should_continue = false;
|
||||||
|
for (auto&h: histories)
|
||||||
|
if (h.empty())
|
||||||
|
continue;
|
||||||
|
else if (h[0] == bddfalse)
|
||||||
|
h.pop_back();
|
||||||
|
else
|
||||||
|
should_continue = true;
|
||||||
|
// Augment those histories with more letters.
|
||||||
|
unsigned historypos = 0;
|
||||||
|
while (should_continue && historypos + 1 < max_histories)
|
||||||
|
{
|
||||||
|
++historypos;
|
||||||
|
for (auto& e: aut->edges())
|
||||||
|
{
|
||||||
|
std::vector<bdd>& hd = histories[e.dst];
|
||||||
|
if (hd.size() >= historypos)
|
||||||
|
{
|
||||||
|
std::vector<bdd>& hs = histories[e.src];
|
||||||
|
if (hd.size() == historypos)
|
||||||
|
{
|
||||||
|
if (hs.size() >= historypos)
|
||||||
|
hd.push_back(hs[historypos - 1]);
|
||||||
|
else
|
||||||
|
hd.push_back(bddfalse);
|
||||||
|
}
|
||||||
|
else if (hs.size() < historypos
|
||||||
|
|| hd[historypos] != hs[historypos - 1])
|
||||||
|
{
|
||||||
|
hd[historypos] = bddfalse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
should_continue = false;
|
||||||
|
for (unsigned n = 0; n < numstates; ++n)
|
||||||
|
{
|
||||||
|
auto& h = histories[n];
|
||||||
|
//for (auto&h: histories)
|
||||||
|
if (h.size() <= historypos)
|
||||||
|
continue;
|
||||||
|
else if (h[historypos] == bddfalse)
|
||||||
|
h.pop_back();
|
||||||
|
else
|
||||||
|
should_continue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// std::cerr << "computed histories:\n";
|
||||||
|
// for (unsigned n = 0; n < numstates; ++n)
|
||||||
|
// {
|
||||||
|
// std::cerr << n << ": [";
|
||||||
|
// for (bdd b: histories[n])
|
||||||
|
// bdd_print_formula(std::cerr, aut->get_dict(), b) << ", ";
|
||||||
|
// std::cerr << '\n';
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
unsigned nedges = aut->num_edges();
|
unsigned nedges = aut->num_edges();
|
||||||
unsigned new_init = -1U;
|
unsigned new_init = -1U;
|
||||||
// The loop might add new edges, but we just want to iterate
|
// The loop might add new edges, but we just want to iterate
|
||||||
|
|
@ -89,9 +189,14 @@ namespace spot
|
||||||
for (unsigned edge = 1; edge <= nedges; ++edge)
|
for (unsigned edge = 1; edge <= nedges; ++edge)
|
||||||
{
|
{
|
||||||
auto& e = aut->edge_storage(edge);
|
auto& e = aut->edge_storage(edge);
|
||||||
|
// Don't bother with terminal states, they won't be
|
||||||
|
// reachable anymore.
|
||||||
|
if (term[si.scc_of(e.src)])
|
||||||
|
continue;
|
||||||
|
// It will loop
|
||||||
if (term[si.scc_of(e.dst)])
|
if (term[si.scc_of(e.dst)])
|
||||||
{
|
{
|
||||||
if (initial_state_reachable
|
if (!initial_state_trivial
|
||||||
// If the source state is the initial state, we
|
// If the source state is the initial state, we
|
||||||
// should not try to combine it with itself...
|
// should not try to combine it with itself...
|
||||||
|| e.src == init)
|
|| e.src == init)
|
||||||
|
|
@ -105,11 +210,55 @@ namespace spot
|
||||||
// state of the automaton, we can get rid of it by
|
// state of the automaton, we can get rid of it by
|
||||||
// combining the edge reaching the terminal state
|
// combining the edge reaching the terminal state
|
||||||
// with the edges leaving the initial state.
|
// with the edges leaving the initial state.
|
||||||
bdd econd = e.cond;
|
//
|
||||||
bool first = true;
|
// However if we have some histories for e.src
|
||||||
|
// (which implies that the automaton is
|
||||||
|
// deterministic), we can try to replay that from
|
||||||
|
// the initial state first.
|
||||||
|
//
|
||||||
|
// One problem with those histories, is that we do
|
||||||
|
// not know how much of it to replay. It's possible
|
||||||
|
// that we cannot find a matching transition (e.g. if
|
||||||
|
// the history if "b" but the choices are "!a" or "a"),
|
||||||
|
// and its also possible to that playing too much of
|
||||||
|
// history will get us back the terminal state. In both
|
||||||
|
// cases, we should try again with a smaller history.
|
||||||
|
unsigned moved_init = init;
|
||||||
|
if (is_det)
|
||||||
|
{
|
||||||
|
auto& h = histories[e.src];
|
||||||
|
int hsize = h.size();
|
||||||
|
for (int hlen = hsize - 1; hlen > 0; --hlen)
|
||||||
|
{
|
||||||
|
for (int pos = hlen - 1; pos >= 0; --pos)
|
||||||
|
{
|
||||||
|
for (auto& e: aut->out(moved_init))
|
||||||
|
if (bdd_implies(h[pos], e.cond))
|
||||||
|
{
|
||||||
|
if (term[si.scc_of(e.dst)])
|
||||||
|
goto failed;
|
||||||
|
moved_init = e.dst;
|
||||||
|
goto moved;
|
||||||
|
}
|
||||||
|
// if we reach this place, we failed to follow
|
||||||
|
// one step of the history.
|
||||||
|
goto failed;
|
||||||
|
moved:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// we have successfully played all history
|
||||||
|
// to a non-terminal state.
|
||||||
|
break;
|
||||||
|
failed:
|
||||||
|
moved_init = init;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Combine this edge with any compatible edge from
|
// Combine this edge with any compatible edge from
|
||||||
// the initial state.
|
// the initial state.
|
||||||
for (auto& ei: aut->out(init))
|
bdd econd = e.cond;
|
||||||
|
bool first = true;
|
||||||
|
for (auto& ei: aut->out(moved_init))
|
||||||
{
|
{
|
||||||
bdd cond = ei.cond & econd;
|
bdd cond = ei.cond & econd;
|
||||||
if (cond != bddfalse)
|
if (cond != bddfalse)
|
||||||
|
|
@ -196,12 +345,22 @@ namespace spot
|
||||||
f = nest_f(f);
|
f = nest_f(f);
|
||||||
}
|
}
|
||||||
twa_graph_ptr aut = ltl_to_tgba_fm(f, dict, true);
|
twa_graph_ptr aut = ltl_to_tgba_fm(f, dict, true);
|
||||||
twa_graph_ptr reduced = minimize_obligation(aut, f, nullptr,
|
twa_graph_ptr reduced = minimize_obligation(aut, f);
|
||||||
!deterministic);
|
if (reduced == aut)
|
||||||
|
return nullptr;
|
||||||
scc_info si(reduced);
|
scc_info si(reduced);
|
||||||
if (!is_terminal_automaton(reduced, &si, true))
|
if (!is_terminal_automaton(reduced, &si, true))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
do_g_f_terminal_inplace(si, state_based);
|
do_g_f_terminal_inplace(si, state_based);
|
||||||
|
if (!deterministic)
|
||||||
|
{
|
||||||
|
scc_info si2(aut);
|
||||||
|
if (!is_terminal_automaton(aut, &si2, true))
|
||||||
|
return reduced;
|
||||||
|
do_g_f_terminal_inplace(si2, state_based);
|
||||||
|
if (aut->num_states() <= reduced->num_states())
|
||||||
|
return aut;
|
||||||
|
}
|
||||||
return reduced;
|
return reduced;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,10 @@ namespace spot
|
||||||
///
|
///
|
||||||
/// If the formula \a gf has the form GΦ where Φ matches either F(φ)
|
/// If the formula \a gf has the form GΦ where Φ matches either F(φ)
|
||||||
/// or F(φ₁)|F(φ₂)|...|F(φₙ), we translate Φ into A_Φ and attempt to
|
/// or F(φ₁)|F(φ₂)|...|F(φₙ), we translate Φ into A_Φ and attempt to
|
||||||
/// minimize it to a WDBA. If \a deterministic is not set, we keep
|
/// minimize it to a WDBA W_Φ. If the resulting automaton is
|
||||||
/// the minimized automaton only if A_Φ is larger. If the resulting
|
/// terminal, we then call g_f_terminal_inplace(W_Φ). If \a
|
||||||
/// automaton is terminal, we then call g_f_terminal_inplace().
|
/// deterministic is not set, we keep the minimized automaton only
|
||||||
|
/// if g_f_terminal_inplace(A_Φ) is larger.
|
||||||
///
|
///
|
||||||
/// Return nullptr if the input formula is not of the supported
|
/// Return nullptr if the input formula is not of the supported
|
||||||
/// form.
|
/// form.
|
||||||
|
|
@ -59,7 +60,9 @@ namespace spot
|
||||||
/// This construction generalizes a construction in the LICS'18
|
/// This construction generalizes a construction in the LICS'18
|
||||||
/// paper of J. Esparza, J. Křetínský, and S. Sickert. This version
|
/// paper of J. Esparza, J. Křetínský, and S. Sickert. This version
|
||||||
/// will work if Φ represent a safety property, even if it is not a
|
/// will work if Φ represent a safety property, even if it is not a
|
||||||
/// syntactic safety.
|
/// syntactic safety. When building deterministic transition-based
|
||||||
|
/// automata, it will also try to remove useless trivial components
|
||||||
|
/// at the beginning of wdba(A_Φ).
|
||||||
SPOT_API twa_graph_ptr
|
SPOT_API twa_graph_ptr
|
||||||
gf_guarantee_to_ba_maybe(formula gf, const bdd_dict_ptr& dict,
|
gf_guarantee_to_ba_maybe(formula gf, const bdd_dict_ptr& dict,
|
||||||
bool deterministic = true, bool state_based = false);
|
bool deterministic = true, bool state_based = false);
|
||||||
|
|
|
||||||
|
|
@ -360,3 +360,19 @@ p-patterns,20, 1,8, 1,8, 3,24, 3,24
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
diff output expected
|
diff output expected
|
||||||
|
|
||||||
|
|
||||||
|
# These four formulas appear in a NEWS entry for Spot 2.6
|
||||||
|
cat >formulas <<EOF
|
||||||
|
GF((a & XXa) | (!a & XX!a)), 4,8, 4,8, 6,14, 7,14
|
||||||
|
GF((a & XXXa) | (!a & XXX!a)), 7,14, 8,16, 8,18, 15,30
|
||||||
|
GF(((a & Xb) | XXc) & Xd), 4,60, 4,64, 4,68, 5,80
|
||||||
|
GF((b | Fa) & (b R Xb)), 2,4, 2,4, 3,6, 3,12
|
||||||
|
EOF
|
||||||
|
|
||||||
|
ltl2tgba -Fformulas/1 --stats='%f, %s,%t' |
|
||||||
|
ltl2tgba -D -F-/1 --stats='%f,%>, %s,%t' |
|
||||||
|
ltl2tgba -B -F-/1 --stats='%f,%>, %s,%t' |
|
||||||
|
ltl2tgba -BD -F-/1 --stats='%f,%>, %s,%t' > output
|
||||||
|
|
||||||
|
diff formulas output
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (C) 2016, 2017 Laboratoire de Recherche et Développement
|
# Copyright (C) 2016, 2017, 2018 Laboratoire de Recherche et Développement
|
||||||
# de l'Epita (LRDE).
|
# de l'Epita (LRDE).
|
||||||
#
|
#
|
||||||
# This file is part of Spot, a model checking library.
|
# This file is part of Spot, a model checking library.
|
||||||
|
|
@ -39,8 +39,15 @@ export SPOT_HOA_TOLERANT
|
||||||
# compatible with Spin's syntax.
|
# compatible with Spin's syntax.
|
||||||
(
|
(
|
||||||
echo _x
|
echo _x
|
||||||
|
# Some formula used by Spot's GF(guarantee) translation.
|
||||||
|
cat <<EOF
|
||||||
|
GF((a & XXa) | (!a & XX!a))
|
||||||
|
GF((a & XXXa) | (!a & XXX!a))
|
||||||
|
GF(((a & Xb) | XXc) & Xd)
|
||||||
|
GF((b | Fa) & (b R Xb))
|
||||||
|
EOF
|
||||||
randltl -n 30 2
|
randltl -n 30 2
|
||||||
) | ltlcross 'ltl3ba -H1' 'ltl3ba -H2' 'ltl3ba -H3' \
|
) | ltlcross -D 'ltl3ba -H1' 'ltl3ba -H2' 'ltl3ba -H3' ltl2tgba \
|
||||||
--ambiguous --strength --csv=output.csv
|
--ambiguous --strength --csv=output.csv
|
||||||
|
|
||||||
grep _x output.csv && exit 1
|
grep _x output.csv && exit 1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue