translate: enable a restricted form of ltl-split for TGBA/BA

Fixes #267

* spot/twaalgos/gfguarantee.cc: Fix a typo when comparing automata
sizes.
* spot/twaalgos/translate.cc, spot/twaalgos/translate.hh: Use
ltl-split even for BA/TGBA, but only of conjunctions with GF(..)
in those cases.
* tests/core/ltl2tgba2.test: Adjust and add the example of #267.
* tests/core/degenid.test, tests/core/parity2.test,
tests/core/stutter-tgba.test, tests/python/automata.ipynb,
tests/python/highlighting.ipynb, tests/python/stutter-inv.ipynb,
bin/spot-x.cc: Adjust.
This commit is contained in:
Alexandre Duret-Lutz 2018-06-28 23:02:26 +02:00
parent 4235b007f3
commit f5f5daec9a
11 changed files with 1338 additions and 1494 deletions

View file

@ -418,7 +418,7 @@ namespace spot
if (!is_terminal_automaton(aut, &si2, true))
return reduced;
do_g_f_terminal_inplace(si2, state_based);
if (aut->num_states() <= reduced->num_states())
if (aut->num_states() < reduced->num_states())
return aut;
}
return reduced;

View file

@ -105,7 +105,8 @@ namespace spot
simpl_owned_ = simpl_ = new tl_simplifier(options, dict);
}
twa_graph_ptr translator::run(formula* f)
twa_graph_ptr translator::run_aux(formula r)
{
#define PREF_ (pref_ & (Small | Deterministic))
@ -118,49 +119,6 @@ namespace spot
set_pref(pref_ | postprocessor::Deterministic);
}
// Do we want to relabel Boolean subformulas?
// If we have a huge formula such as
// (a1 & a2 & ... & an) U (b1 | b2 | ... | bm)
// then it is more efficient to translate
// a U b
// and then fix the automaton. We use relabel_bse() to find
// sub-formulas that are Boolean but do not have common terms.
//
// This rewriting is enabled only if the formula
// 1) has some Boolean subformula
// 2) has more than relabel_bool_ atomic propositions (the default
// is 4, but this can be changed)
// 3) relabel_bse() actually reduces the number of atomic
// propositions.
relabeling_map m;
formula to_work_on = *f;
if (relabel_bool_ > 0)
{
bool has_boolean_sub = false; // that is not atomic
std::set<formula> aps;
to_work_on.traverse([&](const formula& f)
{
if (f.is(op::ap))
aps.insert(f);
else if (f.is_boolean())
has_boolean_sub = true;
return false;
});
unsigned atomic_props = aps.size();
if (has_boolean_sub && (atomic_props >= (unsigned) relabel_bool_))
{
formula relabeled = relabel_bse(to_work_on, Pnn, &m);
if (m.size() < atomic_props)
to_work_on = relabeled;
else
m.clear();
}
}
formula r = simpl_->simplify(to_work_on);
if (to_work_on == *f)
*f = r;
// This helps ltl_to_tgba_fm() to order BDD variables in a more
// natural way (improving the degeneralization).
simpl_->clear_as_bdd_cache();
@ -168,8 +126,7 @@ namespace spot
twa_graph_ptr aut;
twa_graph_ptr aut2 = nullptr;
if (ltl_split_ && (type_ == Generic
|| (type_ & Parity)) && !r.is_syntactic_obligation())
if (ltl_split_ && !r.is_syntactic_obligation())
{
formula r2 = r;
unsigned leading_x = 0;
@ -178,11 +135,11 @@ namespace spot
r2 = r2[0];
++leading_x;
}
if (type_ == Generic)
if (type_ == Generic || type_ == TGBA)
{
// F(q|u|f) = q|F(u)|F(f)
// F(q|u|f) = q|F(u)|F(f) only for generic acceptance
// G(q&e&f) = q&G(e)&G(f)
bool want_u = r2.is({op::F, op::Or});
bool want_u = r2.is({op::F, op::Or}) && (type_ == Generic);
if (want_u || r2.is({op::G, op::And}))
{
std::vector<formula> susp;
@ -204,7 +161,12 @@ namespace spot
r2 = formula::multop(op2, susp);
}
}
if (r2.is_syntactic_obligation() || !r2.is(op::And, op::Or))
if (r2.is_syntactic_obligation() || !r2.is(op::And, op::Or) ||
// For TGBA/BA we only do conjunction. There is nothing wrong
// with disjunction, but it seems to generated larger automata
// in many cases and it needs to be further investigated. Maybe
// this could be relaxed in the case of deterministic output.
(r2.is(op::Or) && (type_ == TGBA || type_ == BA)))
goto nosplit;
bool is_and = r2.is(op::And);
@ -212,16 +174,38 @@ namespace spot
std::vector<formula> oblg;
std::vector<formula> susp;
std::vector<formula> rest;
bool want_g = type_ == TGBA || type_ == BA;
for (formula child: r2)
{
if (child.is_syntactic_obligation())
oblg.push_back(child);
else if (child.is_eventual() && child.is_universal()
&& (type_ == Generic))
&& (!want_g || child.is(op::G)))
susp.push_back(child);
else
rest.push_back(child);
}
if (!susp.empty())
{
// The only cases where we accept susp and rest to be both
// non-empty is when doing arbitrary acceptance, or when doing
// Generic or TGBA.
if (!rest.empty() && !(type_ == Generic || type_ == TGBA))
{
rest.insert(rest.end(), susp.begin(), susp.end());
susp.clear();
}
// For Parity, we want to translate all suspendable
// formulas at once.
if (rest.empty() && type_ & Parity)
susp = { formula::multop(r2.kind(), susp) };
}
// For TGBA and BA, we only split if there is something to
// suspend.
if (susp.empty() && (type_ == TGBA || type_ == BA))
goto nosplit;
option_map om;
if (opt_)
om = *opt_;
@ -238,34 +222,26 @@ namespace spot
return run(f);
};
// std::cerr << "splitting\n";
aut = nullptr;
// All obligations can be converted into a minimal WDBA.
if (!oblg.empty())
{
formula oblg_f = formula::multop(r2.kind(), oblg);
//std::cerr << "oblg: " << oblg_f << '\n';
aut = transrun(oblg_f);
}
if (!rest.empty())
{
formula rest_f = formula::multop(r2.kind(), rest);
// In case type_ is Parity, all suspendable formulas have
// been put into rest_f. But if the entire rest_f is
// suspendable, we want to handle it like so.
if (rest_f.is_eventual() && rest_f.is_universal())
{
assert(susp.empty());
susp.push_back(rest_f);
}
//std::cerr << "rest: " << rest_f << '\n';
twa_graph_ptr rest_aut = transrun(rest_f);
if (aut == nullptr)
aut = rest_aut;
else if (is_and)
aut = product(aut, rest_aut);
else
{
twa_graph_ptr rest_aut = transrun(rest_f);
if (aut == nullptr)
aut = rest_aut;
else if (is_and)
aut = product(aut, rest_aut);
else
aut = product_or(aut, rest_aut);
}
aut = product_or(aut, rest_aut);
}
if (!susp.empty())
{
@ -273,6 +249,7 @@ namespace spot
// Each suspendable formula separately
for (formula f: susp)
{
//std::cerr << "susp: " << f << '\n';
twa_graph_ptr one = transrun(f);
if (!susp_aut)
susp_aut = one;
@ -370,6 +347,56 @@ namespace spot
aut = std::move(aut2);
}
return aut;
}
twa_graph_ptr translator::run(formula* f)
{
// Do we want to relabel Boolean subformulas?
// If we have a huge formula such as
// (a1 & a2 & ... & an) U (b1 | b2 | ... | bm)
// then it is more efficient to translate
// a U b
// and then fix the automaton. We use relabel_bse() to find
// sub-formulas that are Boolean but do not have common terms.
//
// This rewriting is enabled only if the formula
// 1) has some Boolean subformula
// 2) has more than relabel_bool_ atomic propositions (the default
// is 4, but this can be changed)
// 3) relabel_bse() actually reduces the number of atomic
// propositions.
relabeling_map m;
formula to_work_on = *f;
if (relabel_bool_ > 0)
{
bool has_boolean_sub = false; // that is not atomic
std::set<formula> aps;
to_work_on.traverse([&](const formula& f)
{
if (f.is(op::ap))
aps.insert(f);
else if (f.is_boolean())
has_boolean_sub = true;
return false;
});
unsigned atomic_props = aps.size();
if (has_boolean_sub && (atomic_props >= (unsigned) relabel_bool_))
{
formula relabeled = relabel_bse(to_work_on, Pnn, &m);
if (m.size() < atomic_props)
to_work_on = relabeled;
else
m.clear();
}
}
formula r = simpl_->simplify(to_work_on);
if (to_work_on == *f)
*f = r;
auto aut = run_aux(r);
if (!m.empty())
relabel_here(aut, &m);
return aut;

View file

@ -138,6 +138,7 @@ namespace spot
protected:
void setup_opt(const option_map* opt);
void build_simplifier(const bdd_dict_ptr& dict);
twa_graph_ptr run_aux(formula f);
private:
tl_simplifier* simpl_;