### 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.```
parent c01909e3
 ... ... @@ -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 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' '1' run 0 \$x 'a ^ a' '0' ... ...
 ... ... @@ -343,7 +343,7 @@ namespace spot // Return true iff the option set (syntactic implication // 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) bool implication_neg(const formula* f1, const formula* f2, bool right) ... ... @@ -2784,65 +2784,59 @@ namespace spot constant* neutral = is_and ? 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; ++f2; while (f2 != res->end()) const formula* fi = check->nth(i); const formula* fo = check->all_but(i); // 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); // if f1 => f2, then f1 | f2 = f2 // if f2 => f1, then f1 & f2 = f2 if ((op == multop::Or && c_->implication(*f1, *f2)) || (op == multop::And && c_->implication(*f2, *f1))) check->destroy(); check = is_multop(fo, op); if (!check) { // Remove f1. (*f1)->destroy(); *f1 = 0; ++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(); result_ = fo; for (unsigned j = 0; j < i; ++j) (*res)[j]->destroy(); delete res; result_ = neutral; return; } else ++f2; --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; for (unsigned j = 0; j < i; ++j) (*res)[j]->destroy(); delete res; return; } else { fo->destroy(); res->push_back(fi->clone()); ++i; } ++f1; } check->destroy(); } assert(!res->empty()); ... ... @@ -4417,278 +4411,345 @@ namespace spot formula::opkind fk = f->kind(); formula::opkind gk = g->kind(); // Deal with all lines except the first two. switch (fk) { case formula::Constant: case formula::AtomicProp: case formula::BUnOp: case formula::AutomatOp: break; case formula::UnOp: // 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) { const unop* f_ = down_cast(f); unop::type fo = f_->op(); if ((fo == unop::X || fo == unop::F) && g->is_eventual() && syntactic_implication(f_->child(), g)) return true; if (gk == formula::UnOp) { const unop* g_ = down_cast(g); unop::type go = g_->op(); if (fo == unop::X) { if (go == unop::X && syntactic_implication(f_->child(), g_->child())) return true; } } else if (gk == formula::BinOp && fo == unop::G) { const binop* g_ = down_cast(g); binop::type go = g_->op(); const formula* g1 = g_->first(); const formula* g2 = g_->second(); if ((go == binop::U || go == binop::R) && syntactic_implication(f_->child(), g2)) return true; else if (go == binop::W && (syntactic_implication(f_->child(), g1) || syntactic_implication(f_->child(), g2))) return true; else if (go == binop::M && (syntactic_implication(f_->child(), g1) && syntactic_implication(f_->child(), g2))) return true; } // First column. if (fo == unop::G && syntactic_implication(f_->child(), g)) return true; case formula::Constant: case formula::AtomicProp: case formula::BUnOp: case formula::AutomatOp: break; } case formula::BinOp: { const binop* f_ = down_cast(f); binop::type fo = f_->op(); const formula* f1 = f_->first(); const formula* f2 = f_->second(); case formula::UnOp: { const unop* f_ = down_cast(f); unop::type fo = f_->op(); if (gk == formula::UnOp) { const unop* g_ = down_cast(g); unop::type go = g_->op(); if (go == unop::F) if ((fo == unop::X || fo == unop::F) && g->is_eventual() && syntactic_implication(f_->child(), g)) return true; if (gk == formula::UnOp) { const unop* g_ = down_cast(g); unop::type go = g_->op(); if (fo == unop::X) { if (go == unop::X && syntactic_implication(f_->child(), g_->child())) return true; } } else if (gk == formula::BinOp && fo == unop::G) { const binop* g_ = down_cast(g); binop::type go = g_->op(); const formula* g1 = g_->first(); const formula* g2 = g_->second(); if ((go == binop::U || go == binop::R) && syntactic_implication(f_->child(), g2)) return true; else if (go == binop::W && (syntactic_implication(f_->child(), g1) || syntactic_implication(f_->child(), g2))) return true; else if (go == binop::M && (syntactic_implication(f_->child(), g1) && syntactic_implication(f_->child(), g2))) return true; } // First column. if (fo == unop::G && syntactic_implication(f_->child(), g)) return true; break; } case formula::BinOp: { const binop* f_ = down_cast(f); binop::type fo = f_->op(); const formula* f1 = f_->first(); const formula* f2 = f_->second(); if (gk == formula::UnOp) { const unop* g_ = down_cast(g); unop::type go = g_->op(); if (go == unop::F) { if (fo == binop::U) { if (syntactic_implication(f2, g_->child())) return true; } else if (fo == binop::W) { if (syntactic_implication(f1, g_->child()) && syntactic_implication(f2, g_->child())) return true; } else if (fo == binop::R) { if (syntactic_implication(f2, g_->child())) return true; } else if (fo == binop::M) { if (syntactic_implication(f1, g_->child()) || syntactic_implication(f2, g_->child())) return true; } } } else if (gk == formula::BinOp) { const binop* g_ = down_cast(g); binop::type go = g_->op(); const formula* g1 = g_->first(); const formula* g2 = g_->second(); if ((fo == binop::U && (go == binop::U || go == binop::W)) || (fo == binop::W && go == binop::W) || (fo == binop::R && go == binop::R) || (fo == binop::M && (go == binop::R || go == binop::M))) { if (syntactic_implication(f1, g1) && syntactic_implication(f2, g2)) return true; } else if (fo == binop::W && go == binop::U) { if (syntactic_implication(f1, g2) && syntactic_implication(f2, g2)) return true; } else if (fo == binop::R && go == binop::M) { if (syntactic_implication(f2, g1) && syntactic_implication(f2, g2)) return true; } else if ((fo == binop::U && (go == binop::R || go == binop::M)) || (fo == binop::W && go == binop::R)) { if (syntactic_implication(f1, g2) && syntactic_implication(f2, g1) && syntactic_implication(f2, g2)) return true; } else if ((fo == binop::M && (go == binop::U || go == binop::W)) || (fo == binop::R && go == binop::W)) { if (syntactic_implication(f1, g2) && syntactic_implication(f2, g1)) return true; } } // First column. if (fo == binop::U || fo == binop::W) { if (syntactic_implication(f1, g) && syntactic_implication(f2, g)) return true; } else if (fo == binop::R || fo == binop::M) { if (syntactic_implication(f2, g)) return true; } break; } case formula::MultOp: { const multop* f_ = down_cast(f); multop::type fo = f_->op(); unsigned fs = f_->size(); // First column. switch (fo) { case multop::Or: { if (fo == binop::U) { if (syntactic_implication(f2, g_->child())) return true; } else if (fo == binop::W) { if (syntactic_implication(f1, g_->child()) && syntactic_implication(f2, g_->child())) return true; } else if (fo == binop::R) 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)) { if (syntactic_implication(f2, g_->child())) return true; } else if (fo == binop::M) { if (syntactic_implication(f1, g_->child()) || syntactic_implication(f2, g_->child())) return true; bool r = syntactic_implication(bops, g); bops->destroy(); if (!r) break; } } } else if (gk == formula::BinOp) { const binop* g_ = down_cast(g); binop::type go = g_->op(); const formula* g1 = g_->first(); const formula* g2 = g_->second(); if ((fo == binop::U && (go == binop::U || go == binop::W)) || (fo == binop::W && go == binop::W) || (fo == binop::R && go == binop::R) || (fo == binop::M && (go == binop::R || go == binop::M))) { if (syntactic_implication(f1, g1) && syntactic_implication(f2, g2)) return true; } else if (fo == binop::W && go == binop::U) { if (syntactic_implication(f1, g2) && syntactic_implication(f2, g2)) return true; } else if (fo == binop::R && go == binop::M) { if (syntactic_implication(f2, g1) && syntactic_implication(f2, g2)) return true; } else if ((fo == binop::U && (go == binop::R || go == binop::M)) || (fo == binop::W && go == binop::R)) { if (syntactic_implication(f1, g2) && syntactic_implication(f2, g1) && syntactic_implication(f2, g2)) bool b = true; for (; i < fs; ++i) if (!syntactic_implication(f_->nth(i), g)) { b = false; break; } if (b) return true; break; } else if ((fo == binop::M && (go == binop::U || go == binop::W)) || (fo == binop::R && go == binop::W)) case multop::And: { if (syntactic_implication(f1, g2) && syntactic_implication(f2, g1)) return true; 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)) return true; break; } } // First column. if (fo == binop::U || fo == binop::W) { if (syntactic_implication(f1, g) && syntactic_implication(f2, g)) return true; } else if (fo == binop::R || fo == binop::M) { if (syntactic_implication(f2, g)) return true; } break; case multop::Concat: case multop::Fusion: case multop::AndNLM: case multop::AndRat: case multop::OrRat: break; } break; } } case formula::MultOp: // First two lines of the table. // (Don't check equality, it has already be done.) if (!g->is_boolean()) switch (gk) { const multop* f_ = down_cast(f); multop::type fo = f_->op(); unsigned fs = f_->size(); case formula::Constant: case formula::AtomicProp: case formula::BUnOp: case formula::AutomatOp: break; // First column. switch (fo) { case multop::Or: case formula::UnOp: { const unop* g_ = down_cast(g); unop::type go = g_->op(); if (go == unop::F) { bool b = true; for (unsigned i = 0; i < fs; ++i) if (!syntactic_implication(f_->nth(i), g)) { b &= false; break; } if (b) if (syntactic_implication(f, g_->child())) return true; break; } case multop::And: else if (go == unop::G || go == unop::X) { for (unsigned i = 0; i < fs; ++i) if (syntactic_implication(f_->nth(i), g)) return true; break; if (f->is_universal() && syntactic_implication(f, g_->child())) return true; } case multop::Concat: case multop::Fusion: case multop::AndNLM: case multop::AndRat: case multop::OrRat: break; } break; } } // First two lines. switch (gk) { case formula::Constant: case formula::AtomicProp: case formula::BUnOp: case formula::AutomatOp: break; case formula::UnOp: { const unop* g_ = down_cast(g); unop::type go = g_->op(); if (go == unop::F) { if (syntactic_implication(f, g_->child())) return true; } else if (go == unop::G || go == unop::X) { if (f->is_universal() && syntactic_implication(f, g_->child())) return true; } break; } case formula::BinOp: { const binop* g_ = down_cast(g); binop::type go = g_->op(); const formula* g1 = g_->first(); const formula* g2 = g_->second(); if (go == binop::U || go == binop::W) { if (syntactic_implication(f, g2)) return true; } else if (go == binop::M || go == binop::R) { if (syntactic_implication(f, g1) && syntactic_implication(f, g2)) return true; } break; } <