spot/src/ltlvisit/simplify.cc
Alexandre Duret-Lutz e4ecc2d465 simplify: add four simplification rules for GF and FG
GF(a|Xb) = GF(a|b)      GF(a|Fb) = GF(a|b)
FG(a&Xb) = FG(a&b)      FG(a&Gb) = FG(a&b)

* src/ltlvisit/simplify.cc: Implement them.
* NEWS, doc/tl/tl.tex: Document them.
* src/ltltest/reduccmp.test: Test then.
2013-01-11 17:16:17 +01:00

4482 lines
113 KiB
C++

// -*- coding: utf-8 -*-
// Copyright (C) 2011, 2012, 2013 Laboratoire de Recherche et
// Developpement de l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <iostream>
//#define TRACE
#ifdef TRACE
#define trace std::cerr
#else
#define trace while (0) std::cerr
#endif
#include "simplify.hh"
#include "misc/hash.hh"
#include "ltlast/allnodes.hh"
#include "ltlast/visitor.hh"
#include "ltlvisit/contain.hh"
#include "ltlvisit/tostring.hh"
#include "ltlvisit/snf.hh"
#include <cassert>
namespace spot
{
namespace ltl
{
// The name of this class is public, but not its contents.
class ltl_simplifier_cache
{
typedef Sgi::hash_map<const formula*, const formula*,
ptr_hash<formula> > f2f_map;
typedef Sgi::hash_map<const formula*, bdd,
ptr_hash<formula> > f2b_map;
typedef std::pair<const formula*, const formula*> pairf;
typedef std::map<pairf, bool> syntimpl_cache_t;
public:
bdd_dict* dict;
ltl_simplifier_options options;
language_containment_checker lcc;
~ltl_simplifier_cache()
{
{
f2f_map::iterator i = simplified_.begin();
f2f_map::iterator end = simplified_.end();
while (i != end)
{
f2f_map::iterator old = i++;
old->second->destroy();
old->first->destroy();
}
}
{
f2f_map::iterator i = nenoform_.begin();
f2f_map::iterator end = nenoform_.end();
while (i != end)
{
f2f_map::iterator old = i++;
old->second->destroy();
old->first->destroy();
}
}
{
f2b_map::iterator i = as_bdd_.begin();
f2b_map::iterator end = as_bdd_.end();
while (i != end)
{
f2b_map::iterator old = i++;
old->first->destroy();
}
}
{
syntimpl_cache_t::iterator i = syntimpl_.begin();
syntimpl_cache_t::iterator end = syntimpl_.end();
while (i != end)
{
syntimpl_cache_t::iterator old = i++;
old->first.first->destroy();
old->first.second->destroy();
}
}
{
snf_cache::iterator i = snf_cache_.begin();
snf_cache::iterator end = snf_cache_.end();
while (i != end)
{
snf_cache::iterator old = i++;
old->second->destroy();
old->first->destroy();
}
}
dict->unregister_all_my_variables(this);
}
ltl_simplifier_cache(bdd_dict* d)
: dict(d), lcc(d, true, true, false, false)
{
}
ltl_simplifier_cache(bdd_dict* d, const ltl_simplifier_options& opt)
: dict(d), options(opt), lcc(d, true, true, false, false)
{
options.containment_checks |= options.containment_checks_stronger;
}
void
print_stats(std::ostream& os) const
{
os << "simplified formulae: " << simplified_.size() << " entries\n"
<< "negative normal form: " << nenoform_.size() << " entries\n"
<< "syntactic implications: " << syntimpl_.size() << " entries\n"
<< "boolean to bdd: " << as_bdd_.size() << " entries\n"
<< "star normal form: " << snf_cache_.size() << " entries\n";
}
void
clear_as_bdd_cache()
{
f2b_map::iterator i = as_bdd_.begin();
f2b_map::iterator end = as_bdd_.end();
while (i != end)
{
f2b_map::iterator old = i++;
old->first->destroy();
}
as_bdd_.clear();
}
// Convert a Boolean formula into a BDD for easier comparison.
bdd
as_bdd(const formula* f)
{
// Lookup the result in case it has already been computed.
f2b_map::const_iterator it = as_bdd_.find(f);
if (it != as_bdd_.end())
return it->second;
bdd result = bddfalse;
switch (f->kind())
{
case formula::Constant:
if (f == constant::true_instance())
result = bddtrue;
else if (f == constant::false_instance())
result = bddfalse;
else
assert(!"Unsupported operator");
break;
case formula::AtomicProp:
result = bdd_ithvar(dict->register_proposition(f, this));
break;
case formula::UnOp:
{
const unop* uo = static_cast<const unop*>(f);
assert(uo->op() == unop::Not);
result = !as_bdd(uo->child());
break;
}
case formula::BinOp:
{
const binop* bo = static_cast<const binop*>(f);
int op = 0;
switch (bo->op())
{
case binop::Xor:
op = bddop_xor;
break;
case binop::Implies:
op = bddop_imp;
break;
case binop::Equiv:
op = bddop_biimp;
break;
default:
assert(!"Unsupported operator");
}
result = bdd_apply(as_bdd(bo->first()), as_bdd(bo->second()), op);
break;
}
case formula::MultOp:
{
const multop* mo = static_cast<const multop*>(f);
switch (mo->op())
{
case multop::And:
{
result = bddtrue;
unsigned s = mo->size();
for (unsigned n = 0; n < s; ++n)
result &= as_bdd(mo->nth(n));
break;
}
case multop::Or:
{
result = bddfalse;
unsigned s = mo->size();
for (unsigned n = 0; n < s; ++n)
result |= as_bdd(mo->nth(n));
break;
}
case multop::AndNLM:
case multop::AndRat:
case multop::OrRat:
case multop::Concat:
case multop::Fusion:
assert(!"Unsupported operator");
break;
}
break;
}
case formula::BUnOp:
case formula::AutomatOp:
assert(!"Unsupported operator");
break;
}
// Cache the result before returning.
as_bdd_[f->clone()] = result;
return result;
}
const formula*
lookup_nenoform(const formula* f)
{
f2f_map::const_iterator i = nenoform_.find(f);
if (i == nenoform_.end())
return 0;
return i->second->clone();
}
void
cache_nenoform(const formula* orig, const formula* nenoform)
{
nenoform_[orig->clone()] = nenoform->clone();
}
// Return true iff the option set (syntactic implication
// or containment checks) allow to prove that f1 => f2.
bool
implication(const formula* f1, const formula* f2)
{
trace << "[->] does " << to_string(f1) << " implies "
<< to_string(f2) << " ?" << std::endl;
if ((options.synt_impl && syntactic_implication(f1, f2))
|| (options.containment_checks && contained(f1, f2)))
{
trace << "[->] Yes" << std::endl;
return true;
}
trace << "[->] No" << std::endl;
return false;
}
// Return true if f1 => f2 syntactically
bool
syntactic_implication(const formula* f1, const formula* f2);
bool
syntactic_implication_aux(const formula* f1, const formula* f2);
// Return true if f1 => f2
bool
contained(const formula* f1, const formula* f2)
{
if (!f1->is_psl_formula() || !f2->is_psl_formula())
return false;
return lcc.contained(f1, f2);
}
// If right==false, true if !f1 => f2, false otherwise.
// If right==true, true if f1 => !f2, false otherwise.
bool
syntactic_implication_neg(const formula* f1, const formula* f2,
bool right);
// Return true if f1 => !f2
bool contained_neg(const formula* f1, const formula* f2)
{
if (!f1->is_psl_formula() || !f2->is_psl_formula())
return false;
trace << "[CN] Does (" << to_string(f1) << ") implies !("
<< to_string(f2) << ") ?" << std::endl;
if (lcc.contained_neg(f1, f2))
{
trace << "[CN] Yes" << std::endl;
return true;
}
else
{
trace << "[CN] No" << std::endl;
return false;
}
}
// Return true if f1 => !f2
bool neg_contained(const formula* f1, const formula* f2)
{
if (!f1->is_psl_formula() || !f2->is_psl_formula())
return false;
trace << "[NC] Does (" << to_string(f1) << ") implies !("
<< to_string(f2) << ") ?" << std::endl;
if (lcc.neg_contained(f1, f2))
{
trace << "[NC] Yes" << std::endl;
return true;
}
else
{
trace << "[NC] No" << std::endl;
return false;
}
}
// 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=true)
bool
implication_neg(const formula* f1, const formula* f2, bool right)
{
trace << "[IN] Does " << (right ? "(" : "!(")
<< to_string(f1) << ") implies "
<< (right ? "!(" : "(") << to_string(f2) << ") ?"
<< std::endl;
if ((options.synt_impl && syntactic_implication_neg(f1, f2, right))
|| (options.containment_checks && right && contained_neg(f1, f2))
|| (options.containment_checks && !right && neg_contained(f1, f2)))
{
trace << "[IN] Yes" << std::endl;
return true;
}
else
{
trace << "[IN] No" << std::endl;
return false;
}
}
const formula*
lookup_simplified(const formula* f)
{
f2f_map::const_iterator i = simplified_.find(f);
if (i == simplified_.end())
return 0;
return i->second->clone();
}
void
cache_simplified(const formula* orig, const formula* simplified)
{
simplified_[orig->clone()] = simplified->clone();
}
const formula*
star_normal_form(const formula* f)
{
return ltl::star_normal_form(f, &snf_cache_);
}
private:
f2b_map as_bdd_;
f2f_map simplified_;
f2f_map nenoform_;
syntimpl_cache_t syntimpl_;
snf_cache snf_cache_;
};
namespace
{
//////////////////////////////////////////////////////////////////////
//
// NEGATIVE_NORMAL_FORM_VISITOR
//
//////////////////////////////////////////////////////////////////////
// Forward declaration.
const formula*
nenoform_recursively(const formula* f,
bool negated,
ltl_simplifier_cache* c);
class negative_normal_form_visitor: public visitor
{
public:
negative_normal_form_visitor(bool negated, ltl_simplifier_cache* c)
: negated_(negated), cache_(c)
{
}
virtual
~negative_normal_form_visitor()
{
}
const formula* result() const
{
return result_;
}
void
visit(const atomic_prop* ap)
{
const formula* f = ap->clone();
if (negated_)
result_ = unop::instance(unop::Not, f);
else
result_ = f;
}
void
visit(const constant* c)
{
// Negation of constants is taken care of in the constructor
// of unop::Not, so these cases should be caught by
// nenoform_recursively().
assert(!negated_);
result_ = c;
return;
}
void
visit(const unop* uo)
{
const formula* f = uo->child();
unop::type op = uo->op();
switch (op)
{
case unop::Not:
// "Not"s should be caught by nenoform_recursively().
assert(!"Not should not occur");
//result_ = recurse_(f, negated_ ^ true);
return;
case unop::X:
/* !Xa == X!a */
result_ = unop::instance(unop::X, recurse(f));
return;
case unop::F:
/* !Fa == G!a */
result_ = unop::instance(negated_ ? unop::G : unop::F,
recurse(f));
return;
case unop::G:
/* !Ga == F!a */
result_ = unop::instance(negated_ ? unop::F : unop::G,
recurse(f));
return;
case unop::Closure:
result_ = unop::instance(negated_ ?
unop::NegClosure : unop::Closure,
recurse_(f, false));
return;
case unop::NegClosure:
case unop::NegClosureMarked:
result_ = unop::instance(negated_ ?
unop::Closure : op,
recurse_(f, false));
return;
/* !Finish(x), is not simplified */
case unop::Finish:
result_ = unop::instance(uo->op(), recurse_(f, false));
if (negated_)
result_ = unop::instance(unop::Not, result_);
return;
}
/* Unreachable code. */
assert(0);
}
void
visit(const bunop* bo)
{
// !(a*) should never occur.
assert(!negated_);
result_ = bunop::instance(bo->op(), recurse_(bo->child(), false),
bo->min(), bo->max());
}
const formula* equiv_or_xor(bool equiv,
const formula* f1,
const formula* f2)
{
// Rewrite a<=>b as (a&b)|(!a&!b)
if (equiv)
return
multop::instance(multop::Or,
multop::instance(multop::And,
recurse_(f1, false),
recurse_(f2, false)),
multop::instance(multop::And,
recurse_(f1, true),
recurse_(f2, true)));
else
// Rewrite a^b as (a&!b)|(!a&b)
return
multop::instance(multop::Or,
multop::instance(multop::And,
recurse_(f1, false),
recurse_(f2, true)),
multop::instance(multop::And,
recurse_(f1, true),
recurse_(f2, false)));
}
void
visit(const binop* bo)
{
const formula* f1 = bo->first();
const formula* f2 = bo->second();
switch (bo->op())
{
case binop::Xor:
// !(a ^ b) == a <=> b
result_ = equiv_or_xor(negated_, f1, f2);
return;
case binop::Equiv:
// !(a <=> b) == a ^ b
result_ = equiv_or_xor(!negated_, f1, f2);
return;
case binop::Implies:
if (negated_)
// !(a => b) == a & !b
result_ = multop::instance(multop::And,
recurse_(f1, false),
recurse_(f2, true));
else // a => b == !a | b
result_ = multop::instance(multop::Or,
recurse_(f1, true),
recurse_(f2, false));
return;
case binop::U:
// !(a U b) == !a R !b
result_ = binop::instance(negated_ ? binop::R : binop::U,
recurse(f1), recurse(f2));
return;
case binop::R:
// !(a R b) == !a U !b
result_ = binop::instance(negated_ ? binop::U : binop::R,
recurse(f1), recurse(f2));
return;
case binop::W:
// !(a W b) == !a M !b
result_ = binop::instance(negated_ ? binop::M : binop::W,
recurse(f1), recurse(f2));
return;
case binop::M:
// !(a M b) == !a W !b
result_ = binop::instance(negated_ ? binop::W : binop::M,
recurse(f1), recurse(f2));
return;
case binop::UConcat:
// !(a []-> b) == a<>-> !b
result_ = binop::instance(negated_ ?
binop::EConcat : binop::UConcat,
recurse_(f1, false), recurse(f2));
return;
case binop::EConcat:
// !(a <>-> b) == a[]-> !b
result_ = binop::instance(negated_ ?
binop::UConcat : binop::EConcat,
recurse_(f1, false), recurse(f2));
return;
case binop::EConcatMarked:
// !(a <>-> b) == a[]-> !b
result_ = binop::instance(negated_ ?
binop::UConcat :
binop::EConcatMarked,
recurse_(f1, false), recurse(f2));
return;
}
// Unreachable code.
assert(0);
}
void
visit(const automatop* ao)
{
bool negated = negated_;
negated_ = false;
automatop::vec* res = new automatop::vec;
unsigned aos = ao->size();
for (unsigned i = 0; i < aos; ++i)
res->push_back(recurse(ao->nth(i)));
result_ = automatop::instance(ao->get_nfa(), res, negated);
}
void
visit(const multop* mo)
{
multop::type op = mo->op();
/* !(a & b & c) == !a | !b | !c */
/* !(a | b | c) == !a & !b & !c */
if (negated_)
switch (op)
{
case multop::And:
op = multop::Or;
break;
case multop::Or:
op = multop::And;
break;
case multop::Concat:
case multop::Fusion:
case multop::AndNLM:
case multop::OrRat:
case multop::AndRat:
break;
}
multop::vec* res = new multop::vec;
unsigned mos = mo->size();
switch (op)
{
case multop::And:
case multop::Or:
{
for (unsigned i = 0; i < mos; ++i)
res->push_back(recurse(mo->nth(i)));
result_ = multop::instance(op, res);
break;
}
case multop::Concat:
case multop::Fusion:
case multop::AndNLM:
case multop::AndRat:
case multop::OrRat:
{
for (unsigned i = 0; i < mos; ++i)
res->push_back(recurse_(mo->nth(i), false));
result_ = multop::instance(op, res);
assert(!negated_);
}
}
}
const formula*
recurse_(const formula* f, bool negated)
{
return nenoform_recursively(f, negated, cache_);
}
const formula*
recurse(const formula* f)
{
return recurse_(f, negated_);
}
protected:
const formula* result_;
bool negated_;
ltl_simplifier_cache* cache_;
};
const formula*
nenoform_recursively(const formula* f,
bool negated,
ltl_simplifier_cache* c)
{
if (const unop* uo = is_Not(f))
{
negated = !negated;
f = uo->child();
}
const formula* key = f;
if (negated)
key = unop::instance(unop::Not, f->clone());
const formula* result = c->lookup_nenoform(key);
if (result)
goto done;
if (key->is_in_nenoform()
|| (c->options.nenoform_stop_on_boolean && key->is_boolean()))
{
result = key->clone();
}
else
{
negative_normal_form_visitor v(negated, c);
f->accept(v);
result = v.result();
}
c->cache_nenoform(key, result);
done:
if (negated)
key->destroy();
return result;
}
//////////////////////////////////////////////////////////////////////
//
// SIMPLIFY_VISITOR
//
//////////////////////////////////////////////////////////////////////
// Forward declaration.
const formula*
simplify_recursively(const formula* f, ltl_simplifier_cache* c);
// X(a) R b or X(a) M b
// This returns a.
const formula*
is_XRM(const formula* f)
{
const binop* bo = is_binop(f, binop::R, binop::M);
if (!bo)
return 0;
const unop* uo = is_X(bo->first());
if (!uo)
return 0;
return uo->child();
}
// X(a) W b or X(a) U b
// This returns a.
const formula*
is_XWU(const formula* f)
{
const binop* bo = is_binop(f, binop::W, binop::U);
if (!bo)
return 0;
const unop* uo = is_X(bo->first());
if (!uo)
return 0;
return uo->child();
}
// b & X(b W a) or b & X(b U a)
// This returns (b W a) or (b U a).
const binop*
is_bXbWU(const formula* f)
{
const multop* mo = is_multop(f, multop::And);
if (!mo)
return 0;
unsigned s = mo->size();
for (unsigned pos = 0; pos < s; ++pos)
{
const unop* u = is_X(mo->nth(pos));
if (!u)
continue;
const binop* bo = is_binop(u->child(), binop::U, binop::W);
if (!bo)
continue;
const formula* b = mo->all_but(pos);
bool result = (b == bo->first());
b->destroy();
if (result)
return bo;
}
return 0;
}
// b | X(b R a) or b | X(b M a)
// This returns (b R a) or (b M a).
const binop*
is_bXbRM(const formula* f)
{
const multop* mo = is_multop(f, multop::Or);
if (!mo)
return 0;
unsigned s = mo->size();
for (unsigned pos = 0; pos < s; ++pos)
{
const unop* u = is_X(mo->nth(pos));
if (!u)
continue;
const binop* bo = is_binop(u->child(), binop::R, binop::M);
if (!bo)
continue;
const formula* b = mo->all_but(pos);
bool result = (b == bo->first());
b->destroy();
if (result)
return bo;
}
return 0;
}
const formula*
unop_multop(unop::type uop, multop::type mop, multop::vec* v)
{
return unop::instance(uop, multop::instance(mop, v));
}
const formula*
unop_unop_multop(unop::type uop1, unop::type uop2, multop::type mop,
multop::vec* v)
{
return unop::instance(uop1, unop_multop(uop2, mop, v));
}
const formula*
unop_unop(unop::type uop1, unop::type uop2, const formula* f)
{
return unop::instance(uop1, unop::instance(uop2, f));
}
struct mospliter
{
enum what { Split_GF = (1 << 0),
Strip_GF = (1 << 1) | (1 << 0),
Split_FG = (1 << 2),
Strip_FG = (1 << 3) | (1 << 2),
Split_F = (1 << 4),
Strip_F = (1 << 5) | (1 << 4),
Split_G = (1 << 6),
Strip_G = (1 << 7) | (1 << 6),
Strip_X = (1 << 8),
Split_U_or_W = (1 << 9),
Split_R_or_M = (1 << 10),
Split_EventUniv = (1 << 11),
Split_Event = (1 << 12),
Split_Univ = (1 << 13),
Split_Bool = (1 << 14)
};
void init()
{
res_GF = (split_ & Split_GF) ? new multop::vec : 0;
res_FG = (split_ & Split_FG) ? new multop::vec : 0;
res_F = (split_ & Split_F) ? new multop::vec : 0;
res_G = (split_ & Split_G) ? new multop::vec : 0;
res_X = (split_ & Strip_X) ? new multop::vec : 0;
res_U_or_W = (split_ & Split_U_or_W) ? new multop::vec : 0;
res_R_or_M = (split_ & Split_R_or_M) ? new multop::vec : 0;
res_EventUniv = (split_ & Split_EventUniv) ? new multop::vec : 0;
res_Event = (split_ & Split_Event) ? new multop::vec : 0;
res_Univ = (split_ & Split_Univ) ? new multop::vec : 0;
res_Bool = (split_ & Split_Bool) ? new multop::vec : 0;
res_other = new multop::vec;
}
void process(const formula* f)
{
switch (f->kind())
{
case formula::UnOp:
{
const unop* uo = static_cast<const unop*>(f);
const formula* c = uo->child();
switch (uo->op())
{
case unop::X:
if (res_X)
{
res_X->push_back(c->clone());
return;
}
break;
case unop::F:
if (res_FG)
if (const unop* cc = is_G(c))
{
res_FG->push_back(((split_ & Strip_FG) == Strip_FG
? cc->child() : f)->clone());
return;
}
if (res_F)
{
res_F->push_back(((split_ & Strip_F) == Strip_F
? c : f)->clone());
return;
}
break;
case unop::G:
if (res_GF)
if (const unop* cc = is_F(c))
{
res_GF->push_back(((split_ & Strip_GF) == Strip_GF
? cc->child() : f)->clone());
return;
}
if (res_G)
{
res_G->push_back(((split_ & Strip_G) == Strip_G
? c : f)->clone());
return;
}
break;
default:
break;
}
}
break;
case formula::BinOp:
{
const binop* bo = static_cast<const binop*>(f);
switch (bo->op())
{
case binop::U:
case binop::W:
if (res_U_or_W)
{
res_U_or_W->push_back(bo->clone());
return;
}
break;
case binop::R:
case binop::M:
if (res_R_or_M)
{
res_R_or_M->push_back(bo->clone());
return;
}
break;
default:
break;
}
}
break;
default:
if (res_Bool && f->is_boolean())
{
res_Bool->push_back(f->clone());
return;
}
break;
}
if (c_->options.event_univ)
{
bool e = f->is_eventual();
bool u = f->is_universal();
if (res_EventUniv && e && u)
{
res_EventUniv->push_back(f->clone());
return;
}
if (res_Event && e)
{
res_Event->push_back(f->clone());
return;
}
if (res_Univ && u)
{
res_Univ->push_back(f->clone());
return;
}
}
res_other->push_back(f->clone());
}
mospliter(unsigned split, multop::vec* v, ltl_simplifier_cache* cache)
: split_(split), c_(cache)
{
init();
multop::vec::const_iterator end = v->end();
for (multop::vec::const_iterator i = v->begin(); i < end; ++i)
{
if (*i) // skip null pointers left by previous simplifications
{
process(*i);
(*i)->destroy();
}
}
delete v;
}
mospliter(unsigned split, const multop* mo,
ltl_simplifier_cache* cache)
: split_(split), c_(cache)
{
init();
unsigned mos = mo->size();
for (unsigned i = 0; i < mos; ++i)
{
const formula* f = simplify_recursively(mo->nth(i), cache);
process(f);
f->destroy();
}
mo->destroy();
}
multop::vec* res_GF;
multop::vec* res_FG;
multop::vec* res_F;
multop::vec* res_G;
multop::vec* res_X;
multop::vec* res_U_or_W;
multop::vec* res_R_or_M;
multop::vec* res_Event;
multop::vec* res_Univ;
multop::vec* res_EventUniv;
multop::vec* res_Bool;
multop::vec* res_other;
unsigned split_;
ltl_simplifier_cache* c_;
};
class simplify_visitor: public visitor
{
public:
simplify_visitor(ltl_simplifier_cache* cache)
: c_(cache), opt_(cache->options)
{
}
virtual ~simplify_visitor()
{
}
const formula*
result() const
{
return result_;
}
void
visit(const atomic_prop* ap)
{
result_ = ap->clone();
}
void
visit(const constant* c)
{
result_ = c;
}
void
visit(const bunop* bo)
{
bunop::type op = bo->op();
unsigned min = bo->min();
const formula* h = recurse(bo->child());
switch (op)
{
case bunop::Star:
if (h->accepts_eword())
min = 0;
if (min == 0)
{
const formula* s = c_->star_normal_form(h);
h->destroy();
h = s;
}
result_ = bunop::instance(op, h, min, bo->max());
break;
}
}
// if !neg build c&X(c&X(...&X(tail))) with n occurences of c
// if neg build !c|X(!c|X(...|X(tail))).
const formula*
dup_b_x_tail(bool neg, const formula* c,
const formula* tail, unsigned n)
{
c->clone();
multop::type mop;
if (neg)
{
c = unop::instance(unop::Not, c);
mop = multop::Or;
}
else
{
mop = multop::And;
}
while (n--)
{
tail = unop::instance(unop::X, tail);
tail = // b&X(tail) or !b|X(tail)
multop::instance(mop, c->clone(), tail);
}
c->destroy();
return tail;
}
void
visit(const unop* uo)
{
result_ = recurse(uo->child());
unop::type op = uo->op();
switch (op)
{
case unop::Not:
break;
case unop::X:
// X(constant) = constant is a trivial identity, but if
// the constant has been constructed by recurse() this
// identity has not been applied.
if (is_constant(result_))
return;
// Xf = f if f is both eventual and universal.
if (result_->is_universal() && result_->is_eventual())
{
if (opt_.event_univ)
return;
// If EventUniv simplification is disabled, use
// only the following basic rewriting rules:
// XGF(f) = GF(f) and XFG(f) = FG(f)
// The former comes from Somenzi&Bloem (CAV'00).
// It's not clear why they do not list the second.
if (opt_.reduce_basics &&
(is_GF(result_) || is_FG(result_)))
return;
}
// If Xa = a, keep only a.
if (opt_.containment_checks_stronger
&& c_->lcc.equal(result_, uo))
return;
// Disabled: X(f1 & GF(f2)) = X(f1) & GF(f2)
// Disabled: X(f1 | GF(f2)) = X(f1) | GF(f2)
// Disabled: X(f1 & FG(f2)) = X(f1) & FG(f2)
// Disabled: X(f1 | FG(f2)) = X(f1) | FG(f2)
// The above make more sense when reversed,
// so see them in the And and Or rewritings.
break;
case unop::F:
// F(constant) = constant is a trivial identity, but if
// the constant has been constructed by recurse() this
// identity has not been applied.
if (is_constant(result_))
return;
// If f is a pure eventuality formula then F(f)=f.
if (opt_.event_univ && result_->is_eventual())
return;
if (opt_.reduce_basics)
{
// F(a U b) = F(b)
const binop* bo = is_U(result_);
if (bo)
{
const formula* r =
unop::instance(unop::F, bo->second()->clone());
bo->destroy();
result_ = recurse_destroy(r);
return;
}
// F(a M b) = F(a & b)
bo = is_M(result_);
if (bo)
{
const formula* r =
unop::instance(unop::F,
multop::instance(multop::And,
bo->first()->clone(),
bo->second()->clone()));
bo->destroy();
result_ = recurse_destroy(r);
return;
}
// FX(a) = XF(a)
if (const unop* u = is_X(result_))
{
const formula* res =
unop_unop(unop::X, unop::F, u->child()->clone());
u->destroy();
// FXX(a) = XXF(a) ...
// FXG(a) = XFG(a) = FG(a) ...
result_ = recurse_destroy(res);
return;
}
// FG(a & Xb) = FG(a & b)
// FG(a & Gb) = FG(a & b)
if (const unop* g = is_G(result_))
if (const multop* m = is_And(g->child()))
if (!m->is_boolean())
{
m->clone();
mospliter s(mospliter::Strip_G | mospliter::Strip_X,
m, c_);
if (!s.res_G->empty() || !s.res_X->empty())
{
result_->destroy();
s.res_other->insert(s.res_other->begin(),
s.res_G->begin(),
s.res_G->end());
delete s.res_G;
s.res_other->insert(s.res_other->begin(),
s.res_X->begin(),
s.res_X->end());
delete s.res_X;
const formula* in =
multop::instance(multop::And, s.res_other);
result_ =
recurse_destroy(unop_unop(unop::F, unop::G,
in));
return;
}
else
{
for (multop::vec::iterator j =
s.res_other->begin();
j != s.res_other->end(); ++j)
if (*j)
(*j)->destroy();
delete s.res_other;
delete s.res_G;
delete s.res_X;
// and continue...
}
}
}
// if Fa => a, keep a.
if (opt_.containment_checks_stronger
&& c_->lcc.contained(uo, result_))
return;
// Disabled: F(f1 & GF(f2)) = F(f1) & GF(f2)
//
// As is, these two formulae are translated into
// equivalent Büchi automata so the rewriting is
// useless.
//
// However when taken in a larger formula such as F(f1
// & GF(f2)) | F(a & GF(b)), this rewriting used to
// produce (F(f1) & GF(f2)) | (F(a) & GF(b)), missing
// the opportunity to apply the F(E1)|F(E2) = F(E1|E2)
// rule which really helps the translation. F((f1 &
// GF(f2)) | (a & GF(b))) is indeed easier to translate.
//
// So let's not consider this rewriting rule.
break;
case unop::G:
// G(constant) = constant is a trivial identity, but if
// the constant has been constructed by recurse() this
// identity has not been applied.
if (is_constant(result_))
return;
// If f is a pure universality formula then G(f)=f.
if (opt_.event_univ && result_->is_universal())
return;
if (opt_.reduce_basics)
{
// G(a R b) = G(b)
const binop* bo = is_R(result_);
if (bo)
{
const formula* r =
unop::instance(unop::G, bo->second()->clone());
bo->destroy();
result_ = recurse_destroy(r);
return;
}
// G(a W b) = G(a | b)
bo = is_W(result_);
if (bo)
{
const formula* r =
unop::instance(unop::G,
multop::instance(multop::Or,
bo->first()->clone(),
bo->second()->clone()));
bo->destroy();
result_ = recurse_destroy(r);
return;
}
// GX(a) = XG(a)
if (const unop* u = is_X(result_))
{
const formula* res =
unop_unop(unop::X, unop::G, u->child()->clone());
u->destroy();
// GXX(a) = XXG(a) ...
// GXF(a) = XGF(a) = GF(a) ...
result_ = recurse_destroy(res);
return;
}
// G(f1|f2|GF(f3)|GF(f4)|f5|f6) =
// G(f1|f2) | GF(f3|f4) | f5 | f6
// if f5 and f6 are both eventual and universal.
if (const multop* mo = is_Or(result_))
{
mo->clone();
mospliter s(mospliter::Strip_GF |
mospliter::Split_EventUniv,
mo, c_);
s.res_EventUniv->
push_back(unop_multop(unop::G, multop::Or,
s.res_other));
s.res_EventUniv->
push_back(unop_unop_multop(unop::G, unop::F,
multop::Or, s.res_GF));
result_ = multop::instance(multop::Or,
s.res_EventUniv);
if (result_ != uo)
{
mo->destroy();
result_ = recurse_destroy(result_);
return;
}
else
{
// Revert to the previous value of result_,
// for the next simplification.
result_->destroy();
result_ = mo;
}
}
// GF(a | Xb) = GF(a | b)
// GF(a | Fb) = GF(a | b)
if (const unop* f = is_F(result_))
if (const multop* m = is_Or(f->child()))
if (!m->is_boolean())
{
m->clone();
mospliter s(mospliter::Strip_F | mospliter::Strip_X,
m, c_);
if (!s.res_F->empty() || !s.res_X->empty())
{
result_->destroy();
s.res_other->insert(s.res_other->begin(),
s.res_F->begin(),
s.res_F->end());
delete s.res_F;
s.res_other->insert(s.res_other->begin(),
s.res_X->begin(),
s.res_X->end());
delete s.res_X;
const formula* in =
multop::instance(multop::Or, s.res_other);
result_ =
recurse_destroy(unop_unop(unop::G, unop::F,
in));
return;
}
else
{
for (multop::vec::iterator j =
s.res_other->begin();
j != s.res_other->end(); ++j)
if (*j)
(*j)->destroy();
delete s.res_other;
delete s.res_F;
delete s.res_X;
// and continue...
}
}
}
// if a => Ga, keep a.
if (opt_.containment_checks_stronger
&& c_->lcc.contained(result_, uo))
return;
break;
case unop::Closure:
case unop::NegClosure:
case unop::NegClosureMarked:
// {e} = 1 if e accepts [*0]
// !{e} = 0 if e accepts [*0]
if (result_->accepts_eword())
{
result_->destroy();
result_ = ((op == unop::Closure)
? constant::true_instance()
: constant::false_instance());
return;
}
if (!opt_.reduce_size_strictly)
if (const multop* mo = is_OrRat(result_))
{
// {a₁|a₂} = {a₁}| {a₂}
// !{a₁|a₂} = !{a₁}&!{a₂}
unsigned s = mo->size();
multop::vec* v = new multop::vec;
for (unsigned n = 0; n < s; ++n)
v->push_back(unop::instance(op, mo->nth(n)->clone()));
mo->destroy();
result_ =
recurse_destroy(multop::instance(op == unop::Closure
? multop::Or
: multop::And, v));
return;
}
if (const multop* mo = is_Concat(result_))
{
unsigned end = mo->size() - 1;
// {b₁;b₂;b₃*;e₁;f₁;e₂;f₂;e₂;e₃;e₄}
// = b₁&X(b₂&X(b₃ W {e₁;f₁;e₂;f₂}))
// !{b₁;b₂;b₃*;e₁;f₁;e₂;f₂;e₂;e₃;e₄}
// = !b₁|X(!b₂|X(!b₃ M !{e₁;f₁;e₂;f₂}))
// if e denotes a term that accepts [*0]
// and b denotes a Boolean formula.
while (mo->nth(end)->accepts_eword())
--end;
unsigned start = 0;
while (start <= end)
{
const formula* r = mo->nth(start);
const bunop* es = is_KleenStar(r);
if ((r->is_boolean() && !opt_.reduce_size_strictly)
|| (es && es->child()->is_boolean()))
++start;
else
break;
}
unsigned s = end + 1 - start;
if (s != mo->size())
{
multop::vec* v = new multop::vec;
v->reserve(s);
for (unsigned n = start; n <= end; ++n)
v->push_back(mo->nth(n)->clone());
const formula* tail =
multop::instance(multop::Concat, v);
tail = unop::instance(op, tail);
bool doneg = op != unop::Closure;
for (unsigned n = start; n > 0;)
{
--n;
const formula* e = mo->nth(n);
// {b;f} = b & X{f}
// !{b;f} = !b | X!{f}
if (e->is_boolean())
{
tail = unop::instance(unop::X, tail);
e->clone();
if (doneg)
tail =
multop::instance(multop::Or,
unop::instance(unop::Not, e),
tail);
else
tail =
multop::instance(multop::And, e, tail);
}
// {b*;f} = b W {f}
// !{b*;f} = !b M !{f}
else
{
const bunop* es = is_KleenStar(e);
assert(es);
const formula* c = es->child()->clone();
if (doneg)
tail =
binop::instance(binop::M,
unop::instance(unop::Not, c),
tail);
else
tail = binop::instance(binop::W, c, tail);
}
}
mo->destroy();
result_ = recurse_destroy(tail);
return;
}
// {b[*i..j];c} = b&X(b&X(... b&X{b[*0..j-i];c}))
// !{b[*i..j];c} = !b&X(!b&X(... !b&X!{b[*0..j-i];c}))
if (!opt_.reduce_size_strictly)
if (const bunop* s = is_Star(mo->nth(0)))
{
const formula* c = s->child();
unsigned min = s->min();
if (c->is_boolean() && min > 0)
{
unsigned max = s->max();
if (max != bunop::unbounded)
max -= min;
unsigned ss = mo->size();
multop::vec* v = new multop::vec;
v->reserve(ss);
v->push_back(bunop::instance(bunop::Star,
c->clone(),
0, max));
for (unsigned n = 1; n < ss; ++n)
v->push_back(mo->nth(n)->clone());
const formula* tail =
multop::instance(multop::Concat, v);
tail = // {b[*0..j-i]} or !{b[*0..j-i]}
unop::instance(op, tail);
tail =
dup_b_x_tail(op != unop::Closure,
c, tail, min);
mo->destroy();
result_ = recurse_destroy(tail);
return;
}
}
}
// {b[*i..j]} = b&X(b&X(... b)) with i occurences of b
// !{b[*i..j]} = !b&X(!b&X(... !b))
if (!opt_.reduce_size_strictly)
if (const bunop* s = is_Star(result_))
{
const formula* c = s->child();
if (c->is_boolean())
{
unsigned min = s->min();
assert(min > 0);
const formula* tail;
if (op == unop::Closure)
tail =
dup_b_x_tail(false,
c, constant::true_instance(), min);
else
tail =
dup_b_x_tail(true,
c, constant::false_instance(), min);
result_->destroy();
result_ = recurse_destroy(tail);
return;
}
}
break;
case unop::Finish:
// No simplification
break;
}
result_ = unop::instance(op, result_);
}
// Return true iff reduction occurred.
bool
reduce_sere_ltl(binop::type bindop, const formula* a, const formula* b)
{
// All this function is documented assuming bindop ==
// UConcat, but by changing the following variable it can
// perform the rules for EConcat as well.
unop::type op_g;
binop::type op_w;
binop::type op_r;
multop::type op_and;
bool doneg;
if (bindop == binop::UConcat)
{
op_g = unop::G;
op_w = binop::W;
op_r = binop::R;
op_and = multop::And;
doneg = true;
}
else // EConcat & EConcatMarked
{
op_g = unop::F;
op_w = binop::M;
op_r = binop::U;
op_and = multop::Or;
doneg = false;
}
if (!opt_.reduce_basics)
return false;
if (const bunop* bu = is_Star(a))
{
// {[*]}[]->b = Gb
if (a == bunop::one_star())
{
a->destroy();
result_ = recurse_destroy(unop::instance(op_g, b));
return true;
}
const formula* s = bu->child();
unsigned min = bu->min();
unsigned max = bu->max();
// {s[*]}[]->b = b W !s if s is Boolean.
// {s[+]}[]->b = b W !s if s is Boolean.
if (s->is_boolean() && max == bunop::unbounded && min <= 1)
{
const formula* ns = // !s
doneg ? unop::instance(unop::Not, s->clone()) : s->clone();
result_ = // b W !s
binop::instance(op_w, b, ns);
bu->destroy();
result_ = recurse_destroy(result_);
return true;
}
if (opt_.reduce_size_strictly)
return false;
// {s[*i..j]}[]->b = {s;s;...;s[*1..j-i+1]}[]->b
// = {s}[]->X({s}[]->X(...[]->X({s[*1..j-i+1]}[]->b)))
// if i>0 and s does not accept the empty word
if (min == 0 || s->accepts_eword())
return false;
--min;
if (max != bunop::unbounded)
max -= min; // j-i+1
// Don't rewrite s[1..].
if (min == 0)
return false;
const formula* tail = // {s[*1..j-i]}[]->b
binop::instance(bindop,
bunop::instance(bunop::Star,
s->clone(), 1, max),
b);
for (unsigned n = 0; n < min; ++n)
tail = // {s}[]->X(tail)
binop::instance(bindop,
s->clone(),
unop::instance(unop::X, tail));
result_ = tail;
bu->destroy();
result_ = recurse_destroy(result_);
return true;
}
else if (const multop* mo = is_Concat(a))
{
unsigned s = mo->size() - 1;
const formula* last = mo->nth(s);
// {r;[*]}[]->b = {r}[]->Gb
if (last == bunop::one_star())
{
result_ =
binop::instance(bindop,
mo->all_but(s), unop::instance(op_g, b));
mo->destroy();
result_ = recurse_destroy(result_);
return true;
}
const formula* first = mo->nth(0);
// {[*];r}[]->b = G({r}[]->b)
if (first == bunop::one_star())
{
result_ =
unop::instance(op_g,
binop::instance(bindop, mo->all_but(0), b));
mo->destroy();
result_ = recurse_destroy(result_);
return true;
}
if (opt_.reduce_size_strictly)
return false;
// {r;s[*]}[]->b = {r}[]->(b & X(b W !s))
// if s is Boolean and r does not accept [*0];
if (const bunop* l = is_KleenStar(last)) // l = s[*]
if (l->child()->is_boolean())
{
const formula* r = mo->all_but(s);
if (!r->accepts_eword())
{
const formula* ns = // !s
doneg
? unop::instance(unop::Not, l->child()->clone())
: l->child()->clone();
const formula* w = // b W !s
binop::instance(op_w, b->clone(), ns);
const formula* x = // X(b W !s)
unop::instance(unop::X, w);
const formula* d = // b & X(b W !s)
multop::instance(op_and, b, x);
result_ = // {r}[]->(b & X(b W !s))
binop::instance(bindop, r, d);
mo->destroy();
result_ = recurse_destroy(result_);
return true;
}
r->destroy();
}
// {s[*];r}[]->b = !s R ({r}[]->b)
// if s is Boolean and r does not accept [*0];
if (const bunop* l = is_KleenStar(first))
if (l->child()->is_boolean())
{
const formula* r = mo->all_but(0);
if (!r->accepts_eword())
{
const formula* ns = // !s
doneg
? unop::instance(unop::Not, l->child()->clone())
: l->child()->clone();
const formula* u = // {r}[]->b
binop::instance(bindop, r, b);
result_ = // !s R ({r}[]->b)
binop::instance(op_r, ns, u);
mo->destroy();
result_ = recurse_destroy(result_);
return true;
}
r->destroy();
}
// {r₁;r₂;r₃}[]->b = {r₁}[]->X({r₂}[]->X({r₃}[]->b))
// if r₁, r₂, r₃ do not accept [*0].
if (!mo->accepts_eword())
{
unsigned count = 0;
for (unsigned n = 0; n <= s; ++n)
count += !mo->nth(n)->accepts_eword();
assert(count > 0);
if (count == 1)
return false;
// Let e denote a term that accepts [*0]
// and let f denote a term that do not.
// A formula such as {e₁;f₁;e₂;e₃;f₂;e₄}[]->b
// in which count==2 will be grouped
// as follows: r₁ = e₁;f₁;e₂;e₃
// r₂ = f₂;e₄
// this way we have
// {e₁;f₁;e₂;e₃;f₂;e₄}[]->b = {r₁;r₂;r₃}[]->b
// where r₁ and r₂ do not accept [*0].
unsigned pos = s + 1;
// We compute the r formulas from the right
// (i.e., r₂ before r₁.)
multop::vec* r = new multop::vec;
do
r->insert(r->begin(), mo->nth(--pos)->clone());
while (r->front()->accepts_eword());
const formula* tail = // {r₂}[]->b
binop::instance(bindop,
multop::instance(multop::Concat, r),
b);
while (--count)
{
multop::vec* r = new multop::vec;
do
r->insert(r->begin(), mo->nth(--pos)->clone());
while (r->front()->accepts_eword());
// If it's the last block, take all leading
// formulae as well.
if (count == 1)
while (pos > 0)
{
r->insert(r->begin(), mo->nth(--pos)->clone());
assert(r->front()->accepts_eword());
}
tail = // X({r₂}[]->b)
unop::instance(unop::X, tail);
tail = // {r₁}[]->X({r₂}[]->b)
binop::instance(bindop,
multop::instance(multop::Concat, r),
tail);
}
mo->destroy();
result_ = recurse_destroy(tail);
return true;
}
}
else if (opt_.reduce_size_strictly)
{
return false;
}
else if (const multop* mo = is_Fusion(a))
{
// {r₁:r₂:r₃}[]->b = {r₁}[]->({r₂}[]->({r₃}[]->b))
unsigned s = mo->size();
const formula* tail = b;
do
{
--s;
tail = binop::instance(bindop,
mo->nth(s)->clone(), tail);
}
while (s != 0);
mo->destroy();
result_ = recurse_destroy(tail);
return true;
}
else if (const multop* mo = is_OrRat(a))
{
// {r₁|r₂|r₃}[]->b = ({r₁}[]->b)&({r₂}[]->b)&({r₃}[]->b)
unsigned s = mo->size();
multop::vec* v = new multop::vec;
for (unsigned n = 0; n < s; ++n)
{
const formula* x = // {r₁}[]->b
binop::instance(bindop,
mo->nth(n)->clone(), b->clone());
v->push_back(x);
}
mo->destroy();
b->destroy();
result_ = recurse_destroy(multop::instance(op_and, v));
return true;
}
return false;
}
void
visit(const binop* bo)
{
binop::type op = bo->op();
const formula* b = recurse(bo->second());
if (opt_.event_univ)
{
trace << "bo: trying eventuniv rules" << std::endl;
/* If b is a pure eventuality formula then a U b = b.
If b is a pure universality formula a R b = b. */
if ((b->is_eventual() && (op == binop::U))
|| (b->is_universal() && (op == binop::R)))
{
result_ = b;
return;
}
}
const formula* a = recurse(bo->first());
if (opt_.event_univ)
{
/* If a is a pure eventuality formula then a M b = a & b.
If a is a pure universality formula a W b = a|b. */
if (a->is_eventual() && (op == binop::M))
{
result_ =
recurse_destroy(multop::instance(multop::And, a, b));
return;
}
if (a->is_universal() && (op == binop::W))
{
result_ =
recurse_destroy(multop::instance(multop::Or, a, b));
return;
}
// e₁ W e₂ = Ge₁ | e₂
// u₁ M u₂ = Fu₁ & u₂
if (!opt_.reduce_size_strictly)
{
if (op == binop::W && a->is_eventual() && b->is_eventual())
{
result_ =
recurse_destroy(multop::instance
(multop::Or,
unop::instance(unop::G, a), b));
return;
}
if (op == binop::M && a->is_universal() && b->is_universal())
{
result_ =
recurse_destroy(multop::instance
(multop::And,
unop::instance(unop::F, a), b));
return;
}
}
trace << "bo: no eventuniv rule matched" << std::endl;
}
// Inclusion-based rules
if (opt_.synt_impl | opt_.containment_checks)
{
trace << "bo: trying inclusion-based rules" << std::endl;
switch (op)
{
case binop::Xor:
case binop::Equiv:
case binop::Implies:
assert(!"operator not supported for implication rules");
return;
case binop::UConcat:
case binop::EConcat:
case binop::EConcatMarked:
break;
case binop::U:
// if a => b, then a U b = b
// if (a U b) => b, then a U b = b (for stronger containment)
if (c_->implication(a, b)
|| (opt_.containment_checks_stronger
&& c_->contained(bo, b)))
{
a->destroy();
result_ = b;
return;
}
// if !a => b, then a U b = Fb
if (c_->implication_neg(a, b, false))
{
a->destroy();
result_ =
recurse_destroy(unop::instance(unop::F, b));
return;
}
// if a => b, then a U (b U c) = (b U c)
// if a => b, then a U (b W c) = (b W c)
// if b => a, then a U (b U c) = (a U c)
// if a => c, then a U (b R (c U d)) = (b R (c U d))
// if a => c, then a U (b R (c W d)) = (b R (c W d))
// if a => c, then a U (b M (c U d)) = (b M (c U d))
// if a => c, then a U (b M (c W d)) = (b M (c W d))
if (const binop* bo = is_binop(b))
{
// if a => b, then a U (b U c) = (b U c)
// if a => b, then a U (b W c) = (b W c)
if ((bo->op() == binop::U || bo->op() == binop::W)
&& c_->implication(a, bo->first()))
{
a->destroy();
result_ = b;
return;
}
// if b => a, then a U (b U c) = (a U c)
if (bo->op() == binop::U
&& c_->implication(bo->first(), a))
{
result_ = recurse_destroy
(binop::instance(binop::U,
a, bo->second()->clone()));
b->destroy();
return;
}
// if a => c, then a U (b R (c U d)) = (b R (c U d))
// if a => c, then a U (b R (c W d)) = (b R (c W d))
// if a => c, then a U (b M (c U d)) = (b M (c U d))
// if a => c, then a U (b M (c W d)) = (b M (c W d))
if ((bo->op() == binop::R || bo->op() == binop::M)
&& bo->second()->kind() == formula::BinOp)
{
const binop* cd =
static_cast<const binop*>(bo->second());
if ((cd->op() == binop::U || cd->op() == binop::W)
&& c_->implication(a, cd->first()))
{
a->destroy();
result_ = b;
return;
}
}
}
// if a => b, then (a U c) U b = c U b
// if a => b, then (a W c) U b = c U b
// if c => b, then (a U c) U b = (a U c) | b
if (const binop* bo = is_binop(a))
{
if ((bo->op() == binop::U || bo->op() == binop::W)
&& c_->implication(bo->first(), b))
{
result_ = recurse_destroy
(binop::instance(binop::U,
bo->second()->clone(),
b));
a->destroy();
return;
}
else if ((bo->op() == binop::U)
&& c_->implication(bo->second(), b))
{
result_ = recurse_destroy
(multop::instance(multop::Or, a, b));
return;
}
}
break;
case binop::R:
// if b => a, then a R b = b
if (c_->implication(b, a))
{
a->destroy();
result_ = b;
return;
}
// if b => !a, then a R b = Gb
if (c_->implication_neg(b, a, true))
{
a->destroy();
result_ = recurse_destroy(unop::instance(unop::G, b));
return;
}
if (b->kind() == formula::BinOp)
{
// if b => a, then a R (b R c) = b R c
// if b => a, then a R (b M c) = b M c
const binop* bo = static_cast<const binop*>(b);
if ((bo->op() == binop::R || bo->op() == binop::M)
&& c_->implication(bo->first(), a))
{
a->destroy();
result_ = b;
return;
}
// if a => b, then a R (b R c) = a R c
if (bo->op() == binop::R
&& c_->implication(a, bo->first()))
{
result_ = recurse_destroy
(binop::instance(binop::R, a,
bo->second()->clone()));
b->destroy();
return;
}
}
// if b => a, then (a R c) R b = c R b
// if b => a, then (a M c) R b = c R b
// if c => b, then (a R c) R b = (a & c) R b
// if c => b, then (a M c) R b = (a & c) R b
if (const binop* bo = is_binop(a))
{
if (bo->op() == binop::M || bo->op() == binop::R)
{
if (c_->implication(b, bo->first()))
{
result_ = recurse_destroy
(binop::instance(binop::R,
bo->second()->clone(),
b));
a->destroy();
return;
}
else if (c_->implication(bo->second(), b))
{
const formula* ac =
multop::instance(multop::And,
bo->first()->clone(),
bo->second()->clone());
a->destroy();
result_ = recurse_destroy
(binop::instance(binop::R, ac, b));
return;
}
}
}
break;
case binop::W:
// if a => b, then a W b = b
// if a W b => b, then a W b = b (for stronger containment)
if (c_->implication(a, b)
|| (opt_.containment_checks_stronger
&& c_->contained(bo, b)))
{
a->destroy();
result_ = b;
return;
}
// if !a => b then a W b = 1
if (c_->implication_neg(a, b, false))
{
a->destroy();
b->destroy();
result_ = constant::true_instance();
return;
}
// if a => b, then a W (b W c) = (b W c)
// (Beware: even if a => b we do not have a W (b U c) = b U c)
// if b => a, then a W (b U c) = (a W c)
// if b => a, then a W (b W c) = (a W c)
if (b->kind() == formula::BinOp)
{
const binop* bo = static_cast<const binop*>(b);
// if a => b, then a W (b W c) = (b W c)
if (bo->op() == binop::W
&& c_->implication(a, bo->first()))
{
a->destroy();
result_ = b;
return;
}
// if b => a, then a W (b U c) = (a W c)
// if b => a, then a W (b W c) = (a W c)
if ((bo->op() == binop::U || bo->op() == binop::W)
&& c_->implication(bo->first(), a))
{
result_ = recurse_destroy
(binop::instance(binop::W,
a, bo->second()->clone()));
b->destroy();
return;
}
}
// if a => b, then (a U c) W b = c W b
// if a => b, then (a W c) W b = c W b
// if c => b, then (a W c) W b = (a W c) | b
// if c => b, then (a U c) W b = (a U c) | b
if (const binop* bo = is_binop(a))
{
if ((bo->op() == binop::U || bo->op() == binop::W))
{
if (c_->implication(bo->first(), b))
{
result_ = recurse_destroy
(binop::instance(binop::W,
bo->second()->clone(),
b));
a->destroy();
return;
}
else if (c_->implication(bo->second(), b))
{
result_ = recurse_destroy
(multop::instance(multop::Or, a, b));
return;
}
}
}
break;
case binop::M:
// if b => a, then a M b = b
if (c_->implication(b, a))
{
a->destroy();
result_ = b;
return;
}
// if b => !a, then a M b = 0
if (c_->implication_neg(b, a, true))
{
a->destroy();
b->destroy();
result_ = constant::false_instance();
return;
}
if (b->kind() == formula::BinOp)
{
// if b => a, then a M (b M c) = b M c
const binop* bo = static_cast<const binop*>(b);
if (bo->op() == binop::M
&& c_->implication(bo->first(), a))
{
result_ = b;
a->destroy();
return;
}
// if a => b, then a M (b M c) = a M c
// if a => b, then a M (b R c) = a M c
if ((bo->op() == binop::M || bo->op() == binop::R)
&& c_->implication(a, bo->first()))
{
b->destroy();
result_ = recurse_destroy
(binop::instance(binop::M, a,
bo->second()->clone()));
return;
}
}
// if b => a, then (a R c) M b = c M b
// if b => a, then (a M c) M b = c M b
// if c => b, then (a M c) M b = (a & c) M b
if (const binop* bo = is_binop(a))
{
if ((bo->op() == binop::M || bo->op() == binop::R)
&& c_->implication(b, bo->first()))
{
result_ = recurse_destroy
(binop::instance(binop::M,
bo->second()->clone(),
b));
a->destroy();
return;
}
else if ((bo->op() == binop::M)
&& c_->implication(bo->second(), b))
{
const formula* ac =
multop::instance(multop::And,
bo->first()->clone(),
bo->second()->clone());
a->destroy();
result_ = recurse_destroy
(binop::instance(binop::M, ac, b));
return;
}
}
break;
}
trace << "bo: no inclusion-based rules matched" << std::endl;
}
if (!opt_.reduce_basics)
{
trace << "bo: basic reductions disabled" << std::endl;
result_ = binop::instance(op, a, b);
return;
}
trace << "bo: trying basic reductions" << std::endl;
// Rewrite U,R,W,M as F or G when possible.
switch (op)
{
case binop::U:
// true U b == F(b)
if (a == constant::true_instance())
{
result_ = recurse_destroy(unop::instance(unop::F, b));
return;
}
break;
case binop::R:
// false R b == G(b)
if (a == constant::false_instance())
{
result_ = recurse_destroy(unop::instance(unop::G, b));
return;
}
break;
case binop::W:
// a W false == G(a)
if (b == constant::false_instance())
{
result_ = recurse_destroy(unop::instance(unop::G, a));
return;
}
break;
case binop::M:
// a M true == F(a)
if (b == constant::true_instance())
{
result_ = recurse_destroy(unop::instance(unop::F, a));
return;
}
break;
default:
break;
}
switch (op)
{
case binop::W:
case binop::M:
case binop::U:
case binop::R:
{
// These are trivial identities:
// a U false = false
// a U true = true
// a R false = false
// a R true = true
// a W true = true
// a M false = false
if (is_constant(b))
{
result_ = b;
a->destroy();
return;
}
const unop* fu1 = is_unop(a);
const unop* fu2 = is_unop(b);
// X(a) U X(b) = X(a U b)
// X(a) R X(b) = X(a R b)
// X(a) W X(b) = X(a W b)
// X(a) M X(b) = X(a M b)
if (fu1 && fu2
&& fu1->op() == unop::X
&& fu2->op() == unop::X)
{
const formula* bin =
binop::instance(op,
fu1->child()->clone(),
fu2->child()->clone());
a->destroy();
b->destroy();
result_ = recurse_destroy(unop::instance(unop::X, bin));
return;
}
if (op == binop::U || op == binop::W)
{
// a U Ga = Ga
// a W Ga = Ga
if (fu2 && fu2->op() == unop::G && fu2->child() == a)
{
a->destroy();
result_ = b;
return;
}
// a U (b | c | G(a)) = a W (b | c)
// a W (b | c | G(a)) = a W (b | c)
// a U (a & b & c) = (b & c) M a
// a W (a & b & c) = (b & c) R a
if (const multop* fm2 = is_multop(b))
{
multop::type bt = fm2->op();
// a U (b | c | G(a)) = a W (b | c)
// a W (b | c | G(a)) = a W (b | c)
if (bt == multop::Or)
{
int s = fm2->size();
for (int i = 0; i < s; ++i)
{
const unop* c = is_G(fm2->nth(i));
if (!c || c->child() != a)
continue;
result_ =
recurse_destroy(binop::instance
(binop::W, a,
fm2->all_but(i)));
b->destroy();
return;
}
}
// a U (b & a & c) == (b & c) M a
// a W (b & a & c) == (b & c) R a
if (bt == multop::And)
{
int s = fm2->size();
for (int i = 0; i < s; ++i)
{
if (fm2->nth(i) != a)
continue;
result_ = recurse_destroy(binop::instance
(op == binop::U ? binop::M : binop::R,
fm2->all_but(i), a));
b->destroy();
return;
}
}
}
// If b is Boolean:
// (Xc) U b = b | X(b M c)
// (Xc) W b = b | X(b R c)
if (!opt_.reduce_size_strictly
&& fu1 && fu1->op() == unop::X && b->is_boolean())
{
const formula* c = fu1->child()->clone();
fu1->destroy();
const formula* x =
unop::instance(unop::X,
binop::instance(op == binop::U
? binop::M
: binop::R,
b->clone(), c));
result_ =
recurse_destroy(multop::instance(multop::Or, b, x));
return;
}
}
else if (op == binop::M || op == binop::R)
{
// a R Fa = Fa
// a M Fa = Fa
if (fu2 && fu2->op() == unop::F && fu2->child() == a)
{
a->destroy();
result_ = b;
return;
}
// a R (b & c & F(a)) = a M (b & c)
// a M (b & c & F(a)) = a M (b & c)
// a M (b | a | c) == (b | c) U a
// a R (b | a | c) == (b | c) W a
if (const multop* fm2 = is_multop(b))
{
multop::type bt = fm2->op();
// a R (b & c & F(a)) = a M (b & c)
// a M (b & c & F(a)) = a M (b & c)
if (bt == multop::And)
{
int s = fm2->size();
for (int i = 0; i < s; ++i)
{
const unop* c = is_F(fm2->nth(i));
if (!c || c->child() != a)
continue;
result_ =
recurse_destroy(binop::instance
(binop::M, a,
fm2->all_but(i)));
b->destroy();
return;
}
}
// a M (b | a | c) == (b | c) U a
// a R (b | a | c) == (b | c) W a
if (bt == multop::Or)
{
int s = fm2->size();
for (int i = 0; i < s; ++i)
{
if (fm2->nth(i) != a)
continue;
result_ = recurse_destroy(binop::instance
(op == binop::M ? binop::U : binop::W,
fm2->all_but(i), a));
b->destroy();
return;
}
}
}
// If b is Boolean:
// (Xc) R b = b & X(b W c)
// (Xc) M b = b & X(b U c)
if (!opt_.reduce_size_strictly
&& fu1 && fu1->op() == unop::X && b->is_boolean())
{
const formula* c = fu1->child()->clone();
fu1->destroy();
const formula* x =
unop::instance(unop::X,
binop::instance(op == binop::M
? binop::U
: binop::W,
b->clone(), c));
result_ =
recurse_destroy(multop::instance(multop::And, b, x));
return;
}
}
}
case binop::UConcat:
case binop::EConcat:
case binop::EConcatMarked:
if (reduce_sere_ltl(op, a, b))
return;
else
break;
case binop::Xor:
case binop::Equiv:
case binop::Implies:
// No simplification... Yet?
break;
}
result_ = binop::instance(op, a, b);
}
void
visit(const automatop* aut)
{
result_ = aut->clone();
}
void
visit(const multop* mo)
{
unsigned mos = mo->size();
multop::vec* res = new multop::vec;
for (unsigned i = 0; i < mos; ++i)
res->push_back(recurse(mo->nth(i)));
multop::type op = mo->op();
if ((opt_.synt_impl | opt_.containment_checks)
&& (op != multop::AndRat)
&& (op != multop::AndNLM)
&& (op != multop::OrRat)
&& (op != multop::Concat)
&& (op != multop::Fusion))
{
bool is_and = op == multop::And;
constant* neutral = is_and
? constant::false_instance() : constant::true_instance();
multop::vec::iterator f1 = res->begin();
while (f1 != res->end())
{
multop::vec::iterator f2 = f1;
++f2;
while (f2 != res->end())
{
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)))
{
// 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();
delete res;
result_ = neutral;
return;
}
else
++f2;
}
++f1;
}
}
assert(!res->empty());
// basics reduction do not concern Boolean formulas,
// so don't waste time trying to apply them.
if (opt_.reduce_basics && !mo->is_boolean())
{
switch (op)
{
case multop::And:
assert(!mo->is_sere_formula());
{
// a & X(G(a&b...) & c...) = Ga & X(G(b...) & c...)
// a & (Xa W b) = b R a
// a & (Xa U b) = b M a
// a & (b | X(b R a)) = b R a
// a & (b | X(b M a)) = b M a
if (!mo->is_X_free())
{
typedef Sgi::hash_set<const formula*,
ptr_hash<formula> > fset_t;
typedef Sgi::hash_map<const formula*,
std::set<unsigned>,
ptr_hash<formula> > fmap_t;
fset_t xgset; // XG(...)
fset_t xset; // X(...)
fmap_t wuset; // (X...)W(...) or (X...)U(...)
unsigned s = res->size();
std::vector<bool> tokill(s);
// Make a pass to search for subterms
// of the form XGa or X(... & G(...&a&...) & ...)
for (unsigned n = 0; n < s; ++n)
{
if (!(*res)[n])
continue;
const formula* xarg = is_XWU((*res)[n]);
if (xarg)
{
wuset[xarg].insert(n);
continue;
}
// Now we are looking for
// - X(...)
// - b | X(b R ...)
// - b | X(b M ...)
if ((*res)[n]->is_X_free())
continue;
const binop* barg = is_bXbRM((*res)[n]);
if (barg)
{
wuset[barg->second()].insert(n);
continue;
}
const unop* uo = is_X((*res)[n]);
if (!uo)
continue;
const formula* c = uo->child();
const multop* a;
const unop* g;
if ((g = is_G(c)))
{
#define HANDLE_G const multop* a2; \
if ((a2 = is_And(g->child()))) \
{ \
unsigned y = a2->size(); \
for (unsigned n = 0; n < y; ++n) \
xgset. \
insert(a2->nth(n)->clone()); \
} \
else \
{ \
xgset.insert(g->child()->clone()); \
}
HANDLE_G;
}
else if ((a = is_And(c)))
{
unsigned z = a->size();
for (unsigned m = 0; m < z; ++m)
{
const formula* x = a->nth(m);
if ((g = is_G(x)))
{
HANDLE_G;
}
else
{
xset.insert(x->clone());
}
}
}
else
{
xset.insert(c->clone());
}
(*res)[n]->destroy();
(*res)[n] = 0;
}
// Make a second pass to check if the "a"
// terms can be used to simplify "Xa W b",
// "Xa U b", "b | X(b R a)", or "b | X(b M a)".
for (unsigned n = 0; n < s; ++n)
{
if (!(*res)[n])
continue;
fmap_t::const_iterator gs =
wuset.find((*res)[n]);
if (gs == wuset.end())
continue;
const std::set<unsigned>& s = gs->second;
std::set<unsigned>::const_iterator g;
for (g = s.begin(); g != s.end(); ++g)
{
unsigned pos = *g;
const binop* wu = is_binop((*res)[pos]);
if (wu)
{
// a & (Xa W b) = b R a
// a & (Xa U b) = b M a
binop::type t = (wu->op() == binop::U)
? binop::M : binop::R;
const unop* xa =
down_cast<const unop*>(wu->first());
const formula* a = xa->child()->clone();
const formula* b = wu->second()->clone();
wu->destroy();
(*res)[pos] = binop::instance(t, b, a);
}
else
{
// a & (b | X(b R a)) = b R a
// a & (b | X(b M a)) = b M a
wu = is_bXbRM((*res)[pos]);
assert(wu);
wu->clone();
(*res)[pos]->destroy();
(*res)[pos] = wu;
}
// Remember to kill "a".
tokill[n] = true;
}
}
// Make third pass to search for terms 'a'
// that also appears as 'XGa'. Replace them
// by 'Ga' and delete XGa.
for (unsigned n = 0; n < s; ++n)
{
const formula* x = (*res)[n];
if (!x)
continue;
fset_t::const_iterator g = xgset.find(x);
if (g != xgset.end())
{
// x can appear only once.
const formula* gf = *g;
xgset.erase(g);
gf->destroy();
(*res)[n] = unop::instance(unop::G, x);
}
else if (tokill[n])
{
(*res)[n]->destroy();
(*res)[n] = 0;
}
}
multop::vec* xv = new multop::vec;
size_t xgs = xgset.size();
xv->reserve(xset.size() + 1);
if (xgs > 0)
{
multop::vec* xgv = new multop::vec;
xgv->reserve(xgs);
fset_t::iterator i;
for (i = xgset.begin(); i != xgset.end(); ++i)
xgv->push_back(*i);
const formula* gv =
multop::instance(multop::And, xgv);
xv->push_back(unop::instance(unop::G, gv));
}
fset_t::iterator j;
for (j = xset.begin(); j != xset.end(); ++j)
xv->push_back(*j);
const formula* av =
multop::instance(multop::And, xv);
res->push_back(unop::instance(unop::X, av));
}
// Gather all operands by type.
mospliter s(mospliter::Strip_X |
mospliter::Strip_FG |
mospliter::Strip_G |
mospliter::Split_F |
mospliter::Split_U_or_W |
mospliter::Split_R_or_M |
mospliter::Split_EventUniv,
res, c_);
// FG(a) & FG(b) = FG(a & b)
const formula* allFG =
unop_unop_multop(unop::F, unop::G, multop::And,
s.res_FG);
// Xa & Xb = X(a & b)
// Xa & Xb & FG(c) = X(a & b & FG(c))
// For Universal&Eventual formulae f1...fn we also have:
// Xa & Xb & f1...fn = X(a & b & f1...fn)
if (!s.res_X->empty())
{
s.res_X->push_back(allFG);
allFG = 0;
s.res_X->insert(s.res_X->begin(),
s.res_EventUniv->begin(),
s.res_EventUniv->end());
}
else
// We don't rewrite Ga & f1...fn = G(a & f1..fn)
// similarly to what we do in the unop::Or case
// as it is not clear what we'd gain by doing so.
{
s.res_other->insert(s.res_other->begin(),
s.res_EventUniv->begin(),
s.res_EventUniv->end());
}
delete s.res_EventUniv;
// Xa & Xb & f1...fn = X(a & b & f1...fn)
// is built at the end of this multop::And case.
// G(a) & G(b) = G(a & b)
// is built at the end of this multop::And case.
// The following three loops perform these rewritings:
// (a U b) & (c U b) = (a & c) U b
// (a U b) & (c W b) = (a & c) U b
// (a W b) & (c W b) = (a & c) W b
// (a R b) & (a R c) = a R (b & c)
// (a R b) & (a M c) = a M (b & c)
// (a M b) & (a M c) = a M (b & c)
// F(a) & (a R b) = a M b
// F(a) & (a M b) = a M b
// F(b) & (a W b) = a U b
// F(b) & (a U b) = a U b
typedef Sgi::hash_map<const formula*,
multop::vec::iterator,
ptr_hash<formula> > fmap_t;
fmap_t uwmap; // associates "b" to "a U b" or "a W b"
fmap_t rmmap; // associates "a" to "a R b" or "a M b"
// (a U b) & (c U b) = (a & c) U b
// (a U b) & (c W b) = (a & c) U b
// (a W b) & (c W b) = (a & c) W b
for (multop::vec::iterator i = s.res_U_or_W->begin();
i != s.res_U_or_W->end(); ++i)
{
const binop* bo = static_cast<const binop*>(*i);
const formula* b = bo->second();
fmap_t::iterator j = uwmap.find(b);
if (j == uwmap.end())
{
// First occurrence.
uwmap[b] = i;
continue;
}
// We already have one occurrence. Merge them.
const binop* old =
static_cast<const binop*>(*j->second);
binop::type op = binop::W;
if (bo->op() == binop::U
|| old->op() == binop::U)
op = binop::U;
const formula* fst_arg =
multop::instance(multop::And,
old->first()->clone(),
bo->first()->clone());
*j->second = binop::instance(op, fst_arg, b->clone());
assert((*j->second)->kind() == formula::BinOp);
*i = 0;
old->destroy();
bo->destroy();
}
// (a R b) & (a R c) = a R (b & c)
// (a R b) & (a M c) = a M (b & c)
// (a M b) & (a M c) = a M (b & c)
for (multop::vec::iterator i = s.res_R_or_M->begin();
i != s.res_R_or_M->end(); ++i)
{
const binop* bo = static_cast<const binop*>(*i);
const formula* a = bo->first();
fmap_t::iterator j = rmmap.find(a);
if (j == rmmap.end())
{
// First occurrence.
rmmap[a] = i;
continue;
}
// We already have one occurrence. Merge them.
const binop* old =
static_cast<const binop*>(*j->second);
binop::type op = binop::R;
if (bo->op() == binop::M
|| old->op() == binop::M)
op = binop::M;
const formula* snd_arg =
multop::instance(multop::And,
old->second()->clone(),
bo->second()->clone());
*j->second = binop::instance(op, a->clone(), snd_arg);
assert((*j->second)->kind() == formula::BinOp);
*i = 0;
old->destroy();
bo->destroy();
}
// F(a) & (a R b) = a M b
// F(a) & (a M b) = a M b
// F(b) & (a W b) = a U b
// F(b) & (a U b) = a U b
for (multop::vec::iterator i = s.res_F->begin();
i != s.res_F->end(); ++i)
{
bool superfluous = false;
const unop* uo = static_cast<const unop*>(*i);
const formula* c = uo->child();
fmap_t::iterator j = uwmap.find(c);
if (j != uwmap.end())
{
superfluous = true;
const binop* bo =
static_cast<const binop*>(*j->second);
if (bo->op() == binop::W)
{
*j->second =
binop::instance(binop::U,
bo->first()->clone(),
bo->second()->clone());
assert((*j->second)->kind()
== formula::BinOp);
bo->destroy();
}
}
j = rmmap.find(c);
if (j != rmmap.end())
{
superfluous = true;
const binop* bo =
static_cast<const binop*>(*j->second);
if (bo->op() == binop::R)
{
*j->second =
binop::instance(binop::M,
bo->first()->clone(),
bo->second()->clone());
assert((*j->second)->kind()
== formula::BinOp);
bo->destroy();
}
}
if (superfluous)
{
(*i)->destroy();
*i = 0;
}
}
s.res_other->reserve(s.res_other->size()
+ s.res_F->size()
+ s.res_U_or_W->size()
+ s.res_R_or_M->size()
+ 3);
s.res_other->insert(s.res_other->end(),
s.res_F->begin(),
s.res_F->end());
delete s.res_F;
s.res_other->insert(s.res_other->end(),
s.res_U_or_W->begin(),
s.res_U_or_W->end());
delete s.res_U_or_W;
s.res_other->insert(s.res_other->end(),
s.res_R_or_M->begin(),
s.res_R_or_M->end());
delete s.res_R_or_M;
// Those "G" formulae that are eventual can be
// postponed inside the X term if there is one.
//
// In effect we rewrite
// Xa&Xb&GFc&GFd&Ge as X(a&b&G(Fc&Fd))&Ge
if (!s.res_X->empty())
{
multop::vec* event = new multop::vec;
for (multop::vec::iterator i = s.res_G->begin();
i != s.res_G->end(); ++i)
if ((*i)->is_eventual())
{
event->push_back(*i);
*i = 0; // Remove it from res_G.
}
s.res_X->push_back(unop_multop(unop::G,
multop::And, event));
}
// G(a) & G(b) & ... = G(a & b & ...)
const formula* allG =
unop_multop(unop::G, multop::And, s.res_G);
// Xa & Xb & ... = X(a & b & ...)
const formula* allX =
unop_multop(unop::X, multop::And, s.res_X);
s.res_other->push_back(allX);
s.res_other->push_back(allG);
s.res_other->push_back(allFG);
result_ = multop::instance(multop::And, s.res_other);
// If we altered the formula in some way, process
// it another time.
if (result_ != mo)
result_ = recurse_destroy(result_);
return;
}
break;
case multop::AndRat:
{
mospliter s(mospliter::Split_Bool, res, c_);
if (!s.res_Bool->empty())
{
// b1 & b2 & b3 = b1 ∧ b2 ∧ b3
const formula* b =
multop::instance(multop::And, s.res_Bool);
multop::vec* ares = new multop::vec;
for (multop::vec::iterator i = s.res_other->begin();
i != s.res_other->end(); ++i)
switch ((*i)->kind())
{
case formula::BUnOp:
{
const bunop* r = down_cast<const bunop*>(*i);
// b && r[*i..j] = b & r if i<=1<=j
// = 0 otherwise
switch (r->op())
{
case bunop::Star:
if (r->min() > 1 || r->max() < 1)
goto returnfalse;
ares->push_back(r->child()->clone());
r->destroy();
*i = 0;
break;
}
break;
}
case formula::MultOp:
{
const multop* r = down_cast<const multop*>(*i);
unsigned rs = r->size();
switch (r->op())
{
case multop::Fusion:
//b && {r1:..:rn} = b && r1 && .. && rn
for (unsigned j = 0; j < rs; ++j)
ares->push_back(r->nth(j)->clone());
r->destroy();
*i = 0;
break;
case multop::Concat:
// b && {r1;...;rn} =
// - b && ri if there is only one ri
// that does not accept [*0]
// - b && (r1|...|rn) if all ri
// do not accept [*0]
// - 0 if more than one ri accept [*0]
{
const formula* ri = 0;
unsigned nonempty = 0;
for (unsigned j = 0; j < rs; ++j)
{
const formula* jf = r->nth(j);
if (!jf->accepts_eword())
{
ri = jf;
++nonempty;
}
}
if (nonempty == 1)
{
ares->push_back(ri->clone());
}
else if (nonempty == 0)
{
multop::vec* sum = new multop::vec;
for (unsigned j = 0; j < rs; ++j)
sum->push_back(r->nth(j)
->clone());
const formula* sumf =
multop::instance(multop::OrRat,
sum);
ares->push_back(sumf);
}
else
{
goto returnfalse;
}
r->destroy();
*i = 0;
break;
}
default:
goto common;
}
break;
}
default:
common:
ares->push_back(*i);
*i = 0;
break;
}
delete s.res_other;
ares->push_back(b);
result_ = multop::instance(multop::AndRat, ares);
// If we altered the formula in some way, process
// it another time.
if (result_ != mo)
result_ = recurse_destroy(result_);
return;
returnfalse:
b->destroy();
for (multop::vec::iterator i = s.res_other->begin();
i != s.res_other->end(); ++i)
if (*i)
(*i)->destroy();
delete s.res_other;
for (multop::vec::iterator i = ares->begin();
i != ares->end(); ++i)
(*i)->destroy();
delete ares;
result_ = constant::false_instance();
return;
}
else
{
// No Boolean as argument of &&.
delete s.res_Bool;
// Look for occurrences of {b;r} or {b:r}. We have
// {b1;r1}&&{b2;r2} = {b1&&b2};{r1&&r2}
// head1 tail1
// {b1:r1}&&{b2:r2} = {b1&&b2}:{r1&&r2}
// head2 tail2
multop::vec* head1 = new multop::vec;
multop::vec* tail1 = new multop::vec;
multop::vec* head2 = new multop::vec;
multop::vec* tail2 = new multop::vec;
for (multop::vec::iterator i = s.res_other->begin();
i != s.res_other->end(); ++i)
{
if (!*i)
continue;
if ((*i)->kind() != formula::MultOp)
continue;
const multop* f = down_cast<const multop*>(*i);
const formula* h = f->nth(0);
if (!h->is_boolean())
continue;
multop::type op = f->op();
if (op == multop::Concat)
{
head1->push_back(h->clone());
multop::vec* tail = new multop::vec;
unsigned s = f->size();
for (unsigned j = 1; j < s; ++j)
tail->push_back(f->nth(j)->clone());
tail1->push_back(multop::instance(op, tail));
(*i)->destroy();
*i = 0;
}
else if (op == multop::Fusion)
{
head2->push_back(h->clone());
multop::vec* tail = new multop::vec;
unsigned s = f->size();
for (unsigned j = 1; j < s; ++j)
tail->push_back(f->nth(j)->clone());
tail2->push_back(multop::instance(op, tail));
(*i)->destroy();
*i = 0;
}
else
{
continue;
}
}
if (!head1->empty())
{
const formula* h =
multop::instance(multop::And, head1);
const formula* t =
multop::instance(multop::AndRat, tail1);
const formula* f =
multop::instance(multop::Concat, h, t);
s.res_other->push_back(f);
}
else
{
delete head1;
delete tail1;
}
if (!head2->empty())
{
const formula* h =
multop::instance(multop::And, head2);
const formula* t =
multop::instance(multop::AndRat, tail2);
const formula* f =
multop::instance(multop::Fusion, h, t);
s.res_other->push_back(f);
}
else
{
delete head2;
delete tail2;
}
// {r1;b1}&&{r2;b2} = {r1&&r2};{b1∧b2}
// head3 tail3
// {r1:b1}&&{r2:b2} = {r1&&r2}:{b1∧b2}
// head4 tail4
multop::vec* head3 = new multop::vec;
multop::vec* tail3 = new multop::vec;
multop::vec* head4 = new multop::vec;
multop::vec* tail4 = new multop::vec;
for (multop::vec::iterator i = s.res_other->begin();
i != s.res_other->end(); ++i)
{
if (!*i)
continue;
if ((*i)->kind() != formula::MultOp)
continue;
const multop* f = down_cast<const multop*>(*i);
unsigned s = f->size() - 1;
const formula* t = f->nth(s);
if (!t->is_boolean())
continue;
multop::type op = f->op();
if (op == multop::Concat)
{
tail3->push_back(t->clone());
multop::vec* head = new multop::vec;
for (unsigned j = 0; j < s; ++j)
head->push_back(f->nth(j)->clone());
head3->push_back(multop::instance(op, head));
(*i)->destroy();
*i = 0;
}
else if (op == multop::Fusion)
{
tail4->push_back(t->clone());
multop::vec* head = new multop::vec;
for (unsigned j = 0; j < s; ++j)
head->push_back(f->nth(j)->clone());
head4->push_back(multop::instance(op, head));
(*i)->destroy();
*i = 0;
}
else
{
continue;
}
}
if (!head3->empty())
{
const formula* h =
multop::instance(multop::AndRat, head3);
const formula* t =
multop::instance(multop::And, tail3);
const formula* f =
multop::instance(multop::Concat, h, t);
s.res_other->push_back(f);
}
else
{
delete head3;
delete tail3;
}
if (!head4->empty())
{
const formula* h =
multop::instance(multop::AndRat, head4);
const formula* t =
multop::instance(multop::And, tail4);
const formula* f =
multop::instance(multop::Fusion, h, t);
s.res_other->push_back(f);
}
else
{
delete head4;
delete tail4;
}
result_ =
multop::instance(multop::AndRat, s.res_other);
// If we altered the formula in some way, process
// it another time.
if (result_ != mo)
result_ = recurse_destroy(result_);
return;
}
}
case multop::Or:
{
// a | X(F(a) | c...) = Fa | X(c...)
// a | (Xa R b) = b W a
// a | (Xa M b) = b U a
// a | (b & X(b W a)) = b W a
// a | (b & X(b U a)) = b U a
if (!mo->is_X_free())
{
typedef Sgi::hash_set<const formula*,
ptr_hash<formula> > fset_t;
typedef Sgi::hash_map<const formula*,
std::set<unsigned>,
ptr_hash<formula> > fmap_t;
fset_t xfset; // XF(...)
fset_t xset; // X(...)
fmap_t rmset; // (X...)R(...) or (X...)M(...) or
// b & X(b W ...) or b & X(b U ...)
unsigned s = res->size();
std::vector<bool> tokill(s);
// Make a pass to search for subterms
// of the form XFa or X(... | F(...|a|...) | ...)
for (unsigned n = 0; n < s; ++n)
{
if (!(*res)[n])
continue;
const formula* xarg = is_XRM((*res)[n]);
if (xarg)
{
rmset[xarg].insert(n);
continue;
}
// Now we are looking for
// - X(...)
// - b & X(b W ...)
// - b & X(b U ...)
if ((*res)[n]->is_X_free())
continue;
const binop* barg = is_bXbWU((*res)[n]);
if (barg)
{
rmset[barg->second()].insert(n);
continue;
}
const unop* uo = is_X((*res)[n]);
if (!uo)
continue;
const formula* c = uo->child();
const multop* o;
const unop* f;
if ((f = is_F(c)))
{
#define HANDLE_F const multop* o2; \
if ((o2 = is_Or(f->child()))) \
{ \
unsigned y = o2->size(); \
for (unsigned n = 0; n < y; ++n) \
xfset. \
insert(o2->nth(n)->clone()); \
} \
else \
{ \
xfset.insert(f->child()->clone()); \
}
HANDLE_F;
}
else if ((o = is_Or(c)))
{
unsigned z = o->size();
for (unsigned m = 0; m < z; ++m)
{
const formula* x = o->nth(m);
if ((f = is_F(x)))
{
HANDLE_F;
}
else
{
xset.insert(x->clone());
}
}
}
else
{
xset.insert(c->clone());
}
(*res)[n]->destroy();
(*res)[n] = 0;
}
// Make a second pass to check if we can
// remove all instance of XF(a).
unsigned allofthem = xfset.size();
for (unsigned n = 0; n < s; ++n)
{
const formula* x = (*res)[n];
if (!x)
continue;
fset_t::const_iterator f = xfset.find(x);
if (f != xfset.end())
--allofthem;
assert(allofthem != -1U);
// At the same time, check if "a" can also
// be used to simplify "Xa R b", "Xa M b".
// "b & X(b W a)", or "b & X(b U a)".
fmap_t::const_iterator gs = rmset.find(x);
if (gs == rmset.end())
continue;
const std::set<unsigned>& s = gs->second;
std::set<unsigned>::const_iterator g;
for (g = s.begin(); g != s.end(); ++g)
{
unsigned pos = *g;
const binop* rm = is_binop((*res)[pos]);
if (rm)
{
// a | (Xa R b) = b W a
// a | (Xa M b) = b U a
binop::type t = (rm->op() == binop::M)
? binop::U : binop::W;
const unop* xa =
down_cast<const unop*>(rm->first());
const formula* a = xa->child()->clone();
const formula* b = rm->second()->clone();
rm->destroy();
(*res)[pos] = binop::instance(t, b, a);
}
else
{
// a | (b & X(b W a)) = b W a
// a | (b & X(b U a)) = b U a
rm = is_bXbWU((*res)[pos]);
assert(rm);
rm->clone();
(*res)[pos]->destroy();
(*res)[pos] = rm;
}
// Remember to kill "a".
tokill[n] = true;
}
}
// If we can remove all of them...
if (allofthem == 0)
// Make third pass to search for terms 'a'
// that also appears as 'XFa'. Replace them
// by 'Fa' and delete XFa.
for (unsigned n = 0; n < s; ++n)
{
const formula* x = (*res)[n];
if (!x)
continue;
fset_t::const_iterator f = xfset.find(x);
if (f != xfset.end())
{
// x can appear only once.
const formula* ff = *f;
xfset.erase(f);
ff->destroy();
(*res)[n] = unop::instance(unop::F, x);
// We don't need to kill "a" anymore.
tokill[n] = false;
}
}
// Kill any remaining "a", used to simplify Xa R b
// or Xa M b.
for (unsigned n = 0; n < s; ++n)
if (tokill[n] && (*res)[n])
{
(*res)[n]->destroy();
(*res)[n] = 0;
}
// Now rebuild the formula that remains.
multop::vec* xv = new multop::vec;
size_t xfs = xfset.size();
xv->reserve(xset.size() + 1);
if (xfs > 0)
{
// Group all XF(a)|XF(b|c|...)|... as XF(a|b|c|...)
multop::vec* xfv = new multop::vec;
xfv->reserve(xfs);
fset_t::iterator i;
for (i = xfset.begin(); i != xfset.end(); ++i)
xfv->push_back(*i);
const formula* fv =
multop::instance(multop::Or, xfv);
xv->push_back(unop::instance(unop::F, fv));
}
// Also gather the remaining Xa | X(b|c) as X(b|c).
fset_t::iterator j;
for (j = xset.begin(); j != xset.end(); ++j)
xv->push_back(*j);
const formula* ov = multop::instance(multop::Or, xv);
res->push_back(unop::instance(unop::X, ov));
}
// Gather all operand by type.
mospliter s(mospliter::Strip_X |
mospliter::Strip_GF |
mospliter::Strip_F |
mospliter::Split_G |
mospliter::Split_U_or_W |
mospliter::Split_R_or_M |
mospliter::Split_EventUniv,
res, c_);
// GF(a) | GF(b) = GF(a | b)
const formula* allGF =
unop_unop_multop(unop::G, unop::F, multop::Or, s.res_GF);
// Xa | Xb = X(a | b)
// Xa | Xb | GF(c) = X(a | b | GF(c))
// For Universal&Eventual formula f1...fn we also have:
// Xa | Xb | f1...fn = X(a | b | f1...fn)
if (!s.res_X->empty())
{
s.res_X->push_back(allGF);
allGF = 0;
s.res_X->insert(s.res_X->end(),
s.res_EventUniv->begin(),
s.res_EventUniv->end());
}
else if (!s.res_F->empty()
&& s.res_G->empty()
&& s.res_U_or_W->empty()
&& s.res_R_or_M->empty()
&& s.res_other->empty())
{
// If there is no X but some F and only
// eventual&universal formulae f1...fn|GF(c), do:
// Fa|Fb|f1...fn|GF(c) = F(a|b|f1...fn|GF(c))
//
// The reasoning here is that if we should
// move f1...fn|GF(c) inside the "F" only
// if it allows us to move all terms under F,
// allowing a nice initial self-loop.
//
// For instance:
// F(a|GFb) 3st.6tr. with initial self-loop
// Fa|GFb 4st.8tr. without initial self-loop
//
// However, if other term are presents they will
// prevent the formation of a self-loop, and the
// rewriting is unwelcome:
// F(a|GFb)|Gc 5st.11tr. without initial self-loop
// Fa|GFb|Gc 5st.10tr. without initial self-loop
// (counting the number of "subtransitions"
// or, degeneralizing the automaton amplifies
// these differences)
s.res_F->push_back(allGF);
allGF = 0;
s.res_F->insert(s.res_F->end(),
s.res_EventUniv->begin(),
s.res_EventUniv->end());
}
else
{
s.res_other->insert(s.res_other->end(),
s.res_EventUniv->begin(),
s.res_EventUniv->end());
}
delete s.res_EventUniv;
// Xa | Xb | f1...fn = X(a | b | f1...fn)
// is built at the end of this multop::Or case.
// F(a) | F(b) = F(a | b)
// is built at the end of this multop::Or case.
// The following three loops perform these rewritings:
// (a U b) | (a U c) = a U (b | c)
// (a W b) | (a U c) = a W (b | c)
// (a W b) | (a W c) = a W (b | c)
// (a R b) | (c R b) = (a | c) R b
// (a R b) | (c M b) = (a | c) R b
// (a M b) | (c M b) = (a | c) M b
// G(a) | (a U b) = a W b
// G(a) | (a W b) = a W b
// G(b) | (a R b) = a R b.
// G(b) | (a M b) = a R b.
typedef Sgi::hash_map<const formula*,
multop::vec::iterator,
ptr_hash<formula> > fmap_t;
fmap_t uwmap; // associates "a" to "a U b" or "a W b"
fmap_t rmmap; // associates "b" to "a R b" or "a M b"
// (a U b) | (a U c) = a U (b | c)
// (a W b) | (a U c) = a W (b | c)
// (a W b) | (a W c) = a W (b | c)
for (multop::vec::iterator i = s.res_U_or_W->begin();
i != s.res_U_or_W->end(); ++i)
{
const binop* bo = static_cast<const binop*>(*i);
const formula* a = bo->first();
fmap_t::iterator j = uwmap.find(a);
if (j == uwmap.end())
{
// First occurrence.
uwmap[a] = i;
continue;
}
// We already have one occurrence. Merge them.
const binop* old =
static_cast<const binop*>(*j->second);
binop::type op = binop::U;
if (bo->op() == binop::W
|| old->op() == binop::W)
op = binop::W;
const formula* snd_arg =
multop::instance(multop::Or,
old->second()->clone(),
bo->second()->clone());
*j->second = binop::instance(op, a->clone(), snd_arg);
assert((*j->second)->kind() == formula::BinOp);
*i = 0;
old->destroy();
bo->destroy();
}
// (a R b) | (c R b) = (a | c) R b
// (a R b) | (c M b) = (a | c) R b
// (a M b) | (c M b) = (a | c) M b
for (multop::vec::iterator i = s.res_R_or_M->begin();
i != s.res_R_or_M->end(); ++i)
{
const binop* bo = static_cast<const binop*>(*i);
const formula* b = bo->second();
fmap_t::iterator j = rmmap.find(b);
if (j == rmmap.end())
{
// First occurrence.
rmmap[b] = i;
continue;
}
// We already have one occurrence. Merge them.
const binop* old =
static_cast<const binop*>(*j->second);
binop::type op = binop::M;
if (bo->op() == binop::R
|| old->op() == binop::R)
op = binop::R;
const formula* fst_arg =
multop::instance(multop::Or,
old->first()->clone(),
bo->first()->clone());
*j->second = binop::instance(op, fst_arg, b->clone());
assert((*j->second)->kind() == formula::BinOp);
*i = 0;
old->destroy();
bo->destroy();
}
// G(a) | (a U b) = a W b
// G(a) | (a W b) = a W b
// G(b) | (a R b) = a R b.
// G(b) | (a M b) = a R b.
for (multop::vec::iterator i = s.res_G->begin();
i != s.res_G->end(); ++i)
{
bool superfluous = false;
const unop* uo = static_cast<const unop*>(*i);
const formula* c = uo->child();
fmap_t::iterator j = uwmap.find(c);
if (j != uwmap.end())
{
superfluous = true;
const binop* bo =
static_cast<const binop*>(*j->second);
if (bo->op() == binop::U)
{
*j->second =
binop::instance(binop::W,
bo->first()->clone(),
bo->second()->clone());
assert((*j->second)->kind() == formula::BinOp);
bo->destroy();
}
}
j = rmmap.find(c);
if (j != rmmap.end())
{
superfluous = true;
const binop* bo =
static_cast<const binop*>(*j->second);
if (bo->op() == binop::M)
{
*j->second =
binop::instance(binop::R,
bo->first()->clone(),
bo->second()->clone());
assert((*j->second)->kind() == formula::BinOp);
bo->destroy();
}
}
if (superfluous)
{
(*i)->destroy();
*i = 0;
}
}
s.res_other->reserve(s.res_other->size()
+ s.res_G->size()
+ s.res_U_or_W->size()
+ s.res_R_or_M->size()
+ 3);
s.res_other->insert(s.res_other->end(),
s.res_G->begin(),
s.res_G->end());
delete s.res_G;
s.res_other->insert(s.res_other->end(),
s.res_U_or_W->begin(),
s.res_U_or_W->end());
delete s.res_U_or_W;
s.res_other->insert(s.res_other->end(),
s.res_R_or_M->begin(),
s.res_R_or_M->end());
delete s.res_R_or_M;
// Those "F" formulae that are universal can be
// postponed inside the X term if there is one.
//
// In effect we rewrite
// Xa|Xb|FGc|FGd|Fe as X(a|b|F(Gc|Gd))|Fe
if (!s.res_X->empty())
{
multop::vec* univ = new multop::vec;
for (multop::vec::iterator i = s.res_F->begin();
i != s.res_F->end(); ++i)
if ((*i)->is_universal())
{
univ->push_back(*i);
*i = 0; // Remove it from res_F.
}
s.res_X->push_back(unop_multop(unop::F,
multop::Or, univ));
}
// F(a) | F(b) | ... = F(a | b | ...)
const formula* allF =
unop_multop(unop::F, multop::Or, s.res_F);
// Xa | Xb | ... = X(a | b | ...)
const formula* allX =
unop_multop(unop::X, multop::Or, s.res_X);
s.res_other->push_back(allX);
s.res_other->push_back(allF);
s.res_other->push_back(allGF);
result_ = multop::instance(multop::Or, s.res_other);
// If we altered the formula in some way, process
// it another time.
if (result_ != mo)
result_ = recurse_destroy(result_);
return;
}
case multop::OrRat:
// FIXME: No simplifications yet.
break;
case multop::AndNLM:
{
mospliter s(mospliter::Split_Bool, res, c_);
if (!s.res_Bool->empty())
{
// b1 & b2 & b3 = b1 ∧ b2 ∧ b3
const formula* b =
multop::instance(multop::And, s.res_Bool);
// now we just consider b & rest
const formula* rest =
multop::instance(multop::AndNLM,
s.res_other);
// We have b & rest = b : rest if rest does not
// accept [*0]. Otherwise b & rest = b | (b : rest)
// FIXME: It would be nice to remove [*0] from rest.
if (rest->accepts_eword())
{
// The b & rest = b | (b : rest) rewriting
// augment the size, so do that only when
// explicitly requested.
if (!opt_.reduce_size_strictly)
{
const formula* brest =
multop::instance(multop::Fusion, b->clone(),
rest);
result_ =
multop::instance(multop::OrRat, b, brest);
}
else
{
result_ = multop::instance(multop::AndNLM,
b, rest);
}
}
else
{
result_ = multop::instance(multop::Fusion,
b, rest);
}
// If we altered the formula in some way, process
// it another time.
if (result_ != mo)
result_ = recurse_destroy(result_);
return;
}
else
{
// No Boolean as argument of &&.
delete s.res_Bool;
// Look for occurrences of {b;r} or {b:r}. We have
// {b1;r1}&{b2;r2} = {b1∧b2};{r1&r2}
// head1 tail1
// {b1:r1}&{b2:r2} = {b1∧b2}:{r1&r2}
// head2 tail2
multop::vec* head1 = new multop::vec;
multop::vec* tail1 = new multop::vec;
multop::vec* head2 = new multop::vec;
multop::vec* tail2 = new multop::vec;
for (multop::vec::iterator i = s.res_other->begin();
i != s.res_other->end(); ++i)
{
if (!*i)
continue;
if ((*i)->kind() != formula::MultOp)
continue;
const multop* f = down_cast<const multop*>(*i);
const formula* h = f->nth(0);
if (!h->is_boolean())
continue;
multop::type op = f->op();
if (op == multop::Concat)
{
head1->push_back(h->clone());
multop::vec* tail = new multop::vec;
unsigned s = f->size();
for (unsigned j = 1; j < s; ++j)
tail->push_back(f->nth(j)->clone());
tail1->push_back(multop::instance(op, tail));
(*i)->destroy();
*i = 0;
}
else if (op == multop::Fusion)
{
head2->push_back(h->clone());
multop::vec* tail = new multop::vec;
unsigned s = f->size();
for (unsigned j = 1; j < s; ++j)
tail->push_back(f->nth(j)->clone());
tail2->push_back(multop::instance(op, tail));
(*i)->destroy();
*i = 0;
}
else
{
continue;
}
}
if (!head1->empty())
{
const formula* h =
multop::instance(multop::And, head1);
const formula* t =
multop::instance(multop::AndNLM, tail1);
const formula* f =
multop::instance(multop::Concat, h, t);
s.res_other->push_back(f);
}
else
{
delete head1;
delete tail1;
}
if (!head2->empty())
{
const formula* h =
multop::instance(multop::And, head2);
const formula* t =
multop::instance(multop::AndNLM, tail2);
const formula* f =
multop::instance(multop::Fusion, h, t);
s.res_other->push_back(f);
}
else
{
delete head2;
delete tail2;
}
// {r1;b1}&{r2;b2} = {r1&r2};{b1∧b2}
// head3 tail3
// {r1:b1}&{r2:b2} = {r1&r2}:{b1∧b2}
// head4 tail4
multop::vec* head3 = new multop::vec;
multop::vec* tail3 = new multop::vec;
multop::vec* head4 = new multop::vec;
multop::vec* tail4 = new multop::vec;
for (multop::vec::iterator i = s.res_other->begin();
i != s.res_other->end(); ++i)
{
if (!*i)
continue;
if ((*i)->kind() != formula::MultOp)
continue;
const multop* f = down_cast<const multop*>(*i);
unsigned s = f->size() - 1;
const formula* t = f->nth(s);
if (!t->is_boolean())
continue;
multop::type op = f->op();
if (op == multop::Concat)
{
tail3->push_back(t->clone());
multop::vec* head = new multop::vec;
for (unsigned j = 0; j < s; ++j)
head->push_back(f->nth(j)->clone());
head3->push_back(multop::instance(op, head));
(*i)->destroy();
*i = 0;
}
else if (op == multop::Fusion)
{
tail4->push_back(t->clone());
multop::vec* head = new multop::vec;
for (unsigned j = 0; j < s; ++j)
head->push_back(f->nth(j)->clone());
head4->push_back(multop::instance(op, head));
(*i)->destroy();
*i = 0;
}
else
{
continue;
}
}
if (!head3->empty())
{
const formula* h =
multop::instance(multop::AndNLM, head3);
const formula* t =
multop::instance(multop::And, tail3);
const formula* f =
multop::instance(multop::Concat, h, t);
s.res_other->push_back(f);
}
else
{
delete head3;
delete tail3;
}
if (!head4->empty())
{
const formula* h =
multop::instance(multop::AndNLM, head4);
const formula* t =
multop::instance(multop::And, tail4);
const formula* f =
multop::instance(multop::Fusion, h, t);
s.res_other->push_back(f);
}
else
{
delete head4;
delete tail4;
}
result_ = multop::instance(multop::AndNLM, s.res_other);
// If we altered the formula in some way, process
// it another time.
if (result_ != mo)
result_ = recurse_destroy(result_);
return;
}
break;
}
case multop::Concat:
case multop::Fusion:
break;
}
}
result_ = multop::instance(mo->op(), res);
}
const formula*
recurse(const formula* f)
{
return simplify_recursively(f, c_);
}
const formula*
recurse_destroy(const formula* f)
{
const formula* tmp = recurse(f);
f->destroy();
return tmp;
}
protected:
const formula* result_;
ltl_simplifier_cache* c_;
const ltl_simplifier_options& opt_;
};
const formula*
simplify_recursively(const formula* f,
ltl_simplifier_cache* c)
{
trace << "** simplify_recursively(" << to_string(f) << ")";
const formula* result = c->lookup_simplified(f);
if (result)
{
trace << " cached: " << to_string(result) << std::endl;
return result;
}
else
{
trace << " miss" << std::endl;
}
simplify_visitor v(c);
f->accept(v);
result = v.result();
trace << "** simplify_recursively(" << to_string(f) << ") result: "
<< to_string(result) << std::endl;
c->cache_simplified(f, result);
return result;
}
}
//////////////////////////////////////////////////////////////////////
// ltl_simplifier_cache
// This implements the recursive rules for syntactic implication.
// (To follow this code please look at the table given as an
// appendix in the documentation for temporal logic operators.)
inline
bool
ltl_simplifier_cache::syntactic_implication_aux(const formula* f,
const formula* g)
{
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:
{
const unop* f_ = down_cast<const unop*>(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<const unop*>(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<const binop*>(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<const binop*>(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<const unop*>(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<const binop*>(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<const multop*>(f);
multop::type fo = f_->op();
unsigned fs = f_->size();
// First column.
switch (fo)
{
case multop::Or:
{
bool b = true;
for (unsigned i = 0; i < fs; ++i)
if (!syntactic_implication(f_->nth(i), g))
{
b &= false;
break;
}
if (b)
return true;
break;
}
case multop::And:
{
for (unsigned i = 0; i < fs; ++i)
if (syntactic_implication(f_->nth(i), g))
return true;
break;
}
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<const unop*>(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<const binop*>(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;
}
case formula::MultOp:
{
const multop* g_ = down_cast<const multop*>(g);
multop::type go = g_->op();
unsigned gs = g_->size();
switch (go)
{
case multop::And:
{
bool b = true;
for (unsigned i = 0; i < gs; ++i)
if (!syntactic_implication(f, g_->nth(i)))
{
b &= false;
break;
}
if (b)
return true;
break;
}
case multop::Or:
{
for (unsigned i = 0; i < gs; ++i)
if (syntactic_implication(f, g_->nth(i)))
return true;
break;
}
case multop::Concat:
case multop::Fusion:
case multop::AndNLM:
case multop::AndRat:
case multop::OrRat:
break;
}
break;
}
}
return false;
}
// Return true if f => g syntactically
bool
ltl_simplifier_cache::syntactic_implication(const formula* f,
const formula* g)
{
// We cannot run syntactic_implication on SERE formulae,
// except on Boolean formulae.
if (f->is_sere_formula() && !f->is_boolean())
return false;
if (g->is_sere_formula() && !g->is_boolean())
return false;
if (f == g)
return true;
if (g == constant::true_instance()
|| f == constant::false_instance())
return true;
if (g == constant::false_instance()
|| f == constant::true_instance())
return false;
// Often we compare a literals (an atomic_prop or its negation)
// to another literal. The result is necessarily false. To be
// true, the two literals would have to be equal, be we have
// already checked that.
if (f->is_in_nenoform() && g->is_in_nenoform()
&& (is_atomic_prop(f) || is_unop(f, unop::Not))
&& (is_atomic_prop(g) || is_unop(g, unop::Not)))
return false;
// Cache lookup
{
pairf p(f, g);
syntimpl_cache_t::const_iterator i = syntimpl_.find(p);
if (i != syntimpl_.end())
return i->second;
}
bool result;
if (f->is_boolean() && g->is_boolean())
result = bdd_implies(as_bdd(f), as_bdd(g));
else
result = syntactic_implication_aux(f, g);
// Cache result
{
pairf p(f->clone(), g->clone());
syntimpl_[p] = result;
// std::cerr << to_string(f) << (result ? " ==> " : " =/=> ")
// << to_string(g) << std::endl;
}
return result;
}
// If right==false, true if !f1 => f2, false otherwise.
// If right==true, true if f1 => !f2, false otherwise.
bool
ltl_simplifier_cache::syntactic_implication_neg(const formula* f1,
const formula* f2,
bool right)
{
// We cannot run syntactic_implication_neg on SERE formulae,
// except on Boolean formulae.
if (f1->is_sere_formula() && !f1->is_boolean())
return false;
if (f2->is_sere_formula() && !f2->is_boolean())
return false;
if (right)
f2 = nenoform_recursively(f2, true, this);
else
f1 = nenoform_recursively(f1, true, this);
bool result = syntactic_implication(f1, f2);
(right ? f2 : f1)->destroy();
return result;
}
/////////////////////////////////////////////////////////////////////
// ltl_simplifier
ltl_simplifier::ltl_simplifier(bdd_dict* d)
{
if (!d)
{
d = new bdd_dict;
owndict = true;
}
else
{
owndict = false;
}
cache_ = new ltl_simplifier_cache(d);
}
ltl_simplifier::ltl_simplifier(const ltl_simplifier_options& opt,
bdd_dict* d)
{
if (!d)
{
d = new bdd_dict;
owndict = true;
}
else
{
owndict = false;
}
cache_ = new ltl_simplifier_cache(d, opt);
}
ltl_simplifier::~ltl_simplifier()
{
bdd_dict* todelete = 0;
if (owndict)
todelete = cache_->dict;
delete cache_;
// It has to be deleted after the cache.
delete todelete;
}
const formula*
ltl_simplifier::simplify(const formula* f)
{
const formula* neno = 0;
if (!f->is_in_nenoform())
f = neno = negative_normal_form(f, false);
const formula* res = simplify_recursively(f, cache_);
if (neno)
neno->destroy();
return res;
}
const formula*
ltl_simplifier::negative_normal_form(const formula* f, bool negated)
{
return nenoform_recursively(f, negated, cache_);
}
bool
ltl_simplifier::syntactic_implication(const formula* f1, const formula* f2)
{
return cache_->syntactic_implication(f1, f2);
}
bool
ltl_simplifier::syntactic_implication_neg(const formula* f1,
const formula* f2, bool right)
{
return cache_->syntactic_implication_neg(f1, f2, right);
}
bool
ltl_simplifier::are_equivalent(const formula* f, const formula* g)
{
return cache_->lcc.equal(f, g);
}
bool
ltl_simplifier::implication(const formula* f, const formula* g)
{
return cache_->lcc.contained(f, g);
}
bdd
ltl_simplifier::as_bdd(const formula* f)
{
return cache_->as_bdd(f);
}
const formula*
ltl_simplifier::star_normal_form(const formula* f)
{
return cache_->star_normal_form(f);
}
bdd_dict*
ltl_simplifier::get_dict() const
{
return cache_->dict;
}
void
ltl_simplifier::print_stats(std::ostream& os) const
{
cache_->print_stats(os);
}
void
ltl_simplifier::clear_as_bdd_cache()
{
cache_->clear_as_bdd_cache();
cache_->lcc.clear();
}
}
}