/* * This file is part of the program ltl2dstar (http://www.ltl2dstar.de/). * Copyright (C) 2005-2007 Joachim Klein * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** @file * Implementation of LTL2DSTAR_Scheduler */ #include "LTL2DRA.hpp" #include "LTL2DSTAR_Scheduler.hpp" #include "DAUnionAlgorithm.hpp" #include "DRAOptimizations.hpp" #include "NBA2DRA.hpp" #include "DRA2NBA.hpp" #include "LTLSafetyAutomata.hpp" #include "StutterSensitivenessInformation.hpp" #include "common/TempFile.hpp" #include "common/RunProgram.hpp" #include "common/Exceptions.hpp" #include #include #include #include #include #include #include #include #include #include /** Dummy namespace for the building blocks */ class LTL2DSTAR_Scheduler_Trees { public: class LTL2DSTAR_Tree_Start; class LTL2DSTAR_Tree_Safra; class LTL2DSTAR_Tree_Rabin; class LTL2DSTAR_Tree_Streett; class LTL2DSTAR_Tree_Union; /** Base class for the building blocks for the scheduler */ class LTL2DSTAR_Tree { public: /** Type of a vector over the children */ typedef std::vector child_vector; /** * Constructor * @param ltl The LTL formula * @param options the LTL2DSTAR options * @param sched a reference back to the scheduler */ LTL2DSTAR_Tree(LTLFormula_ptr& ltl, LTL2DSTAR_Options options, LTL2DSTAR_Scheduler& sched) : _ltl(ltl), _options(options), _sched(sched) {} /** Destructor */ virtual ~LTL2DSTAR_Tree() { for (child_vector::iterator it=children.begin(); it!=children.end(); ++it) { delete *it; } } /** Print the tree on output stream */ virtual void printTree(std::ostream& out, unsigned int level=0) { for (unsigned int i=0;iprintTree(out, level+1); } } /** Abstract virtual function for tree generation */ virtual void generateTree() = 0; /** Estimate the size of the automaton */ virtual unsigned int guestimate() { return 0; } /** Hook that is called after calculate() finishes */ virtual void hook_after_calculate() {}; /** Calculate the automaton for this building block, by default * calculate the automata for the children and then choose the smallest. */ virtual void calculate(int level=0, unsigned int limit=0) { if (_options.verbose_scheduler) { std::cerr << "Calculate ("<< level <<"): " << typeid(*this).name() << std::endl; } calculateChildren(level, limit); bool first=true; for (child_vector::iterator it=children.begin(); it!=children.end(); ++it) { if ((*it)->_automaton==0) { continue; } if (first) { _automaton=(*it)->_automaton; _comment=(*it)->_comment; } else { if (_automaton->size() > (*it)->_automaton->size()) { _automaton=(*it)->_automaton; _comment=(*it)->_comment; } } first=false; } hook_after_calculate(); } /** Add a new child */ void addChild(LTL2DSTAR_Tree* child) { if (child==0) {return;} children.push_back(child); } /** Calculate the automata for the children */ void calculateChildren(int level=0, unsigned int limit=0) { if (_sched.flagOptLimits()) { DRA_ptr _min_automaton; unsigned int _min_size=0; for (child_vector::iterator it=children.begin(); it!=children.end(); ++it) { unsigned int child_limit; if (_min_size!=0) { if (limit > 0) { child_limit=(std::min)(_sched.calcLimit(_min_size), limit); } else { child_limit=_sched.calcLimit(_min_size); } } else { child_limit=limit; } if (_options.verbose_scheduler) { std::cerr << " Limit (with alpha) = " << child_limit << std::endl; } try { (*it)->calculate(level+1, child_limit); if ((*it)->_automaton.get()!=0) { if (_min_size==0 || (*it)->_automaton->size() < _min_size) { _min_automaton=(*it)->_automaton; _min_size=_min_automaton->size(); } else { // delete automaton as it is bigger // than necessary (*it)->_automaton.reset(); } } } catch (LimitReachedException& e) { (*it)->_automaton.reset(); } } } else { for (child_vector::iterator it=children.begin(); it!=children.end(); ++it) { (*it)->calculate(level+1, limit); } } } LTLFormula_ptr _ltl; LTL2DSTAR_Options _options; unsigned int priority; DRA_ptr _automaton; std::string _comment; LTL2DSTAR_Scheduler& _sched; std::vector children; }; /** The root building block for the calculation of DRA/DSA */ class LTL2DSTAR_Tree_Start : public LTL2DSTAR_Tree { public: /** * Constructor * @param ltl The LTL formula * @param options the LTL2DSTAR options * @param sched a reference back to the scheduler */ LTL2DSTAR_Tree_Start(LTLFormula_ptr ltl, LTL2DSTAR_Options options, LTL2DSTAR_Scheduler& sched) : LTL2DSTAR_Tree(ltl, options, sched) { generateTree(); } /** Generate the tree */ virtual void generateTree() { LTL2DSTAR_Tree_Rabin* rabin=0; LTL2DSTAR_Tree_Streett* streett=0; if (_options.automata==LTL2DSTAR_Options::RABIN || _options.automata==LTL2DSTAR_Options::RABIN_AND_STREETT) { rabin=new LTL2DSTAR_Tree_Rabin(_ltl, _options, _sched); } if (_options.automata==LTL2DSTAR_Options::STREETT || _options.automata==LTL2DSTAR_Options::RABIN_AND_STREETT) { streett=new LTL2DSTAR_Tree_Streett(_ltl->negate()->toPNF(), _options, _sched); } if (rabin!=0 && streett!=0) { unsigned int rabin_est = rabin->guestimate(); unsigned int streett_est = streett->guestimate(); if (_options.verbose_scheduler) { std::cerr << "NBA-Estimates: Rabin: "<checkLTL(_ltl); if (!stutter_information->isCompletelyInsensitive() && _options.opt_safra.partial_stutter_check) { NBA_ptr nba, complement_nba; if (rabin) { nba=rabin->getNBA(); } else if (streett) { nba=streett->getNBA(); } if (rabin && streett) { complement_nba=streett->getNBA(); } if (!nba) { stutter_information->checkPartial(*_ltl, _sched.getLTL2DRA()); } else if (!complement_nba) { stutter_information->checkPartial(*nba, *_ltl->negate()->toPNF(), _sched.getLTL2DRA()); } else { stutter_information->checkNBAs(*nba, *complement_nba); } } if (rabin) { rabin->setStutterInformation(stutter_information); } if (streett) { streett->setStutterInformation(stutter_information); } } } }; /** A building block for the calculation of a Rabin automaton * (via Safra, Scheck or Union) */ class LTL2DSTAR_Tree_Rabin : public LTL2DSTAR_Tree { public: /** * Constructor * @param ltl The LTL formula * @param options the LTL2DSTAR options * @param sched a reference back to the scheduler */ LTL2DSTAR_Tree_Rabin(LTLFormula_ptr ltl, LTL2DSTAR_Options options, LTL2DSTAR_Scheduler& sched) : LTL2DSTAR_Tree(ltl, options, sched), _tree_normal(0), _tree_union(0) { generateTree(); } /** Estimate the size of the automaton (use the estimate of Safra's * building block ) */ virtual unsigned int guestimate() { if (_tree_normal!=0) { return _tree_normal->guestimate(); } return 0; } /** Hook after calculation */ virtual void hook_after_calculate() { if (_tree_normal!=0 && _sched.flagStatNBA()) { _comment = _comment + std::string("+NBAstd=")+ (boost::lexical_cast(guestimate())); } } /** Generate the tree */ virtual void generateTree() { if (_options.scheck_path!="") { if (LTL2DSTAR_Tree_Scheck::worksWith(*_ltl, _options.verbose_scheduler)) { addChild(new LTL2DSTAR_Tree_Scheck(_ltl, _options, _sched)); } // add stuff for path. check here } if (_options.allow_union && _ltl->getRootNode()->getType() == LTLNode::T_OR) { _tree_union=new LTL2DSTAR_Tree_Union(_ltl, _options, _sched); addChild(_tree_union); } if (!((_options.only_union && _options.allow_union) || (_options.only_safety && _options.safety))) { _tree_normal=new LTL2DSTAR_Tree_Safra(_ltl, _options, _sched); addChild(_tree_normal); } } NBA_ptr getNBA() { if (_tree_normal) { return _tree_normal->getNBA(); } return NBA_ptr((NBA_t*)0); } void setStutterInformation(StutterSensitivenessInformation::ptr stutter_information) { _stutter_information=stutter_information; if (_tree_normal) { _tree_normal->setStutterInformation(_stutter_information); } if (_tree_union) { _tree_union->setStutterInformation(_stutter_information); } } private: // memory will be freed by normal tree destructor LTL2DSTAR_Tree_Safra *_tree_normal; LTL2DSTAR_Tree_Union *_tree_union; StutterSensitivenessInformation::ptr _stutter_information; }; /** Building block for the translation from LTL to DRA using Safra's algorithm */ class LTL2DSTAR_Tree_Safra : public LTL2DSTAR_Tree { public: /** * Constructor * @param ltl The LTL formula * @param options the LTL2DSTAR options * @param sched a reference back to the scheduler */ LTL2DSTAR_Tree_Safra(LTLFormula_ptr ltl, LTL2DSTAR_Options options, LTL2DSTAR_Scheduler& sched) : LTL2DSTAR_Tree(ltl, options, sched) { generateTree(); } /** Generate the tree */ virtual void generateTree() { } /** Translate LTL -> NBA */ void generateNBA() { if (_nba.get()==0) { _nba = _sched.getLTL2DRA().ltl2nba(*_ltl); } } NBA_ptr getNBA() { generateNBA(); return _nba; } /** Estimate the size of the DRA (returns the size of the NBA) */ virtual unsigned int guestimate() { generateNBA(); if (_nba.get()!=0) { return _nba->size(); } return 0; } /** Translate the LTL formula to DRA using Safra's algorithm */ virtual void calculate(int level, unsigned int limit) { if (_options.verbose_scheduler) { std::cerr << "Calculate ("<< level <<"): " << typeid(*this).name() << std::endl; std::cerr << " Limit = " << limit << std::endl; } generateNBA(); if (_nba.get()==0) { THROW_EXCEPTION(Exception, "Couldn't create NBA from LTL formula"); }; _automaton=_sched.getLTL2DRA().nba2dra(*_nba, limit, _options.detailed_states, _stutter_information); _comment=std::string("Safra[NBA=")+ boost::lexical_cast(_nba->size())+ "]"; if (_options.optimizeAcceptance) { _automaton->optimizeAcceptanceCondition(); } if (_options.bisim) { DRAOptimizations dra_optimizer; _automaton=dra_optimizer.optimizeBisimulation(*_automaton, false, _options.detailed_states, false); } } void setStutterInformation(StutterSensitivenessInformation::ptr stutter_information) { _stutter_information=stutter_information; } private: NBA_ptr _nba; StutterSensitivenessInformation::ptr _stutter_information; }; /** Generate DRA by using the union construction on two DRAs */ class LTL2DSTAR_Tree_Union : public LTL2DSTAR_Tree { public: /** * Constructor * @param ltl The LTL formula * @param options the LTL2DSTAR options * @param sched a reference back to the scheduler */ LTL2DSTAR_Tree_Union(LTLFormula_ptr ltl, LTL2DSTAR_Options options, LTL2DSTAR_Scheduler& sched) : LTL2DSTAR_Tree(ltl, options, sched), _left_tree(0), _right_tree(0) { _left= _ltl->getSubFormula(_ltl->getRootNode()->getLeft()); _right= _ltl->getSubFormula(_ltl->getRootNode()->getRight()); generateTree(); } /** * Generate the tree */ virtual void generateTree() { LTL2DSTAR_Options rec_opt=_options; rec_opt.recursion(); _left_tree=new LTL2DSTAR_Tree_Rabin(_left, rec_opt, _sched); addChild(_left_tree); _right_tree=new LTL2DSTAR_Tree_Rabin(_right, rec_opt, _sched); addChild(_right_tree); } /** * Perform union construction */ virtual void calculate(int level, unsigned int limit) { if (_options.verbose_scheduler) { std::cerr << "Calculate ("<< level <<"): " << typeid(*this).name() << std::endl; } try { children[0]->calculate(level+1, limit); children[1]->calculate(level+1, limit); } catch (LimitReachedException& e) { _automaton.reset(); return; } if (children[0]->_automaton.get()==0 || children[1]->_automaton.get()==0) { return; } bool union_trueloop=_sched.getLTL2DRA().getOptions().union_trueloop; if (_sched.getLTL2DRA().getOptions().stutter) { _automaton=DRA_t::calculateUnionStuttered(*children[0]->_automaton, *children[1]->_automaton, _stutter_information, union_trueloop, _options.detailed_states); } else { _automaton=DRA_t::calculateUnion(*children[0]->_automaton, *children[1]->_automaton, union_trueloop, _options.detailed_states); /* _automaton=DRAOperations::dra_union(*children[0]->_automaton, *children[1]->_automaton, union_trueloop, _options.detailed_states); */ } _comment=std::string("Union{")+ children[0]->_comment+","+ children[1]->_comment+"}"; if (_options.optimizeAcceptance) { _automaton->optimizeAcceptanceCondition(); } if (_options.bisim) { DRAOptimizations dra_optimizer; _automaton=dra_optimizer.optimizeBisimulation(*_automaton, false, _options.detailed_states, false); } hook_after_calculate(); } void setStutterInformation(StutterSensitivenessInformation::ptr stutter_information) { _stutter_information=stutter_information; StutterSensitivenessInformation::ptr left_stutter_info(new StutterSensitivenessInformation(*stutter_information)); StutterSensitivenessInformation::ptr right_stutter_info(new StutterSensitivenessInformation(*stutter_information)); if (!stutter_information->isCompletelyInsensitive()) { left_stutter_info->checkLTL(_left); right_stutter_info->checkLTL(_right); } if (!left_stutter_info->isCompletelyInsensitive()) { left_stutter_info->checkPartial(*_left_tree->getNBA(), *_left->negate()->toPNF(), _sched.getLTL2DRA()); } if (!right_stutter_info->isCompletelyInsensitive()) { right_stutter_info->checkPartial(*_right_tree->getNBA(), *_right->negate()->toPNF(), _sched.getLTL2DRA()); } _left_tree->setStutterInformation(left_stutter_info); _right_tree->setStutterInformation(right_stutter_info); } LTL2DSTAR_Tree_Rabin *_left_tree; LTL2DSTAR_Tree_Rabin *_right_tree; LTLFormula_ptr _left; LTLFormula_ptr _right; StutterSensitivenessInformation::ptr _stutter_information; }; /** * Generate Streett automaton by calculating the Rabin automaton * for the negated formula */ class LTL2DSTAR_Tree_Streett : public LTL2DSTAR_Tree { public: /** * Constructor * @param ltl The LTL formula * @param options the LTL2DSTAR options * @param sched a reference back to the scheduler */ LTL2DSTAR_Tree_Streett(LTLFormula_ptr ltl, LTL2DSTAR_Options options, LTL2DSTAR_Scheduler& sched) : LTL2DSTAR_Tree(ltl, options, sched) { generateTree(); } /** Estimate automaton size (use estimate of Rabin building block) */ virtual unsigned int guestimate() { if (children[0]!=0) { return children[0]->guestimate(); } return 0; } NBA_ptr getNBA() { if (_tree_rabin) { return _tree_rabin->getNBA(); } return NBA_ptr((NBA_t*)0); } /** Generate tree */ virtual void generateTree() { LTL2DSTAR_Options opt=_options; opt.automata=LTL2DSTAR_Options::RABIN; opt.scheck_path=""; // disable scheck _tree_rabin=new LTL2DSTAR_Tree_Rabin(_ltl, opt, _sched); addChild(_tree_rabin); } /** Calculate */ virtual void calculate(int level, unsigned int limit) { if (_options.verbose_scheduler) { std::cerr << "Calculate ("<< level <<"): " << typeid(*this).name() << std::endl; } try { children[0]->calculate(level, limit); } catch (LimitReachedException& e) { _automaton.reset(); return; } _automaton=children[0]->_automaton; _comment=std::string("Streett{")+ children[0]->_comment+"}"; if (_automaton.get()!=0) { _automaton->considerAsStreett(); } hook_after_calculate(); } void setStutterInformation(StutterSensitivenessInformation::ptr stutter_information) { _stutter_information=stutter_information; _tree_rabin->setStutterInformation(stutter_information); } private: LTL2DSTAR_Tree_Rabin* _tree_rabin; StutterSensitivenessInformation::ptr _stutter_information; }; /** * Use Scheck to generate a DBA (transformed into a DRA) for a (co-)safe LTL formula */ class LTL2DSTAR_Tree_Scheck : public LTL2DSTAR_Tree { public: /** * Constructor * @param ltl The LTL formula * @param options the LTL2DSTAR options * @param sched a reference back to the scheduler */ LTL2DSTAR_Tree_Scheck(LTLFormula_ptr ltl, LTL2DSTAR_Options options, LTL2DSTAR_Scheduler& sched) : LTL2DSTAR_Tree(ltl, options, sched) { generateTree(); } /** Check if the formula is syntactically (co-)safe */ static bool worksWith(LTLFormula& ltl, bool verbose=false) { if (ltl.isSafe()) { if (verbose) { std::cerr << "Formula is safe" << std::endl; } return true; } else if (ltl.isCoSafe()) { if (verbose) { std::cerr << "Formula is cosafe" << std::endl; } return true; } return false; } /** Generate tree */ virtual void generateTree() { } /** Calculate */ virtual void calculate(int level, unsigned int limit) { if (_options.verbose_scheduler) { std::cerr << "Calculate ("<< level <<"): " << typeid(*this).name() << std::endl; } LTLSafetyAutomata lsa; _automaton=lsa.ltl2dra(*_ltl, _options.scheck_path); if (_automaton.get()==0) {return;} _comment=std::string("Scheck"); if (_options.optimizeAcceptance) { _automaton->optimizeAcceptanceCondition(); } /* not really necessary with scheck if (_options.bisim) { DRAOptimizations dra_optimizer; _automaton=dra_optimizer.optimizeBisimulation(*_automaton, false, _options.detailed_states, false); } */ hook_after_calculate(); } }; }; /** Calculate the new limit using factor alpha (returns 0 if no limit) */ unsigned int LTL2DSTAR_Scheduler::calcLimit(unsigned int limit) { if (limit==0) {return limit;} if (flagOptLimits()) { double new_limit=(limit*_alpha)+1.0; if (new_limit > (std::numeric_limits::max)()) { limit = 0; } else { limit=(unsigned int)new_limit; } } return limit; } /** Constructor * @param ltl2dra the wrapper for LTL->NBA and NBA->DRA * @param opt_limits use limiting? * @param alpha the limiting factor */ LTL2DSTAR_Scheduler::LTL2DSTAR_Scheduler(LTL2DRA& ltl2dra, bool opt_limits, double alpha) : _ltl2dra(ltl2dra), _opt_limits(opt_limits), _alpha(alpha), _stat_NBA(false) {} /** * Generate a DRA/DSA for the LTL formula */ DRA_ptr LTL2DSTAR_Scheduler::calculate(LTLFormula& ltl, LTL2DSTAR_Options ltl_opt) { LTLFormula_ptr ltl_p(ltl.toPNF()); if (ltl_opt.verbose_scheduler) { std::cerr << ltl_p->toStringInfix() << std::endl; } LTL2DSTAR_Scheduler_Trees::LTL2DSTAR_Tree_Start root(ltl_p, ltl_opt, *this); if (ltl_opt.verbose_scheduler) { root.printTree(std::cerr); } root.calculate(); DRA_ptr result=root._automaton; if (result.get()!=0) { result->setComment(root._comment+getTimingInformation()); } return result; } std::string LTL2DSTAR_Scheduler::getTimingInformation() { if (StutterSensitivenessInformation::hasTimekeeping()) { unsigned long ms=StutterSensitivenessInformation::getTimeKeeper().getElapsedMilliseconds(); return " TIME(stuttercheck)=:"+ boost::lexical_cast(ms)+ ":"; } else { return ""; } } /** Get the LTL2DRA wrapper class */ LTL2DRA& LTL2DSTAR_Scheduler::getLTL2DRA() { return _ltl2dra; }