simplify: GF(f)=GF(dnf(f)) FG(f)=FG(cnf(f))

These rules come from Delag's paper, and help some cases
in issue #385.

* spot/tl/simplify.cc: Implement the simplification.
* doc/tl/tl.tex, NEWS: Document it.
* tests/core/385.test: New file.
* tests/Makefile.am: Add it.
* tests/core/reduccmp.test: More tests.
* tests/core/ltl2tgba2.test: Adjust one improved case.
* tests/python/automata.ipynb, tests/python/twagraph-internals.ipynb:
Adjust expected output, as the cnf/dnf reorder some subformulas.
This commit is contained in:
Alexandre Duret-Lutz 2019-06-17 00:19:02 +02:00
parent df326e032b
commit da5d23f0a2
9 changed files with 2517 additions and 2291 deletions

View file

@ -33,6 +33,7 @@
#include <spot/tl/snf.hh>
#include <spot/tl/length.hh>
#include <spot/twa/formula2bdd.hh>
#include <spot/misc/minato.hh>
#include <cassert>
#include <memory>
@ -45,6 +46,7 @@ namespace spot
{
typedef std::unordered_map<formula, formula> f2f_map;
typedef std::unordered_map<formula, bdd> f2b_map;
typedef std::map<int, formula> b2f_map;
typedef std::pair<formula, formula> pairf;
typedef std::map<pairf, bool> syntimpl_cache_t;
public:
@ -78,13 +80,18 @@ namespace spot
<< "syntactic implications: " << syntimpl_.size() << " entries\n"
<< "boolean to bdd: " << as_bdd_.size() << " entries\n"
<< "star normal form: " << snf_cache_.size() << " entries\n"
<< "boolean isop: " << bool_isop_.size() << " entries\n";
<< "boolean isop: " << bool_isop_.size() << " entries\n"
<< "as dnf: " << as_dnf_.size() << " entries\n"
<< "as cnf: " << as_cnf_.size() << " entries\n";
}
void
clear_as_bdd_cache()
{
as_bdd_.clear();
for (auto p: bdd_to_f_)
dict->unregister_variable(p.first, this);
bdd_to_f_.clear();
}
// Convert a Boolean formula into a BDD for easier comparison.
@ -136,7 +143,12 @@ namespace spot
break;
}
default:
SPOT_UNIMPLEMENTED();
{
unsigned var = dict->register_anonymous_variables(1, this);
bdd_to_f_[var] = f;
result = bdd_ithvar(var);
break;
}
}
// Cache the result before returning.
@ -144,6 +156,83 @@ namespace spot
return result;
}
formula as_xnf(formula f, bool cnf)
{
bdd in = as_bdd(f);
if (cnf)
in = !in;
minato_isop isop(in);
bdd cube;
vec clauses;
while ((cube = isop.next()) != bddfalse)
{
vec literals;
while (cube != bddtrue)
{
int var = bdd_var(cube);
const bdd_dict::bdd_info& i = dict->bdd_map[var];
formula res;
if (i.type == bdd_dict::var)
{
res = i.f;
}
else
{
res = bdd_to_f_[var];
assert(res);
}
bdd high = bdd_high(cube);
if (high == bddfalse)
{
if (!cnf)
res = formula::Not(res);
cube = bdd_low(cube);
}
else
{
if (cnf)
res = formula::Not(res);
// If bdd_low is not false, then cube was not a
// conjunction.
assert(bdd_low(cube) == bddfalse);
cube = high;
}
assert(cube != bddfalse);
literals.emplace_back(res);
}
if (cnf)
clauses.emplace_back(formula::Or(literals));
else
clauses.emplace_back(formula::And(literals));
}
if (cnf)
return formula::And(clauses);
else
return formula::Or(clauses);
}
formula as_dnf(formula f)
{
auto i = as_dnf_.find(f);
if (i != as_dnf_.end())
return i->second;
formula r = as_xnf(f, false);
as_dnf_[f] = r;
return r;
}
formula as_cnf(formula f)
{
auto i = as_cnf_.find(f);
if (i != as_cnf_.end())
return i->second;
formula r = as_xnf(f, true);
as_cnf_[f] = r;
return r;
}
formula
lookup_nenoform(formula f)
{
@ -303,7 +392,10 @@ namespace spot
private:
f2b_map as_bdd_;
b2f_map bdd_to_f_;
f2f_map simplified_;
f2f_map as_dnf_;
f2f_map as_cnf_;
f2f_map nenoform_;
syntimpl_cache_t syntimpl_;
snf_cache snf_cache_;
@ -987,22 +1079,32 @@ namespace spot
return recurse(res);
}
// FG(a & Xb) = FG(a & b)
// FG(a & Gb) = FG(a & b)
if (c.is({op::G, op::And}))
// FG(a) = FG(dnf(a)) if a is not Boolean
// and contains some | above non-Boolean subformulas.
if (c.is(op::G) && !c[0].is_boolean())
{
formula m = c[0];
if (!m.is_boolean())
{
formula out = m.map([](formula f)
{
if (f.is(op::X, op::G))
return f[0];
return f;
});
if (out != m)
return recurse(unop_unop(op::F, op::G, out));
}
bool want_cnf = m.is(op::And);
if (!want_cnf && m.is(op::Or))
for (auto cc : m)
if (cc.is(op::And))
{
want_cnf = true;
break;
}
if (want_cnf && !opt_.reduce_size_strictly)
m = c_->as_cnf(m);
// FG(a & Xb) = FG(a & b)
// FG(a & Gb) = FG(a & b)
if (m.is(op::And))
m = m.map([](formula f)
{
if (f.is(op::X, op::G))
return f[0];
return f;
});
if (c[0] != m)
return recurse(unop_unop(op::F, op::G, m));
}
}
// if Fa => a, keep a.
@ -1248,22 +1350,33 @@ namespace spot
return recurse(res);
}
// GF(a | Xb) = GF(a | b)
// GF(a | Fb) = GF(a | b)
if (c.is({op::F, op::Or}))
// GF(a) = GF(dnf(a)) if a is not Boolean
// and contains some | above non-Boolean subformulas.
if (c.is(op::F) && !c[0].is_boolean())
{
formula m = c[0];
if (!m.is_boolean())
{
formula out = m.map([](formula f)
{
if (f.is(op::X, op::F))
return f[0];
return f;
});
if (out != m)
return recurse(unop_unop(op::G, op::F, out));
}
bool want_dnf = m.is(op::Or);
if (!want_dnf && m.is(op::And))
for (auto cc : m)
if (cc.is(op::Or))
{
want_dnf = true;
break;
}
if (want_dnf && !opt_.reduce_size_strictly)
m = c_->as_dnf(m);
// GF(a | Xb) = GF(a | b)
// GF(a | Fb) = GF(a | b)
if (m.is(op::Or))
m = m.map([](formula f)
{
if (f.is(op::X, op::F))
return f[0];
return f;
});
if (c[0] != m)
return recurse(unop_unop(op::G, op::F, m));
}
// GF(f1 & f2 & eu1 & eu2) = G(F(f1 & f2) & eu1 & eu2
if (opt_.event_univ && c.is({op::F, op::And}))