Generalize implication-based simplifications for multops.

And also speedup implication checks for Boolean expressions.

* src/ltlvisit/simplify.cc: Improve implication-based rules
rules for multops by checking one operand against all the
other at once (instead of one by one).  Do not break
Boolean expressions while performing implication checks.
* src/ltlvisit/simplify.hh: Typo.
* src/ltltest/reduccmp.test: More tests.
This commit is contained in:
Alexandre Duret-Lutz 2013-09-29 21:00:57 +02:00
parent c01909e3ff
commit df109869eb
3 changed files with 379 additions and 315 deletions

View file

@ -65,6 +65,11 @@ for x in ../reduccmp ../reductaustr; do
run 0 $x 'a | (b U a) | a' '(b U a)' run 0 $x 'a | (b U a) | a' '(b U a)'
run 0 $x 'a U (b U a)' '(b U a)' run 0 $x 'a U (b U a)' '(b U a)'
run 0 $x 'a & c & (b W a)' 'a & c'
run 0 $x 'a & d & c & e & f & g & b & (x W (g & f))' 'a&b&c&d&e&f&g'
run 0 $x '(F!a & X(!b R a)) | (Ga & X(b U !a))' 'F!a & X(!b R a)'
run 0 $x 'd & ((!a & d) | (a & d))' '(!a & d) | (a & d)'
run 0 $x 'a <-> !a' '0' run 0 $x 'a <-> !a' '0'
run 0 $x 'a <-> a' '1' run 0 $x 'a <-> a' '1'
run 0 $x 'a ^ a' '0' run 0 $x 'a ^ a' '0'

View file

