dlvhex  2.5.0
src/SafetyChecker.cpp
Go to the documentation of this file.
00001 /* dlvhex -- Answer-Set Programming with external interfaces.
00002  * Copyright (C) 2005-2007 Roman Schindlauer
00003  * Copyright (C) 2006-2015 Thomas Krennwallner
00004  * Copyright (C) 2009-2016 Peter Schüller
00005  * Copyright (C) 2011-2016 Christoph Redl
00006  * Copyright (C) 2015-2016 Tobias Kaminski
00007  * Copyright (C) 2015-2016 Antonius Weinzierl
00008  *
00009  * This file is part of dlvhex.
00010  *
00011  * dlvhex is free software; you can redistribute it and/or modify it
00012  * under the terms of the GNU Lesser General Public License as
00013  * published by the Free Software Foundation; either version 2.1 of
00014  * the License, or (at your option) any later version.
00015  *
00016  * dlvhex is distributed in the hope that it will be useful, but
00017  * WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00019  * Lesser General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU Lesser General Public
00022  * License along with dlvhex; if not, write to the Free Software
00023  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
00024  * 02110-1301 USA.
00025  */
00026 
00036 #ifdef HAVE_CONFIG_H
00037 #include "config.h"
00038 #endif                           // HAVE_CONFIG_H
00039 
00040 #include "dlvhex2/SafetyChecker.h"
00041 #include "dlvhex2/Registry.h"
00042 #include "dlvhex2/Printer.h"
00043 
00044 #include <fstream>
00045 
00046 DLVHEX_NAMESPACE_BEGIN
00047 
00048 SafetyCheckerBase::SafetyCheckerBase(const ProgramCtx& ctx):
00049 ctx(ctx)
00050 {
00051 }
00052 
00053 
00054 SafetyCheckerBase::~SafetyCheckerBase()
00055 {
00056 }
00057 
00058 
00059 namespace
00060 {
00061     bool isSafeBuiltinPredicate(ID::TermBuiltinAddress pred) {
00062         switch(pred) {
00063             case ID::TERM_BUILTIN_MUL:
00064             case ID::TERM_BUILTIN_ADD:
00065             case ID::TERM_BUILTIN_INT:
00066             case ID::TERM_BUILTIN_SUCC:
00067                 // October 10, 2012: added the following three cases;
00068                 // it seems that all backends can handle this
00069             case ID::TERM_BUILTIN_SUB:
00070             case ID::TERM_BUILTIN_DIV:
00071             case ID::TERM_BUILTIN_MOD:
00072                 return true;
00073             default:
00074                 return false;
00075         }
00076     }
00077 }
00078 
00079 
00080 SafetyChecker::SafetyChecker(const ProgramCtx& ctx):
00081 SafetyCheckerBase(ctx)
00082 {
00083 }
00084 
00085 
00086 SafetyChecker::~SafetyChecker()
00087 {
00088 }
00089 
00090 
00091 namespace
00092 {
00093 
00094     bool reorderForSafety(RegistryPtr reg, std::list<ID>& src, Tuple& tgt, std::set<ID>& safevars);
00095 
00096     bool transferSafeLiteralsAndNewlySafeVariables(
00097         RegistryPtr reg, std::list<ID>& src, Tuple& tgt, const std::set<ID>& safevars, std::set<ID>& newsafevars);
00098 
00099     // for each element in src:
00100     // 1) check if it is one of 2.1) a) to e) (see SafetyChecker::operator())
00101     // 2) if yes
00102     // 2.1) move it from src to tgt
00103     // 2.2) put new safe vars (see 2.2) into newsafevars
00104     // 2.3) if something was previously nottransferred mark as reordered
00105     // 3) if no mark as nottransferred
00106     // return true iff reordering took place:
00107     //   condition: some element in src was nottransferred AND some element after that element was transferred
00108     bool transferSafeLiteralsAndNewlySafeVariables(
00109     RegistryPtr reg, std::list<ID>& src, Tuple& tgt, const std::set<ID>& safevars, std::set<ID>& newsafevars) {
00110         assert(!src.empty());
00111         assert(!!reg);
00112 
00113         bool nottransferred = false;
00114         bool reordered = false;
00115 
00116         typedef std::list<ID>::iterator ListIterator;
00117         typedef std::set<ID>::const_iterator SetCIterator;
00118         ListIterator it = src.begin();
00119         while( it != src.end() ) {
00120             DBGLOG(DBG,"checking literal " << printToString<RawPrinter>(*it, reg));
00121             bool transfer = false;
00122             assert(!it->isAtom() && it->isLiteral());
00123             if( it->isOrdinaryGroundAtom() ) {
00124                 DBGLOG(DBG," -> safe (ordinary ground)");
00125                 transfer = true;
00126             }
00127             else if( it->isNaf() && !it->isAggregateAtom() ) {
00128                 DBGLOG(DBG," -> need to check if all variables are safe (NAF and not ground and no aggregate)");
00129                 std::set<ID> vars;
00130                 reg->getVariablesInID(*it, vars);
00131                 bool good = true;
00132                 BOOST_FOREACH(ID idv, vars) {
00133                     if( safevars.find(idv) == safevars.end() ) {
00134                         good = false;
00135                         break;
00136                     }
00137                 }
00138                 if( good )
00139                     transfer = true;
00140             }
00141                                  // positive nonground
00142             else if( it->isOrdinaryNongroundAtom() ) {
00143                 DBGLOG(DBG," -> safe, marking all variables as safe");
00144                 reg->getVariablesInID(*it, newsafevars);
00145                 transfer = true;
00146             }
00147             else if( it->isExternalAtom() ) {
00148                 DBGLOG(DBG," -> checking input safety");
00149                 const ExternalAtom& atom = reg->eatoms.getByID(*it);
00150 
00151                 bool good = true;
00152                 BOOST_FOREACH(ID idt, reg->getVariablesInTuple(atom.inputs)) {
00153                     if( idt.isVariableTerm() && safevars.find(idt) == safevars.end() ) {
00154                         good = false;
00155                         break;
00156                     }
00157                 }
00158 
00159                 if( good ) {
00160                     DBGLOG(DBG," -> inputs safe, adding outputs as safe");
00161                     BOOST_FOREACH(ID idt, reg->getVariablesInTuple(atom.tuple)) {
00162                         if( idt.isVariableTerm() )
00163                             newsafevars.insert(idt);
00164                     }
00165                     transfer = true;
00166                 }
00167             }
00168             else if( it->isAggregateAtom() ) {
00169                 // this is complicated if it is implemented properly:
00170                 // so we only do lightweight checking (the backend will complain anyways)
00171                 //
00172                 // a) get safe variables from aggregate body
00173                 // b) if aggregate is an assignment and the aggregate is not in a NAF literal,
00174                 //    make assigned variable safe
00175 
00176                 const AggregateAtom& atom = reg->aatoms.getByID(*it);
00177 
00178                 // a) get safe variables from aggregate body
00179                 // (if we cannot consume the body completely,
00180                 // we have to wait with checking this aggregate)
00181 
00182                 std::list<ID> tmpSrcBody(atom.literals.begin(), atom.literals.end());
00183                 Tuple tmpTgt;
00184                 tmpTgt.reserve(atom.literals.size());
00185                 std::set<ID> tmpNewSafeVars(safevars);
00186 
00187                 bool reordered_aggregate =
00188                     reorderForSafety(reg, tmpSrcBody, tmpTgt, tmpNewSafeVars);
00189 
00190                 if( tmpSrcBody.empty() ) {
00191                     // fully consumed body -> make variables in aggregate body safe
00192                     // TODO we did this before, but this should not be allowed!
00193                     //safevars.insert(tmpNewSafeVars.begin(), tmpNewSafeVars.end());
00194 
00195                     // if aggregate is an assignment and not in NAF, make assigned variable safe
00196                     if( !it->isNaf() ) {
00197                         if( atom.tuple[1] == ID::termFromBuiltin(ID::TERM_BUILTIN_EQ) ) {
00198                             assert(atom.tuple[0] != ID_FAIL);
00199                             if( atom.tuple[0].isVariableTerm() )
00200                                 newsafevars.insert(atom.tuple[0]);
00201                         }
00202 
00203                         if( atom.tuple[3] == ID::termFromBuiltin(ID::TERM_BUILTIN_EQ) ) {
00204                             assert(atom.tuple[4] != ID_FAIL);
00205                             if( atom.tuple[4].isVariableTerm() )
00206                                 newsafevars.insert(atom.tuple[4]);
00207                         }
00208                     }
00209 
00210                     if( reordered_aggregate ) {
00211                         LOG(WARNING,"TODO we should probably store back the reordered aggregate into the registry and transfer this new safety-reordered aggregate (and not propagate the reordering to the parent, as the parent does not change by reordering within the aggregate)");
00212                     }
00213 
00214                     transfer = true;
00215                 }
00216             }
00217             else if( it->isBuiltinAtom() ) {
00218                 const BuiltinAtom& atom = reg->batoms.getByID(*it);
00219 
00220                 // bailout-do
00221                 do {
00222                     // undocumented safety of dlv:
00223                     // equality is safe in both directions
00224                     // nothing else is safe in any direction for comparison builtins
00225                     if( atom.tuple.size() == 3 &&
00226                     atom.tuple[0] == ID::termFromBuiltin(ID::TERM_BUILTIN_EQ) ) {
00227                         DBGLOG(DBG," -> equality builtin");
00228                         if( !atom.tuple[1].isVariableTerm() ||
00229                         safevars.find(atom.tuple[1]) != safevars.end() ) {
00230                             // first can make second safe
00231                             if( atom.tuple[2].isVariableTerm() ) {
00232                                 // second is something that can be made safe
00233                                 newsafevars.insert(atom.tuple[2]);
00234                                 transfer = true;
00235                                 break;
00236                             }
00237                             else {
00238                                 transfer = true;
00239                                 break;
00240                             }
00241                         }
00242                         if( !atom.tuple[2].isVariableTerm() ||
00243                         safevars.find(atom.tuple[2]) != safevars.end() ) {
00244                             // second can make first safe
00245                             if( atom.tuple[1].isVariableTerm() ) {
00246                                 // first is something that can be made safe
00247                                 newsafevars.insert(atom.tuple[1]);
00248                                 transfer = true;
00249                                 break;
00250                             }
00251                             else {
00252                                 transfer = true;
00253                                 break;
00254                             }
00255                         }
00256                     }
00257                     else if( isSafeBuiltinPredicate(static_cast<ID::TermBuiltinAddress>(atom.tuple[0].address)) ) {
00258                         // safe if occurs as last variable in builtin predicate and other variables are safe
00259                         // (see dlv manual)
00260                         DBGLOG(DBG," -> 'safeBuiltinPredicate'");
00261                         Tuple::const_reverse_iterator itt = atom.tuple.rbegin();
00262                         // skip last one
00263                         if( itt != atom.tuple.rend() )
00264                             itt++;
00265                         bool good = true;
00266                         for(; itt != atom.tuple.rend(); ++itt) {
00267                             if( itt->isVariableTerm() && safevars.find(*itt) == safevars.end() ) {
00268                                 good = false;
00269                                 break;
00270                             }
00271                         }
00272 
00273                         if( good ) {
00274                             if( atom.tuple.back().isVariableTerm() )
00275                                 newsafevars.insert(atom.tuple.back());
00276                             transfer = true;
00277                             break;
00278                         }
00279                     }
00280                     else {
00281                         // other -> all variables must be safe
00282                         DBGLOG(DBG," -> other builtin");
00283                         bool good = true;
00284                         BOOST_FOREACH(ID idv, atom.tuple) {
00285                             if( idv.isVariableTerm() &&
00286                             safevars.find(idv) == safevars.end() ) {
00287                                 good = false;
00288                                 break;
00289                             }
00290                         }
00291                         if( good )
00292                             transfer = true;
00293                     }
00294                 }
00295                 while(false);    // bailout-do
00296             }
00297             else {
00298                 assert(false && "encountered unexpected literal during safety reordering");
00299             }
00300 
00301             if( transfer ) {
00302                 // transfer it
00303                 DBGLOG(DBG," -> transferring");
00304                 if( nottransferred )
00305                     reordered = true;
00306                 tgt.push_back(*it);
00307                 it = src.erase(it);
00308             }
00309             else {
00310                 // do not transfer it
00311                 DBGLOG(DBG," -> not transferring");
00312                 nottransferred = true;
00313                 it++;
00314             }
00315         }
00316 
00317         DBGLOG(DBG, "transferSafeLiteralsAndNewlySafeVariables returning with "
00318             "reordered=" << reordered << " and nottransferred=" << nottransferred);
00319         return reordered;
00320     }
00321 
00322     bool reorderForSafety(RegistryPtr reg, std::list<ID>& src, Tuple& tgt, std::set<ID>& safevars) {
00323         DBGLOG_SCOPE(DBG, "rFS", false);
00324         DBGLOG(DBG, "=reorderForSafety");
00325         assert(!!reg);
00326         assert(!src.empty());
00327 
00328         bool changed = false;
00329         do {
00330             DBGLOG(DBG, "safety reordering loop:");
00331             DBGLOG(DBG, " src '" <<
00332                 printManyToString<RawPrinter>(Tuple(src.begin(), src.end()), ",", reg) << "'");
00333             DBGLOG(DBG, " safevars '" <<
00334                 printManyToString<RawPrinter>(Tuple(safevars.begin(), safevars.end()), ",", reg) << "'");
00335 
00336             // 2.1) and 2.2)
00337             std::set<ID> newsafevars;
00338             changed |= transferSafeLiteralsAndNewlySafeVariables(
00339                 reg, src, tgt, safevars, newsafevars);
00340 
00341             DBGLOG(DBG, " -> src '" <<
00342                 printManyToString<RawPrinter>(Tuple(src.begin(), src.end()), ",", reg) << "'");
00343             DBGLOG(DBG, " -> tgt '" <<
00344                 printManyToString<RawPrinter>(Tuple(tgt.begin(), tgt.end()), ",", reg) << "'");
00345             DBGLOG(DBG, " -> newsafevars '" <<
00346                 printManyToString<RawPrinter>(Tuple(newsafevars.begin(), newsafevars.end()), ",", reg) << "'");
00347 
00348             safevars.insert(newsafevars.begin(), newsafevars.end());
00349 
00350             if( newsafevars.empty() )
00351                 break;
00352         }
00353         while( !src.empty() );
00354 
00355         return changed;
00356     }
00357 
00358 }                                // anonymous namespace
00359 
00360 
00361 void
00362 SafetyChecker::operator() () const throw (SyntaxError)
00363 {
00364     Tuple unsafe = checkSafety(true);
00365     assert(unsafe.size() == 0);
00366 }
00367 
00368 
00369 Tuple
00370 SafetyChecker::checkSafety (bool throwOnUnsafeVariables) const throw (SyntaxError)
00371 {
00372     LOG_SCOPE(ANALYZE,"safety",false);
00373     LOG(ANALYZE,"=safety checker");
00374 
00375     RegistryPtr reg = ctx.registry();
00376     assert(!!reg);
00377 
00378     //
00379     // testing for simple rule safety:
00380     // * a constant is safe
00381     // * Note: for compatibility with other solvers, we do not assume
00382     //   (as dlv does) that all anonymous variables are automatically safe
00383     // * a variable is safe if it occurs in a positive ordinary atom
00384     // * a variable is safe if it occurs as particular terms of a positive
00385     //   builtin atom and particular other terms of that builtin atom are safe
00386     //   (since 2010 dlv version the definition of "particular", below called
00387     //   "certain", changed, and possibly will change again in the future)
00388     // * a variable is safe if it occurs in the output list of a positive
00389     //   external atom and all input variables of that atom are safe
00390     // * a variable is safe if it occurs on one side of an assignment aggregate
00391     // * a variable is safe if it occurs in the positive body of an aggregate atom
00392     WARNING("the last line is a simplification")
00393         WARNING("see dlv documentation for better aggregate safety checking")
00394     //
00395     // algorithm (this algorithm does reordering for safety):
00396     // 1) init empty target rule body, init empty safe variables list
00397     // 2) do
00398     // 2.1) find all literals L={L1,L2,...} in source body which are safe
00399     //      a) negative {ordinary atoms, external atoms, builtins, aggregates} with all variables safe
00400     //      b) positive ordinary atoms
00401     //      c) positive external atoms with all input variables safe
00402     //      d) positive builtins with all "builtin input variables" safe
00403     //      e) positive aggregates with assigned variables safe
00404     // 2.2) remove L from source body and append to target body, mark variables as safe
00405     //      for b) mark all variables
00406     //      for c) mark output variables
00407     //      for d) mark "builtin output variables"
00408     //      for e) mark assigned variables
00409     // 2.3) if source rule body not empty and variables were marked repeat
00410     // 3) if source rule body is empty and rule head contains only safe variables
00411     // 3.1) report rule as safe and store it back to registry
00412     // 3.2) otherwise throw an exception containing rule and variables
00413 
00414     BOOST_FOREACH(ID idrule, ctx.idb) {
00415         DBGLOG(ANALYZE,"= check safety of rule " <<
00416             printToString<RawPrinter>(idrule, reg));
00417 
00418         const Rule& rule = reg->rules.getByID(idrule);
00419 
00420         // create rule with same kind and same storage space
00421         Rule newRule(rule.kind);
00422         std::set<ID> safevars;
00423         newRule.body.reserve(rule.body.size());
00424         bool changed = false;
00425 
00426         // only check body if not empty (not for disjunctive facts)
00427         if( !rule.body.empty() ) {
00428             // store source body in linked list (more efficient to modify)
00429             std::list<ID> src(rule.body.begin(), rule.body.end());
00430 
00431             // 2)
00432             changed = reorderForSafety(reg, src, newRule.body, safevars);
00433 
00434             // 3)
00435             if( !src.empty() ) {
00436                 // body is not safe -> report unsafe
00437 
00438                 // get body variables
00439                 std::set<ID> remainingbodyvars;
00440                 Tuple remainingbody(src.begin(), src.end());
00441                 reg->getVariablesInTuple(remainingbody, remainingbodyvars);
00442 
00443                 // get unsafe head variables
00444                 Tuple unsafeBodyVars;
00445                 std::back_insert_iterator<Tuple> inserter(unsafeBodyVars);
00446                 std::set_difference(
00447                     remainingbodyvars.begin(), remainingbodyvars.end(),
00448                     safevars.begin(), safevars.end(),
00449                     inserter);
00450 
00451                 #ifndef NDEBUG
00452                 std::stringstream ss;
00453                 BOOST_FOREACH (ID var, unsafeBodyVars) { ss << printToString<RawPrinter>(var, reg) << " "; }
00454                 DBGLOG(DBG, "Found the following set of unsafe body variables: " << ss.str());
00455                 #endif
00456 
00457                 if (!throwOnUnsafeVariables) return unsafeBodyVars;
00458                 else throw SyntaxError("Rule not safe (body): "
00459                         "'" + printToString<RawPrinter>(idrule, reg) + "': "
00460                         "literals not safe: " +
00461                         printManyToString<RawPrinter>(Tuple(src.begin(), src.end()), ", ", reg) + ", "
00462                         "safe variables: " +
00463                         printManyToString<RawPrinter>(Tuple(safevars.begin(), safevars.end()), ", ", reg) + ", "
00464                         "unsafe variables: " +
00465                         printManyToString<RawPrinter>(Tuple(unsafeBodyVars.begin(), unsafeBodyVars.end()), ", ", reg));
00466             }
00467         }
00468 
00469         // variables in the positive head guard are also safe
00470         if( !rule.headGuard.empty() ) {
00471             // get all head guard variables
00472             std::set<ID> safeheadguardvars;
00473             reg->getVariablesInTuple(rule.headGuard, safeheadguardvars);
00474 
00475             // get positive head guard atoms
00476             std::list<ID> posheadguard;
00477             BOOST_FOREACH (ID id, rule.headGuard) {
00478                 if (!id.isNaf()) posheadguard.push_back(id);
00479             }
00480 
00481             if( !posheadguard.empty() ) {
00482                 // get headGuard variables
00483                 Tuple headGuard(posheadguard.begin(), posheadguard.end());
00484                 reg->getVariablesInTuple(rule.headGuard, safevars);
00485             }
00486             #ifndef NDEBUG
00487             std::stringstream ss;
00488             BOOST_FOREACH (ID var, safevars) { ss << printToString<RawPrinter>(var, reg) << " "; }
00489             DBGLOG(DBG, "Found the following set of safe variables: " << ss.str());
00490             #endif
00491 
00492             // get all head guard variables
00493             std::set<ID> headguardvars;
00494             Tuple headguard(rule.headGuard.begin(), rule.headGuard.end());
00495             reg->getVariablesInTuple(headguard, headguardvars);
00496 
00497             // get unsafe head guard variables
00498             Tuple unsafeHeadGuardVars;
00499             std::back_insert_iterator<Tuple> inserter(unsafeHeadGuardVars);
00500             std::set_difference(
00501                 headguardvars.begin(), headguardvars.end(),
00502                 safevars.begin(), safevars.end(),
00503                 inserter);
00504 
00505             if (!unsafeHeadGuardVars.empty()) {
00506                 #ifndef NDEBUG
00507                 std::stringstream ss;
00508                 BOOST_FOREACH (ID var, unsafeHeadGuardVars) { ss << printToString<RawPrinter>(var, reg) << " "; }
00509                 DBGLOG(DBG, "Found the following set of unsafe head guard variables: " << ss.str());
00510                 #endif
00511                 if (!throwOnUnsafeVariables) return unsafeHeadGuardVars;
00512                 else throw SyntaxError("Rule not safe (head guard): "
00513                         "'" + printToString<RawPrinter>(idrule, reg) + "': "
00514                         "literals not safe: " +
00515                         printManyToString<RawPrinter>(Tuple(rule.headGuard.begin(), rule.headGuard.end()), ", ", reg) + ", "
00516                         "safe variables: " +
00517                         printManyToString<RawPrinter>(Tuple(safeheadguardvars.begin(), safeheadguardvars.end()), ", ", reg) + ", "
00518                         "unsafe variables: " +
00519                         printManyToString<RawPrinter>(Tuple(unsafeHeadGuardVars.begin(), unsafeHeadGuardVars.end()), ", ", reg));
00520             }
00521         }
00522 
00523         // if we are here the body is safe -> check head
00524 
00525         // get head variables
00526         std::set<ID> headvars;
00527         reg->getVariablesInTuple(rule.head, headvars);
00528 
00529         // get unsafe head variables
00530         Tuple unsafeHeadVars;
00531         std::back_insert_iterator<Tuple> inserter(unsafeHeadVars);
00532         std::set_difference(
00533             headvars.begin(), headvars.end(),
00534             safevars.begin(), safevars.end(),
00535             inserter);
00536 
00537         // report unsafe if unsafe
00538         if( !unsafeHeadVars.empty() ) {
00539             #ifndef NDEBUG
00540             std::stringstream ss;
00541             BOOST_FOREACH (ID var, unsafeHeadVars) { ss << printToString<RawPrinter>(var, reg) << " "; }
00542             DBGLOG(DBG, "Found the following set of unsafe head variables: " << ss.str());
00543             #endif
00544             if (!throwOnUnsafeVariables) return unsafeHeadVars;
00545             else throw SyntaxError("Rule not safe (head): "
00546                     "'" + printToString<RawPrinter>(idrule, reg) + "': "
00547                     "variables not safe: " +
00548                     printManyToString<RawPrinter>(unsafeHeadVars, ", ", reg));
00549         }
00550 
00551         // if rule body was reordered for safety, store it back to rule table (i.e., change rule!)
00552         if( changed ) {
00553             DBGLOG(DBG,"storing back rule " << printToString<RawPrinter>(idrule, reg));
00554             newRule.head = rule.head;
00555             if (reg->rules.getIDByElement(newRule) == ID_FAIL) {
00556                 reg->rules.update(rule, newRule);
00557             }
00558             DBGLOG(DBG,"-> reordered rule " << printToString<RawPrinter>(idrule, reg));
00559             assert(&rule == &(reg->rules.getByID(idrule)));
00560         }
00561     }
00562     return Tuple();
00563 }
00564 
00565 
00566 StrongSafetyChecker::StrongSafetyChecker(const ProgramCtx& ctx):
00567 SafetyCheckerBase(ctx)
00568 {
00569 }
00570 
00571 
00572 StrongSafetyChecker::~StrongSafetyChecker()
00573 {
00574 }
00575 
00576 
00577 // testing for strong safety:
00578 //
00579 // A rule is strongly safe, if
00580 // * it is safe and
00581 // * if an external atom in the rule is part of a cycle, each variable in
00582 //   its output list occurs in a positive atom in the body, which does not
00583 //   belong to the cycle.
00584 //
00585 // this is implemented as
00586 // for each component c
00587 // A) check if any external atom has output variables, if no exit with success
00588 // B) get all rule heads in c
00589 // C) for each rule r in c
00590 //   a) for each external atom e in the body of r
00591 //     1) for each output variable of e
00592 //        if e is part of a positive body atom of r
00593 //        and this positive body atom of r does not unify with any rule head in c
00594 //        then e is safe
00595 //     2) if any output variable of e is not safe, rule r is not strongly safe
00596 void
00597 StrongSafetyChecker::operator() () const throw (SyntaxError)
00598 {
00599     LOG_SCOPE(ANALYZE,"strongsafety",false);
00600     LOG(ANALYZE,"=strong safety checker");
00601 
00602     RegistryPtr reg = ctx.registry();
00603     assert(!!reg);
00604 
00605     if (ctx.config.getOption("LiberalSafety")) {
00606         return;
00607     }
00608 
00609     // at this point we may (and do) assume that all rules are safe
00610 
00611     // we also assume that we have a component graph
00612     assert(!!ctx.compgraph);
00613     ComponentGraph::ComponentIterator itc, itc_end;
00614     for(boost::tie(itc, itc_end) = ctx.compgraph->getComponents();
00615     itc != itc_end; ++itc) {
00616         const ComponentGraph::ComponentInfo& ci = ctx.compgraph->propsOf(*itc);
00617 
00618         // ignore components without inner eatoms
00619         // (they are automatically strongly safe)
00620         if( ci.innerEatoms.empty() )
00621             continue;
00622 
00623         // check if any external atom has output variables
00624         bool needToCheck = false;
00625         BOOST_FOREACH(ID eaid, ci.innerEatoms) {
00626             const ExternalAtom& eatom = reg->eatoms.getByID(eaid);
00627             BOOST_FOREACH(ID otid, eatom.tuple) {
00628                 if( reg->getVariablesInID(otid).size() > 0 ) {
00629                     needToCheck = true;
00630                     break;
00631                 }
00632             }
00633             if( needToCheck )
00634                 break;
00635         }
00636 
00637         if( !needToCheck ) {
00638             DBGLOG(DBG,"no need to check strong safety "
00639                 "as there are no outputs in internal eatoms " <<
00640                 printManyToString<RawPrinter>(ci.innerEatoms, ", ", reg));
00641             continue;
00642         }
00643         DBGLOG(DBG,"need to check strong safety in component " << *itc);
00644 
00645         // get rule heads here
00646         // here we store the full atom IDs (we need to unify, the predicate is not sufficient)
00647         std::set<ID> headAtomIDs;
00648         // we only consider inner rules (constraints have no heads)
00649         BOOST_FOREACH(ID rid, ci.innerRules) {
00650             const Rule& rule = reg->rules.getByID(rid);
00651 
00652             BOOST_FOREACH(ID hid, rule.head) {
00653                 if( !hid.isOrdinaryAtom() ) {
00654                     LOG(WARNING,"ignoring non-ordinary atom in rule head for strong safety checking: " <<
00655                         printToString<RawPrinter>(hid, reg));
00656                     continue;
00657                 }
00658                 headAtomIDs.insert(hid);
00659             }
00660         }
00661 
00662         DBGLOG(DBG,"in component " <<  *itc <<
00663             " got set of heads '" <<
00664             printManyToString<RawPrinter>(
00665             Tuple(headAtomIDs.begin(), headAtomIDs.end()), ", ", reg) << "'");
00666 
00667         // now check output variables
00668 
00669         // we again only consider inner rules (positive domain expansion feedback
00670         // cannot happen through constraints as they cannot generate symbols)
00671         BOOST_FOREACH(ID rid, ci.innerRules) {
00672             if( !rid.doesRuleContainExtatoms() ) {
00673                 DBGLOG(DBG,"skipping strong safety check for rule " <<
00674                     printToString<RawPrinter>(rid, reg) << " (no external atoms)");
00675                 continue;
00676             }
00677 
00678             const Rule& rule = reg->rules.getByID(rid);
00679 
00680             DBGLOG(DBG,"now checking strong safety of rule " <<
00681                 printToString<RawPrinter>(rid, reg));
00682 
00683             // find all variable outputs in all eatoms in this rule's body
00684             std::set<ID> varsToCheck;
00685             BOOST_FOREACH(ID lid, rule.body) {
00686                 if( !lid.isExternalAtom() )
00687                     continue;
00688 
00689                 const ExternalAtom& eatom = reg->eatoms.getByID(lid);
00690                 BOOST_FOREACH(ID tid, eatom.tuple) {
00691                     reg->getVariablesInID(tid, varsToCheck);
00692                 }
00693             }
00694 
00695             WARNING("variables that do not occur in any other atom should be automatically strongly safe...?")
00696                 DBGLOG(DBG,"need to find component-external domain predicate "
00697                 "for variables {" << printManyToString<RawPrinter>(
00698                 Tuple(varsToCheck.begin(), varsToCheck.end()), ", ", reg) + "}");
00699 
00700             // for each variable:
00701             // if it is part of a positive body atom of r
00702             // and this positive body atom of r does not unify with any rule head in c
00703             // then e is safe
00704             BOOST_FOREACH(ID vid, varsToCheck) {
00705                 // check strong safety of variable vid
00706                 DBGLOG(DBG,"checking strong safety of variable " <<
00707                     printToString<RawPrinter>(vid,reg));
00708 
00709                 bool variableSafe = false;
00710                 BOOST_FOREACH(ID lid, rule.body) {
00711                     // skip negative bodies
00712                     if( lid.isNaf() )
00713                         continue;
00714 
00715                     // skip external atoms,
00716                     // they could but cannot in general be assumed to limit the domain
00717                     // (and that's the reason we need to check strong safety)
00718                     if( lid.isExternalAtom() )
00719                         continue;
00720 
00721                     // skip non-ordinary atoms
00722                     WARNING("can we use aggregates to limit the domain for strong safety?")
00723                         WARNING("can we use builtin atoms to limit the domain for strong safety?")
00724                         if( lid.isAggregateAtom() ||
00725                         lid.isBuiltinAtom() )
00726                         continue;
00727 
00728                     assert(lid.isOrdinaryAtom());
00729 
00730                     // check if this body literal contains the variable
00731                     // and does not unify with any head
00732                     // (only then the variable is safe)
00733                     bool containsVariable = false;
00734                     const OrdinaryAtom& oatom = reg->lookupOrdinaryAtom(lid);
00735                     assert(!oatom.tuple.empty());
00736                     BOOST_FOREACH (ID var, reg->getVariablesInTuple(oatom.tuple)) {
00737                         if( var == vid ) {
00738                             containsVariable = true;
00739                             break;
00740                         }
00741                     }
00742 
00743                     if( !containsVariable ) {
00744                         DBGLOG(DBG,"skipping body literal " <<
00745                             printToString<RawPrinter>(lid, reg) <<
00746                             " (does not contain variable)");
00747                         continue;
00748                     }
00749 
00750                     // oatom 'oatom' was retrieved using ID 'lid'
00751                     DBGLOG(DBG,"checking unifications of body literal " <<
00752                         printToString<RawPrinter>(lid, reg) << " with component rule heads");
00753                     bool doesNotUnify = true;
00754                     BOOST_FOREACH(ID hid, headAtomIDs) {
00755                         DBGLOG(DBG,"checking against " <<
00756                             printToString<RawPrinter>(hid, reg));
00757                         assert(hid.isOrdinaryAtom());
00758                         const OrdinaryAtom& hoatom = reg->lookupOrdinaryAtom(hid);
00759                         if( oatom.unifiesWith(hoatom, reg) ) {
00760                             DBGLOG(DBG,"unification successful "
00761                                 "-> literal does not limit the domain");
00762                             doesNotUnify = false;
00763                             break;
00764                         }
00765                     }
00766 
00767                     if( doesNotUnify ) {
00768                         DBGLOG(DBG, "variable safe!");
00769                         variableSafe = true;
00770                         break;
00771                     }
00772                 }
00773 
00774                 if( !variableSafe ) {
00775                     throw SyntaxError("Rule is not strongly safe! "
00776                         " Variable " + printToString<RawPrinter>(vid,reg) +
00777                         " fails strong safety check in rule " +
00778                         printToString<RawPrinter>(rid, reg));
00779                 }
00780             }
00781         }
00782     }
00783 }
00784 
00785 
00786 DLVHEX_NAMESPACE_END
00787 
00788 
00789 // vim:expandtab:ts=4:sw=4:
00790 // mode: C++
00791 // End: