spot/src/ltlvisit/simplify.cc
Alexandre Duret-Lutz 395793d986 Rewrite a&XGa as Ga, and a|XFa as Fa.
The actual rules are a bit more complex:
a & X(G(a&b...)&c...) = Ga & X(G(b...)&c...)
a | X(Fa | c) = F(a) | c
with the second rule being applied only if all XF can
be removed.  See the documentation for an example.

* src/ltlvisit/simplify.cc: Implement these new rules.
* doc/tl/tl.tex: Document them.
* src/ltltest/reduccmp.test: Add test cases.
2012-04-28 09:34:44 +02:00

3354 lines
82 KiB
C++

// Copyright (C) 2011, 2012 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 2 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 Spot; see the file COPYING. If not, write to the Free
// Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
#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 <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();
}
}
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, ltl_simplifier_options opt)
: dict(d), options(opt), lcc(d, true, true, false, false)
{
opt.containment_checks |= opt.containment_checks_stronger;
}
// 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::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)
{
return (options.synt_impl && syntactic_implication(f1, f2))
|| (options.containment_checks && contained(f1, f2));
}
// 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();
}
private:
f2b_map as_bdd_;
f2f_map simplified_;
f2f_map nenoform_;
syntimpl_cache_t syntimpl_;
};
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()
{
}
formula* result() const
{
return result_;
}
void
visit(atomic_prop* ap)
{
formula* f = ap->clone();
if (negated_)
result_ = unop::instance(unop::Not, f);
else
result_ = f;
}
void
visit(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(unop* uo)
{
formula* f = uo->child();
switch (uo->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:
result_ = unop::instance(negated_ ?
unop::Closure : uo->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(bunop* bo)
{
// !(a*) is not simplified, whatever that means
result_ = bunop::instance(bo->op(), recurse_(bo->child(), false),
bo->min(), bo->max());
if (negated_)
result_ = unop::instance(unop::Not, result_);
}
formula* equiv_or_xor(bool equiv, formula* f1, 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(binop* bo)
{
formula* f1 = bo->first();
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(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(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:
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:
{
for (unsigned i = 0; i < mos; ++i)
res->push_back(recurse_(mo->nth(i), false));
result_ = multop::instance(op, res);
assert(!negated_);
}
}
}
formula*
recurse_(formula* f, bool negated)
{
return
const_cast<formula*>(nenoform_recursively(f, negated, cache_));
}
formula*
recurse(formula* f)
{
return recurse_(f, negated_);
}
protected:
formula* result_;
bool negated_;
ltl_simplifier_cache* cache_;
};
const formula*
nenoform_recursively(const formula* f,
bool negated,
ltl_simplifier_cache* c)
{
if (f->kind() == formula::UnOp)
{
const unop* uo = static_cast<const unop*>(f);
if (uo->op() == unop::Not)
{
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);
const_cast<formula*>(f)->accept(v);
result = v.result();
}
c->cache_nenoform(key, result);
done:
if (negated)
key->destroy();
return result;
}
//////////////////////////////////////////////////////////////////////
//
// SIMPLIFY_VISITOR
//
//////////////////////////////////////////////////////////////////////
// Forward declaration.
formula*
simplify_recursively(const formula* f, ltl_simplifier_cache* c);
constant*
is_constant(formula* f)
{
if (f->kind() != formula::Constant)
return 0;
return static_cast<constant*>(f);
}
unop*
is_unop(formula* f, unop::type op)
{
if (f->kind() != formula::UnOp)
return 0;
unop* uo = static_cast<unop*>(f);
if (uo->op() != op)
return 0;
return uo;
}
unop*
is_X(formula* f)
{
return is_unop(f, unop::X);
}
unop*
is_F(formula* f)
{
return is_unop(f, unop::F);
}
unop*
is_G(formula* f)
{
return is_unop(f, unop::G);
}
unop*
is_GF(formula* f)
{
unop* op = is_G(f);
if (!op)
return 0;
return is_F(op->child());
}
unop*
is_FG(formula* f)
{
unop* op = is_F(f);
if (!op)
return 0;
return is_G(op->child());
}
binop*
is_binop(formula* f, binop::type op)
{
if (f->kind() != formula::BinOp)
return 0;
binop* bo = static_cast<binop*>(f);
if (bo->op() != op)
return 0;
return bo;
}
binop*
is_U(formula* f)
{
return is_binop(f, binop::U);
}
binop*
is_M(formula* f)
{
return is_binop(f, binop::M);
}
binop*
is_R(formula* f)
{
return is_binop(f, binop::R);
}
binop*
is_W(formula* f)
{
return is_binop(f, binop::W);
}
multop*
is_multop(formula* f, multop::type op)
{
if (f->kind() != formula::MultOp)
return 0;
multop* mo = static_cast<multop*>(f);
if (mo->op() != op)
return 0;
return mo;
}
multop*
is_And(formula* f)
{
return is_multop(f, multop::And);
}
multop*
is_Or(formula* f)
{
return is_multop(f, multop::Or);
}
formula*
unop_multop(unop::type uop, multop::type mop, multop::vec* v)
{
return unop::instance(uop, multop::instance(mop, v));
}
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));
}
formula*
unop_unop(unop::type uop1, unop::type uop2, 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(formula* f)
{
switch (f->kind())
{
case formula::UnOp:
{
unop* uo = static_cast<unop*>(f);
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)
{
unop* cc = is_G(c);
if (cc)
{
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)
{
unop* cc = is_F(c);
if (cc)
{
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:
{
binop* bo = static_cast<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, multop* mo, ltl_simplifier_cache* cache)
: split_(split), c_(cache)
{
init();
unsigned mos = mo->size();
for (unsigned i = 0; i < mos; ++i)
{
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()
{
}
formula*
result() const
{
return result_;
}
void
visit(atomic_prop* ap)
{
result_ = ap->clone();
}
void
visit(constant* c)
{
result_ = c;
}
void
visit(bunop* bo)
{
result_ = bunop::instance(bo->op(), recurse(bo->child()),
bo->min(), bo->max());
}
void
visit(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)
binop* bo = is_U(result_);
if (bo)
{
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)
{
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)
{
unop* u = is_X(result_);
if (u)
{
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;
}
}
}
// 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)
binop* bo = is_R(result_);
if (bo)
{
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)
{
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 (result_->kind() == formula::UnOp)
{
unop* u = static_cast<unop*>(result_);
if (u->op() == unop::X)
{
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 (result_->kind() == formula::MultOp)
{
multop* mo = static_cast<multop*>(result_);
if (mo->op() == multop::Or)
{
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;
}
}
}
}
// if a => Ga, keep a.
if (opt_.containment_checks_stronger
&& c_->lcc.contained(result_, uo))
return;
break;
case unop::Finish:
case unop::Closure:
case unop::NegClosure:
// No simplification
break;
}
result_ = unop::instance(op, result_);
}
void
visit(binop* bo)
{
binop::type op = bo->op();
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;
}
}
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))
{
formula* tmp = multop::instance(multop::And, a, b);
result_ = recurse(tmp);
tmp->destroy();
return;
}
if (a->is_universal() && (op == binop::W))
{
formula* tmp = multop::instance(multop::Or, a, b);
result_ = recurse(tmp);
tmp->destroy();
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 (b->kind() == formula::BinOp)
{
binop* bo = static_cast<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)
{
binop* cd = static_cast<binop*>(bo->second());
if ((cd->op() == binop::U || cd->op() == binop::W)
&& c_->implication(a, cd->first()))
{
a->destroy();
result_ = 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_ = 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
binop* bo = static_cast<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 (a->kind() == formula::BinOp)
{
// if b => a then (a R c) R b = c R b
// if b => a then (a M c) R b = c R b
binop* bo = static_cast<binop*>(a);
if ((bo->op() == binop::R || bo->op() == binop::M)
&& c_->implication(b, bo->first()))
{
result_ = recurse_destroy
(binop::instance(binop::R,
bo->second()->clone(),
b));
a->destroy();
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)
{
binop* bo = static_cast<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;
}
}
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
binop* bo = static_cast<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 (a->kind() == formula::BinOp)
{
// if b => a then (a M c) M b = c M b
binop* bo = static_cast<binop*>(a);
if (bo->op() == binop::M
&& c_->implication(b, bo->first()))
{
result_ = recurse_destroy
(binop::instance(binop::M,
bo->second()->clone(),
b));
a->destroy();
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;
}
// Same effect as dynamic_cast<unop*>, only faster.
unop* fu1 =
(a->kind() == formula::UnOp) ? static_cast<unop*>(a) : 0;
unop* fu2 =
(b->kind() == formula::UnOp) ? static_cast<unop*>(b) : 0;
// 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)
{
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 (b->kind() == formula::MultOp)
{
multop* fm2 = static_cast<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)
{
if (fm2->nth(i)->kind() != formula::UnOp)
continue;
unop* c = static_cast<unop*>(fm2->nth(i));
if (c->op() == unop::G && c->child() == a)
{
multop::vec* v = new multop::vec;
v->reserve(s - 1);
int j;
for (j = 0; j < i; ++j)
v->push_back(fm2->nth(j)->clone());
// skip j=i
for (++j; j < s; ++j)
v->push_back(fm2->nth(j)->clone());
b->destroy();
result_ = recurse_destroy(binop::instance
(binop::W, a,
multop::instance(multop::Or, v)));
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;
multop::vec* v = new multop::vec;
v->reserve(s - 1);
int j;
for (j = 0; j < i; ++j)
v->push_back(fm2->nth(j)->clone());
// skip j=i
for (++j; j < s; ++j)
v->push_back(fm2->nth(j)->clone());
b->destroy();
result_ = recurse_destroy(binop::instance
(op == binop::U ? binop::M : binop::R,
multop::instance(multop::And, v), a));
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
// a M (b & c & F(a)) = a M b
if (b->kind() == formula::MultOp)
{
multop* fm2 = static_cast<multop*>(b);
if (fm2->op() == multop::And)
{
int s = fm2->size();
for (int i = 0; i < s; ++i)
{
if (fm2->nth(i)->kind() != formula::UnOp)
continue;
unop* c = static_cast<unop*>(fm2->nth(i));
if (c->op() == unop::F && c->child() == a)
{
multop::vec* v = new multop::vec;
v->reserve(s - 1);
int j;
for (j = 0; j < i; ++j)
v->push_back(fm2->nth(j)->clone());
// skip j=i
for (++j; j < s; ++j)
v->push_back(fm2->nth(j)->clone());
b->destroy();
result_ = recurse_destroy(binop::instance
(binop::M, a,
multop::instance(multop::And, v)));
return;
}
}
}
}
}
}
case binop::Xor:
case binop::Equiv:
case binop::Implies:
case binop::EConcat:
case binop::UConcat:
case binop::EConcatMarked:
// No simplification... Yet?
break;
}
result_ = binop::instance(op, a, b);
}
void
visit(automatop*)
{
assert(0);
}
void
visit(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::Concat)
&& (op != multop::Fusion))
{
bool is_and = op != multop::Or; // And or AndNLM
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:
if (!mo->is_sere_formula())
{
// a & X(G(a&b...) & c...) = Ga & X(G(b...) & c...)
if (!mo->is_X_free())
{
typedef Sgi::hash_set<formula*,
ptr_hash<formula> > fset_t;
fset_t xgset;
fset_t xset;
unsigned s = res->size();
// 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;
unop* uo = is_X((*res)[n]);
if (!uo)
continue;
formula* c = uo->child();
multop* a;
unop* g;
if ((g = is_G(c)))
{
#define HANDLE_G 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)
{
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 second 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)
{
formula* x = (*res)[n];
if (!x)
continue;
fset_t::const_iterator g = xgset.find(x);
if (g != xgset.end())
{
// x can appear only once.
formula* gf = *g;
xgset.erase(g);
gf->destroy();
(*res)[n] = unop::instance(unop::G, x);
}
}
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);
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);
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)
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<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)
{
binop* bo = static_cast<binop*>(*i);
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.
binop* old = static_cast<binop*>(*j->second);
binop::type op = binop::W;
if (bo->op() == binop::U
|| old->op() == binop::U)
op = binop::U;
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)
{
binop* bo = static_cast<binop*>(*i);
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.
binop* old = static_cast<binop*>(*j->second);
binop::type op = binop::R;
if (bo->op() == binop::M
|| old->op() == binop::M)
op = binop::M;
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;
unop* uo = static_cast<unop*>(*i);
formula* c = uo->child();
fmap_t::iterator j = uwmap.find(c);
if (j != uwmap.end())
{
superfluous = true;
binop* bo = static_cast<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;
binop* bo = static_cast<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 & ...)
formula* allG =
unop_multop(unop::G, multop::And, s.res_G);
// Xa & Xb & ... = X(a & b & ...)
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;
}
else // SERE
{
mospliter s(mospliter::Split_Bool, res, c_);
if (!s.res_Bool->empty())
{
// b1 & b2 & b3 = b1 && b2 && b3
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:
{
bunop* r = down_cast<bunop*>(*i);
// b && r[*i..j] = b & r if i<=1<=j
// = 0 otherwise
// likewise for b && r[=i..j]
// and b && r[->i..j]
if (r->min() > 1 || r->max() < 1)
goto returnfalse;
ares->push_back(r->child()->clone());
r->destroy();
*i = 0;
break;
}
case formula::MultOp:
{
multop* r = down_cast<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]
{
formula* ri = 0;
unsigned nonempty = 0;
for (unsigned j = 0; j < rs; ++j)
{
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());
formula* sumf =
multop::instance(multop::Or, 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::And, 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;
multop* f = down_cast<multop*>(*i);
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())
{
formula* h = multop::instance(multop::And, head1);
formula* t = multop::instance(multop::And, tail1);
formula* f = multop::instance(multop::Concat,
h, t);
s.res_other->push_back(f);
}
else
{
delete head1;
delete tail1;
}
if (!head2->empty())
{
formula* h = multop::instance(multop::And, head2);
formula* t = multop::instance(multop::And, tail2);
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;
multop* f = down_cast<multop*>(*i);
unsigned s = f->size() - 1;
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())
{
formula* h = multop::instance(multop::And, head3);
formula* t = multop::instance(multop::And, tail3);
formula* f = multop::instance(multop::Concat,
h, t);
s.res_other->push_back(f);
}
else
{
delete head3;
delete tail3;
}
if (!head4->empty())
{
formula* h = multop::instance(multop::And, head4);
formula* t = multop::instance(multop::And, tail4);
formula* f = multop::instance(multop::Fusion,
h, t);
s.res_other->push_back(f);
}
else
{
delete head4;
delete tail4;
}
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;
}
}
case multop::Or:
{
// a | X(F(a|b...) | c...) = Fa | X(F(b...) | c...)
if (!mo->is_X_free())
{
typedef Sgi::hash_set<formula*,
ptr_hash<formula> > fset_t;
fset_t xfset;
fset_t xset;
unsigned s = res->size();
// 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;
unop* uo = is_X((*res)[n]);
if (!uo)
continue;
formula* c = uo->child();
multop* o;
unop* f;
if ((f = is_F(c)))
{
#define HANDLE_F 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)
{
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)
{
formula* x = (*res)[n];
if (!x)
continue;
fset_t::const_iterator f = xfset.find(x);
if (f != xfset.end())
--allofthem;
assert(allofthem != -1U);
}
// 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)
{
formula* x = (*res)[n];
if (!x)
continue;
fset_t::const_iterator f = xfset.find(x);
if (f != xfset.end())
{
// x can appear only once.
formula* ff = *f;
xfset.erase(f);
ff->destroy();
(*res)[n] = unop::instance(unop::F, x);
}
}
// 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);
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);
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)
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<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)
{
binop* bo = static_cast<binop*>(*i);
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.
binop* old = static_cast<binop*>(*j->second);
binop::type op = binop::U;
if (bo->op() == binop::W
|| old->op() == binop::W)
op = binop::W;
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)
{
binop* bo = static_cast<binop*>(*i);
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.
binop* old = static_cast<binop*>(*j->second);
binop::type op = binop::M;
if (bo->op() == binop::R
|| old->op() == binop::R)
op = binop::R;
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;
unop* uo = static_cast<unop*>(*i);
formula* c = uo->child();
fmap_t::iterator j = uwmap.find(c);
if (j != uwmap.end())
{
superfluous = true;
binop* bo = static_cast<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;
binop* bo = static_cast<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 | ...)
formula* allF = unop_multop(unop::F, multop::Or, s.res_F);
// Xa | Xb | ... = X(a | b | ...)
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::AndNLM:
case multop::Concat:
case multop::Fusion:
break;
}
}
result_ = multop::instance(mo->op(), res);
}
formula*
recurse(formula* f)
{
return simplify_recursively(f, c_);
}
formula*
recurse_destroy(formula* f)
{
formula* tmp = recurse(f);
f->destroy();
return tmp;
}
protected:
formula* result_;
ltl_simplifier_cache* c_;
const ltl_simplifier_options& opt_;
};
formula*
simplify_recursively(const formula* f,
ltl_simplifier_cache* c)
{
trace << "** simplify_recursively(" << to_string(f) << ")";
formula* result = const_cast<formula*>(c->lookup_simplified(f));
if (result)
{
trace << " cached: " << to_string(result) << std::endl;
return result;
}
else
{
trace << " miss" << std::endl;
}
simplify_visitor v(c);
const_cast<formula*>(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, g1)
&& 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:
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:
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;
// 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())
{
bdd l = as_bdd(f);
bdd r = as_bdd(g);
result = ((l & r) == l);
}
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(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;
}
formula*
ltl_simplifier::simplify(const formula* f)
{
formula* neno = 0;
if (!f->is_in_nenoform())
f = neno = negative_normal_form(f, false);
formula* res = const_cast<formula*>(simplify_recursively(f, cache_));
if (neno)
neno->destroy();
return res;
}
formula*
ltl_simplifier::negative_normal_form(const formula* f, bool negated)
{
return const_cast<formula*>(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);
}
bdd
ltl_simplifier::as_bdd(const formula* f)
{
return cache_->as_bdd(f);
}
bdd_dict*
ltl_simplifier::get_dict() const
{
return cache_->dict;
}
}
}