@ -343,7 +343,7 @@ namespace spot
// Return true iff the option set (syntactic implication // Return true iff the option set (syntactic implication
// or containment checks) allow to prove that // or containment checks) allow to prove that
// - !f2 => f2 (case where right=false) // - !f1 => f2 (case where right=false)
// - f1 => !f2 (case where right=true) // - f1 => !f2 (case where right=true)
bool bool
implication_neg(const formula* f1, const formula* f2, bool right) implication_neg(const formula* f1, const formula* f2, bool right)
@ -2784,65 +2784,59 @@ namespace spot
constant* neutral = is_and constant* neutral = is_and
? constant::false_instance() : constant::true_instance(); ? constant::false_instance() : constant::true_instance();
multop::vec::iterator f1 = res->begin(); result_ = multop::instance(op, res);
const multop* check = is_multop(result_, op);
if (!check)
return;
while (f1 != res->end()) unsigned s = check->size();
unsigned i = 0;
res = new multop::vec;
res->reserve(s);
while (i < s)
{ {
multop::vec::iterator f2 = f1; const formula* fi = check->nth(i);
++f2; const formula* fo = check->all_but(i);
while (f2 != res->end()) // if fi => fo, then fi | fo = fo
// if fo => fi, then fi & fo = fo
if ((op == multop::Or && c_->implication(fi, fo))
|| (op == multop::And && c_->implication(fo, fi)))
{ {
assert(f1 != f2); check->destroy();
// if f1 => f2, then f1 | f2 = f2 check = is_multop(fo, op);
// if f2 => f1, then f1 & f2 = f2 if (!check)
if ((op == multop::Or && c_->implication(*f1, *f2))
|| (op == multop::And && c_->implication(*f2, *f1)))
{ {
// Remove f1. result_ = fo;
(*f1)->destroy(); for (unsigned j = 0; j < i; ++j)
*f1 = 0; (*res)[j]->destroy();
++f1;
break;
}
// if f2 => f1, then f1 | f2 = f1
// if f1 => f2, then f1 & f2 = f1
else if ((op == multop::Or && c_->implication(*f2, *f1))
|| (op == multop::And
&& c_->implication(*f1, *f2)))
{
// Remove f2.
(*f2)->destroy();
// replace it by the last element from the array.
// and start again at the current position.
if (f2 != --res->end())
{
*f2 = res->back();
res->pop_back();
continue;
}
else
{
res->pop_back();
break;
}
}
// if f1 => !f2, then f1 & f2 = false
// if !f1 => f2, then f1 | f2 = true
else if (c_->implication_neg(*f1, *f2, is_and))
{
for (multop::vec::iterator j = res->begin();
j != res->end(); ++j)
if (*j)
(*j)->destroy();
delete res; delete res;
return;
}
--s;
}
// if fi => !fo, then fi & fo = false
// if fo => !fi, then fi & fo = false
// if !fi => fo, then fi | fo = true
// if !fo => fi, then fi | fo = true
else if (c_->implication_neg(fi, fo, is_and)
|| c_->implication_neg(fo, fi, is_and))
{
fo->destroy();
check->destroy();
result_ = neutral; result_ = neutral;
for (unsigned j = 0; j < i; ++j)
(*res)[j]->destroy();
delete res;
return; return;
} }
else else
++f2; {
fo->destroy();
res->push_back(fi->clone());
++i;
} }
++f1;
} }
check->destroy();
} }
assert(!res->empty()); assert(!res->empty());
@ -4417,7 +4411,14 @@ namespace spot
formula::opkind fk = f->kind(); formula::opkind fk = f->kind();
formula::opkind gk = g->kind(); formula::opkind gk = g->kind();
// Deal with all lines except the first two. // We first process all lines from the table except the
// first two, and then we process the first two as a fallback.
//
// However for Boolean formulas we skip the bottom lines
// (keeping only the first one) to prevent them from being
// further split.
if (!f->is_boolean())
// Deal with all lines of the table except the first two.
switch (fk) switch (fk)
{ {
case formula::Constant: case formula::Constant:
@ -4516,7 +4517,8 @@ namespace spot
if ((fo == binop::U && (go == binop::U || go == binop::W)) if ((fo == binop::U && (go == binop::U || go == binop::W))
|| (fo == binop::W && go == binop::W) || (fo == binop::W && go == binop::W)
|| (fo == binop::R && go == binop::R) || (fo == binop::R && go == binop::R)
|| (fo == binop::M && (go == binop::R || go == binop::M))) || (fo == binop::M && (go == binop::R
|| go == binop::M)))
{ {
if (syntactic_implication(f1, g1) if (syntactic_implication(f1, g1)
&& syntactic_implication(f2, g2)) && syntactic_implication(f2, g2))
@ -4534,7 +4536,8 @@ namespace spot
&& syntactic_implication(f2, g2)) && syntactic_implication(f2, g2))
return true; return true;
} }
else if ((fo == binop::U && (go == binop::R || go == binop::M)) else if ((fo == binop::U
&& (go == binop::R || go == binop::M))
|| (fo == binop::W && go == binop::R)) || (fo == binop::W && go == binop::R))
{ {
if (syntactic_implication(f1, g2) if (syntactic_implication(f1, g2)
@ -4542,7 +4545,8 @@ namespace spot
&& syntactic_implication(f2, g2)) && syntactic_implication(f2, g2))
return true; return true;
} }
else if ((fo == binop::M && (go == binop::U || go == binop::W)) else if ((fo == binop::M
&& (go == binop::U || go == binop::W))
|| (fo == binop::R && go == binop::W)) || (fo == binop::R && go == binop::W))
{ {
if (syntactic_implication(f1, g2) if (syntactic_implication(f1, g2)
@ -4576,11 +4580,26 @@ namespace spot
{ {
case multop::Or: case multop::Or:
{ {
if (f->is_boolean())
break;
unsigned i = 0;
// If we are checking something like
// (a | b | Xc) => g,
// split it into
// (a | b) => g
// Xc => g
if (const formula* bops = f_->boolean_operands(&i))
{
bool r = syntactic_implication(bops, g);
bops->destroy();
if (!r)
break;
}
bool b = true; bool b = true;
for (unsigned i = 0; i < fs; ++i) for (; i < fs; ++i)
if (!syntactic_implication(f_->nth(i), g)) if (!syntactic_implication(f_->nth(i), g))
{ {
b &= false; b = false;
break; break;
} }
if (b) if (b)
@ -4589,7 +4608,22 @@ namespace spot
} }
case multop::And: case multop::And:
{ {
for (unsigned i = 0; i < fs; ++i) if (f->is_boolean())
break;
unsigned i = 0;
// If we are checking something like
// (a & b & Xc) => g,
// split it into
// (a & b) => g
// Xc => g
if (const formula* bops = f_->boolean_operands(&i))
{
bool r = syntactic_implication(bops, g);
bops->destroy();
if (r)
return true;
}
for (; i < fs; ++i)
if (syntactic_implication(f_->nth(i), g)) if (syntactic_implication(f_->nth(i), g))
return true; return true;
break; break;
@ -4604,8 +4638,9 @@ namespace spot
break; break;
} }
} }
// First two lines of the table.
// First two lines. // (Don't check equality, it has already be done.)
if (!g->is_boolean())
switch (gk) switch (gk)
{ {
case formula::Constant: case formula::Constant:
@ -4661,11 +4696,24 @@ namespace spot
{ {
case multop::And: case multop::And:
{ {
unsigned i = 0;
// If we are checking something like
// f => (a & b & Xc),
// split it into
// f => (a & b)
// f => Xc
if (const formula* bops = g_->boolean_operands(&i))
{
bool r = syntactic_implication(f, bops);
bops->destroy();
if (!r)
break;
}
bool b = true; bool b = true;
for (unsigned i = 0; i < gs; ++i) for (; i < gs; ++i)
if (!syntactic_implication(f, g_->nth(i))) if (!syntactic_implication(f, g_->nth(i)))
{ {
b &= false; b = false;
break; break;
} }
if (b) if (b)
@ -4674,7 +4722,20 @@ namespace spot
} }
case multop::Or: case multop::Or:
{ {
for (unsigned i = 0; i < gs; ++i) unsigned i = 0;
// If we are checking something like
// f => (a | b | Xc),
// split it into
// f => (a | b)
// f => Xc
if (const formula* bops = g_->boolean_operands(&i))
{
bool r = syntactic_implication(f, bops);
bops->destroy();
if (r)
return true;
}
for (; i < gs; ++i)
if (syntactic_implication(f, g_->nth(i))) if (syntactic_implication(f, g_->nth(i)))
return true; return true;
break; break;
@ -4713,13 +4774,11 @@ namespace spot
|| f == constant::true_instance()) || f == constant::true_instance())
return false; return false;
// Often we compare a literals (an atomic_prop or its negation) // Often we compare a literal (an atomic_prop or its negation)
// to another literal. The result is necessarily false. To be // to another literal. The result is necessarily false. To be
// true, the two literals would have to be equal, but we have // true, the two literals would have to be equal, but we have
// already checked that. // already checked that.
if (f->is_in_nenoform() && g->is_in_nenoform() if (is_literal(f) && is_literal(g))
&& (is_atomic_prop(f) || is_Not(f))
&& (is_atomic_prop(g) || is_Not(g)))
return false; return false;
// Cache lookup // Cache lookup

View file

@ -121,7 +121,7 @@ namespace spot
/// ///
/// If \a right is true, this method returns whether /// If \a right is true, this method returns whether
/// \a f implies !\a g. If \a right is false, this returns /// \a f implies !\a g. If \a right is false, this returns
/// whether !\a g implies \a g. /// whether !\a f implies \a g.
bool syntactic_implication_neg(const formula* f, const formula* g, bool syntactic_implication_neg(const formula* f, const formula* g,
bool right); bool right);