dlvhex  2.5.0
src/PredicateMask.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 
00035 #ifdef HAVE_CONFIG_H
00036 #include "config.h"
00037 #endif                           // HAVE_CONFIG_H
00038 
00039 #include "dlvhex2/PredicateMask.h"
00040 #include "dlvhex2/Interpretation.h"
00041 #include "dlvhex2/Logger.h"
00042 #include "dlvhex2/Printhelpers.h"
00043 #include "dlvhex2/Registry.h"
00044 #include "dlvhex2/Printer.h"
00045 #include "dlvhex2/OrdinaryAtomTable.h"
00046 #include "dlvhex2/PluginInterface.h"
00047 #include "dlvhex2/ProgramCtx.h"
00048 
00049 #include <boost/foreach.hpp>
00050 
00051 DLVHEX_NAMESPACE_BEGIN
00052 
00053 PredicateMask::PredicateMask():
00054 maski(),
00055 knownAddresses(0)
00056 {
00057 }
00058 
00059 
00060 PredicateMask::PredicateMask(const PredicateMask& other):
00061 predicates(other.predicates),
00062 maski(other.maski),
00063 knownAddresses(other.knownAddresses),
00064 updateMutex()                    // must not copy mutex!
00065 {
00066     if( !!other.maski )
00067         LOG(WARNING,"copied PredicateMask with non-NULL mask!");
00068 }
00069 
00070 
00071 PredicateMask&
00072 PredicateMask::operator=(const PredicateMask& other)
00073 {
00074     predicates = other.predicates;
00075     maski = other.maski;
00076     knownAddresses = other.knownAddresses;
00077     // must not copy mutex!
00078     if( !!other.maski )
00079         LOG(WARNING,"assigned PredicateMask with non-NULL mask!");
00080     return *this;
00081 }
00082 
00083 
00084 PredicateMask::~PredicateMask()
00085 {
00086 }
00087 
00088 
00089 void PredicateMask::setRegistry(RegistryPtr reg)
00090 {
00091     assert((!maski || maski->getRegistry() == reg) && "PredicateMask cannot change registry!");
00092     if( !maski ) {
00093         maski.reset(new Interpretation(reg));
00094     }
00095 }
00096 
00097 
00098 void PredicateMask::addPredicate(ID pred)
00099 {
00100     boost::mutex::scoped_lock lock(updateMutex);
00101 
00102     DBGLOG_VSCOPE(DBG,"PM::aP",this,false);
00103     DBGLOG(DBG,"adding predicate " << pred << ", knownAddresses was " << knownAddresses);
00104     assert(pred.isTerm() && pred.isConstantTerm() && "predicate masks can only be done on constant terms");
00105     predicates.insert(pred.address);
00106     knownAddresses = 0;          // scan the whole address space again
00107 }
00108 
00109 
00110 void PredicateMask::updateMask()
00111 {
00112     //DBGLOG_VSCOPE(DBG,"PM::uM",this,false);
00113     //DBGLOG(DBG,"= PredicateMask::updateMask for predicates " <<
00114     //    printset(predicates));
00115 
00116     assert(!!maski);
00117     RegistryPtr reg = maski->getRegistry();
00118     Interpretation::Storage& bits = maski->getStorage();
00119 
00120     unsigned maxaddr = 0;
00121 
00122     OrdinaryAtomTable::AddressIterator it_begin;
00123     {
00124         // get one state of it_end, encoded in maxaddr
00125         // (we must not use it_end, as it is a generic object but denotes
00126         // different endpoints if ogatoms changes during this method)
00127         OrdinaryAtomTable::AddressIterator it_end;
00128         boost::tie(it_begin, it_end) = reg->ogatoms.getAllByAddress();
00129         maxaddr = it_end - it_begin;
00130     }
00131 
00132     boost::mutex::scoped_lock lock(updateMutex);
00133 
00134     // check if we have unknown atoms
00135     //DBGLOG(DBG,"already inspected ogatoms with address < " << knownAddresses <<
00136     //    ", iterator range has size " << maxaddr);
00137     if( maxaddr == knownAddresses )
00138         return;
00139 
00140     // only log real activity
00141     DBGLOG_VSCOPE(DBG,"PM::uM(do)",this,false);
00142     DBGLOG(DBG,"= PredicateMask::updateMask (need to update) for predicates " <<
00143         printset(predicates));
00144 
00145     // if not equal, it must be larger -> we must inspect
00146     assert(maxaddr > knownAddresses);
00147 
00148     // advance iterator to first ogatom unknown to predicateInputMask
00149     OrdinaryAtomTable::AddressIterator it = it_begin;
00150     it += knownAddresses;
00151 
00152     unsigned missingBits = maxaddr - knownAddresses;
00153     DBGLOG(DBG,"need to inspect " << missingBits << " missing bits");
00154 
00155     // check all new ogatoms till the end
00156     #ifndef NDEBUG
00157     {
00158         std::stringstream s;
00159         s << "relevant predicate constants are ";
00160         RawPrinter printer(s, reg);
00161         BOOST_FOREACH(IDAddress addr, predicates) {
00162             printer.print(ID(ID::MAINKIND_TERM | ID::SUBKIND_TERM_CONSTANT, addr));
00163             s << ", ";
00164         }
00165         DBGLOG(DBG,s.str());
00166     }
00167     #endif
00168     assert(knownAddresses == (it - it_begin));
00169     for(;missingBits != 0; it++, missingBits--) {
00170         if (!reg->ogatoms.getIDByAddress(knownAddresses).isHiddenAtom()) {
00171             assert(it != reg->ogatoms.getAllByAddress().second);
00172             const OrdinaryAtom& oatom = *it;
00173             //DBGLOG(DBG,"checking " << oatom.tuple.front());
00174             IDAddress addr = oatom.tuple.front().address;
00175             if( predicates.find(addr) != predicates.end() ) {
00176                 bits.set(it - it_begin);
00177             }
00178         }
00179         knownAddresses++;
00180     }
00181     //  knownAddresses += missingBits;
00182     DBGLOG(DBG,"updateMask created new set of relevant ogatoms: " << *maski << " and knownAddresses is " << knownAddresses);
00183 }
00184 
00185 
00186 ExternalAtomMask::ExternalAtomMask() : PredicateMask(), ctx(0), eatom(0)
00187 {
00188 }
00189 
00190 
00191 ExternalAtomMask::~ExternalAtomMask()
00192 {
00193 }
00194 
00195 
00196 // assumption: this is called once only
00197 // implicit assumption (as groundidb is const& and not stored in the class): groundidb does not change
00198 void ExternalAtomMask::setEAtom(const ProgramCtx& ctx, const ExternalAtom& eatom, const std::vector<ID>& groundidb)
00199 {
00200     assert(this->ctx == 0 && this->eatom == 0 && !this->outputAtoms && "we should never set the eatom twice!");
00201 
00202     LOG_SCOPE(DBG,"sEA",false);
00203     LOG(DBG," = ExternalAtomMask::setEAtom for " << eatom << " and " << groundidb.size() << " rules in groundidb");
00204 
00205     this->eatom = &eatom;
00206     this->ctx = &ctx;
00207     RegistryPtr reg = eatom.pluginAtom->getRegistry();
00208     setRegistry(reg);
00209     outputAtoms.reset(new Interpretation(reg));
00210     auxInputMask.reset(new Interpretation(reg));
00211 
00212     // positive and negative replacement predicates for this eatom
00213     ID posreplacement = reg->getAuxiliaryConstantSymbol('r', eatom.predicate);
00214     ID negreplacement = reg->getAuxiliaryConstantSymbol('n', eatom.predicate);
00215 
00216     // replacement tuple cache
00217     preparedTuple.push_back(posreplacement);
00218     if( eatom.auxInputPredicate != ID_FAIL &&
00219     ctx.config.getOption("IncludeAuxInputInAuxiliaries") ) {
00220         preparedTuple.push_back(eatom.auxInputPredicate);
00221     }
00222 
00223     //
00224     // inputs
00225     //
00226 
00227     // aux input predicate for this eatom
00228     if (eatom.auxInputPredicate != ID_FAIL) {
00229         DBGLOG(DBG, "Adding auxiliary input predicate " << printToString<RawPrinter>(eatom.auxInputPredicate,reg));
00230         addPredicate(eatom.auxInputPredicate);
00231     }
00232 
00233     // predicates of predicate inputs for this eatom
00234     int i = 0;
00235     BOOST_FOREACH (ID p, eatom.inputs) {
00236         preparedTuple.push_back(p);
00237         if (eatom.pluginAtom->getInputType(i) == PluginAtom::PREDICATE) {
00238             DBGLOG(DBG, "Adding input predicate " << printToString<RawPrinter>(p,reg));
00239             addPredicate(p);
00240         }
00241         i++;
00242     }
00243 
00244     //
00245     // outputs
00246     //
00247     preparedTuple.insert(preparedTuple.end(), eatom.tuple.begin(), eatom.tuple.end());
00248     workTuple.assign(preparedTuple.begin(), preparedTuple.end());
00249     DBGLOG(DBG,"preparedTuple is <" << printManyToString<RawPrinter>(preparedTuple, ", ", reg) << ">");
00250 
00251     // find all output atoms (replacement atoms for positive or negative external atoms)
00252     // which possibly belong to this external atom
00253     BOOST_FOREACH (ID rId, groundidb) {
00254         // TODO we could mark PROPERTY_RULE_EXTATOMS for external replacement atoms coming from gringo, then we can rule out all rules without extatoms here by a bit check, perhaps it will not pay off however
00255 
00256         // I think we cannot use a PredicateMask to get these atoms (by simply matching
00257         // posreplacement and negreplacement) because then we get everything in the registry,
00258         // not only those in groundidb, and if we need to count them to ensure we have every
00259         // bit that is relevant (as done for verification of external atoms) this will not
00260         // work anymore.
00261         const Rule& rule = reg->rules.getByID(rId);
00262         BOOST_FOREACH (ID h, rule.head) {
00263             if (h.isExternalAuxiliary()) {
00264                 const OrdinaryAtom& atom = reg->ogatoms.getByID(h);
00265                 if (atom.tuple[0] == posreplacement || atom.tuple[0] == negreplacement) {
00266                     outputAtoms->setFact(h.address);
00267                 }
00268             }
00269         }
00270         BOOST_FOREACH (ID b, rule.body) {
00271             if (b.isExternalAuxiliary()) {
00272                 const OrdinaryAtom& atom = reg->ogatoms.getByID(b);
00273                 if (atom.tuple[0] == posreplacement || atom.tuple[0] == negreplacement) {
00274                     outputAtoms->setFact(b.address);
00275                 }
00276             }
00277         }
00278     }
00279     DBGLOG(DBG, "Watching " << outputAtoms->getStorage().count() << " output atoms: " << *outputAtoms);
00280     addOutputAtoms(outputAtoms);
00281 }
00282 
00283 
00284 void ExternalAtomMask::addOutputAtoms(InterpretationConstPtr intr)
00285 {
00286 
00287     assert(!!eatom);
00288     RegistryPtr reg = ctx->registry();
00289     ID posreplacement = reg->getAuxiliaryConstantSymbol('r', eatom->predicate);
00290     ID negreplacement = reg->getAuxiliaryConstantSymbol('n', eatom->predicate);
00291 
00292     bm::bvector<>::enumerator en = intr->getStorage().first();
00293     bm::bvector<>::enumerator en_end = intr->getStorage().end();
00294     while (en < en_end) {
00295         const IDAddress atom = *en;
00296         ID id = reg->ogatoms.getIDByAddress(atom);
00297         if (id.isExternalAuxiliary()) {
00298             const OrdinaryAtom& oatom = ctx->registry()->ogatoms.getByAddress(atom);
00299             if (oatom.tuple[0] == posreplacement || oatom.tuple[0] == negreplacement) {
00300                 if (matchOutputAtom(oatom.tuple)) {
00301                     DBGLOG(DBG, "Output atom " << oatom.text << " matches the external atom");
00302                     maski->setFact(atom);
00303                 }
00304                 else {
00305                     DBGLOG(DBG, "Output atom " << oatom.text << " does not match the external atom");
00306                 }
00307             }
00308         }
00309         en++;
00310     }
00311 }
00312 
00313 
00314 bool ExternalAtomMask::matchOutputAtom(const Tuple& togatom)
00315 {
00316     assert(eatom);
00317 
00318     //RegistryPtr reg = maski->getRegistry();
00319     //DBGLOG(WARNING,"workTuple=" << printManyToString<RawPrinter>(workTuple, ", ", reg) << " togatom=" << printManyToString<RawPrinter>(togatom, ", ", reg));
00320 
00321     // store togatom into workTuple if possible, otherwise bailout
00322     // then restore workTuple
00323                                  // this must be checked and cannot be asserted because multiple external atoms with the same predicate can have inputs of different sizes
00324     if (workTuple.size() != togatom.size()) return false;
00325     //    assert(workTuple.size() == togatom.size());
00326     assert(workTuple == preparedTuple);
00327     bool ret = true;
00328     for(unsigned idx = 1; idx < togatom.size(); ++idx) {
00329         ID query = togatom[idx];
00330         ID pattern = workTuple[idx];
00331         if( pattern.isVariableTerm() ) {
00332             for(unsigned i = idx; i < togatom.size(); ++i) {
00333                 if( workTuple[i] == pattern )
00334                     workTuple[i] = query;
00335             }
00336         }
00337         else {
00338             assert(pattern.isConstantTerm() || pattern.isIntegerTerm());
00339             if( pattern != query ) {
00340                 ret = false;
00341                 break;
00342             }
00343         }
00344     }
00345 
00346     // restore workTuple
00347     workTuple.assign(preparedTuple.begin(), preparedTuple.end());
00348     return ret;
00349 
00350     #if 0
00351     #ifndef NDEBUG
00352     std::stringstream ss;
00353     ss << "Comparing togatom tuple (";
00354     for (int i = 0; i < togatom.size(); ++i) {
00355         ss << (i > 0 ? ", " : "");
00356         if (togatom[i].isIntegerTerm()) {
00357             ss << togatom[i].address;
00358         }
00359         else {
00360             ss << eatom->pluginAtom->getRegistry()->terms.getByID(togatom[i]).symbol;
00361         }
00362     }
00363     ss << ") to external atom " << eatom->pluginAtom->getRegistry()->terms.getByID(eatom->predicate).symbol << " (input: ";
00364     for (int i = 0; i < eatom->inputs.size(); ++i) {
00365         ss << (i > 0 ? ", " : "");
00366         if (eatom->inputs[i].isIntegerTerm()) {
00367             ss << eatom->inputs[i].address;
00368         }
00369         else {
00370             ss << eatom->pluginAtom->getRegistry()->terms.getByID(eatom->inputs[i]).symbol;
00371         }
00372     }
00373     ss << "; output: ";
00374     for (int i = 0; i < eatom->tuple.size(); ++i) {
00375         ss << (i > 0 ? ", " : "");
00376         if (eatom->tuple[i].isIntegerTerm()) {
00377             ss << eatom->tuple[i].address;
00378         }
00379         else {
00380             ss << eatom->pluginAtom->getRegistry()->terms.getByID(eatom->tuple[i]).symbol;
00381         }
00382     }
00383     ss << ")";
00384     DBGLOG(DBG, ss.str());
00385     #endif
00386     #endif
00387 
00388     #if 0
00389     // check predicate and constant input
00390     int aux = 0;
00391     if (ctx->config.getOption("IncludeAuxInputInAuxiliaries") && eatom->auxInputPredicate != ID_FAIL) {
00392         if (togatom[1] != eatom->auxInputPredicate) return false;
00393         aux = 1;
00394     }
00395     for (unsigned p = 0; p < eatom->inputs.size(); ++p) {
00396         if (eatom->pluginAtom->getInputType(p) == PluginAtom::PREDICATE ||
00397         (eatom->pluginAtom->getInputType(p) == PluginAtom::CONSTANT && !eatom->inputs[p].isVariableTerm())) {
00398             if (togatom[p + aux + 1] != eatom->inputs[p]) {
00399                 //DBGLOG(DBG, "Predicate or constant input mismatch");
00400                 // do not assign variable already here, because we later check against input tuples, this is a redundant fast check to eliminate mismatches in constants and predicates
00401                 return false;
00402             }
00403         }
00404     }
00405 
00406     // remember variable binding
00407     // using map here is not too evil as we have a maximum of eatom->inputs.size() elements in map -> very fast, probably faster than hashtable
00408     typedef std::map<ID, ID> VBMap;
00409     VBMap varBinding;
00410 
00411     // check auxiliary input
00412     bool inputmatch = false;
00413     if (eatom->auxInputPredicate == ID_FAIL) {
00414         inputmatch = true;
00415     }
00416     else {
00417         bm::bvector<>::enumerator en = auxInputMask->getStorage().first();
00418         bm::bvector<>::enumerator en_end = auxInputMask->getStorage().end();
00419         for(; en < en_end; ++en) {
00420             const OrdinaryAtom& tinp = reg->ogatoms.getByAddress(*en);
00421             // check if tinp corresponds to togatom
00422             bool match = true;
00423             for (unsigned i = 1; i < tinp.tuple.size(); ++i) {
00424                 for(std::list<unsigned>::const_iterator it = eatom->auxInputMapping[i-1].begin();
00425                 it != eatom->auxInputMapping[i-1].end(); ++it) {
00426                     const unsigned& pos = *it;
00427                     if (togatom[aux + 1 + pos] != tinp.tuple[i]) {
00428                         match = false;
00429                         break;
00430                     }
00431                     // remember matched variables
00432                     varBinding[eatom->inputs[pos]] = tinp.tuple[i];
00433                 }
00434                 if (!match) break;
00435             }
00436             if (match) {
00437                 inputmatch = true;
00438                 break;
00439             }
00440         }
00441         if (!inputmatch) {
00442             //DBGLOG(DBG, "Auxiliary input mismatch");
00443         }
00444     }
00445     if (!inputmatch) return false;
00446 
00447     // check output tuple
00448     for (unsigned o = 0; o < eatom->tuple.size(); ++o) {
00449         if (eatom->tuple[o].isVariableTerm()) {
00450             VBMap::iterator vbit = varBinding.find(eatom->tuple[o]);
00451             if (vbit == varBinding.end()) {
00452                 vbit->second = togatom[aux + 1 + eatom->inputs.size() + o];
00453             }
00454             else {
00455                 if (vbit->second != togatom[aux + 1 + eatom->inputs.size() + o]) {
00456                     return false;
00457                 }
00458             }
00459         }
00460         else if (eatom->tuple[o].isConstantTerm() || eatom->tuple[o].isIntegerTerm()) {
00461             if (togatom[aux + 1 + eatom->inputs.size() + o] != eatom->tuple[o]) {
00462                 return false;
00463             }
00464         }
00465         else {
00466             assert(false);
00467         }
00468     }
00469     return true;
00470     #endif
00471 }
00472 
00473 
00474 // this method ensures that the mask captures:
00475 //  * predicate input of the external atom
00476 //  * auxiliary input to the external atom
00477 //  * all ground output atoms (replacements) of the external atom
00478 //      this is the set of all ogatoms which use the (positive or negative) auxiliary of this external atom
00479 //      and where the input list matches
00480 //
00481 // the update strategy is as follows:
00482 // 1. update as usual
00483 // 2. if an auxiliary input atom was added, consider all ogatoms over the positive or negative replacement as new
00484 // 3. for all new ogatoms over the positive or negative replacement, check if the input list matches (if not: remove the atom from the mask)
00485 void ExternalAtomMask::updateMask()
00486 {
00487     assert(eatom);
00488     DBGLOG(DBG, "ExternalAtomMask::updateMask");
00489 
00490     // remember what changed
00491     // FIXME we can perhaps make this change computation faster and reduce reallocations using some nice bitmagic operators
00492     InterpretationPtr change = InterpretationPtr(new Interpretation(eatom->pluginAtom->getRegistry()));
00493     change->getStorage() |= maski->getStorage();
00494     PredicateMask::updateMask();
00495     change->getStorage() ^= maski->getStorage();
00496     boost::mutex::scoped_lock lock(updateMutex);
00497 
00498     if (change->getStorage().count() == 0) return;
00499 
00500     // check if an atom over the auxiliary input predicate was added
00501     // (this is not done as a PredicateMask because it alread gets the delta in change)
00502     bool auxAdded = false;
00503     bm::bvector<>::enumerator en = change->getStorage().first();
00504     bm::bvector<>::enumerator en_end = change->getStorage().end();
00505     while (en < en_end) {
00506         const OrdinaryAtom& oatom = eatom->pluginAtom->getRegistry()->ogatoms.getByAddress(*en);
00507         if (oatom.tuple[0] == eatom->auxInputPredicate) {
00508             auxInputMask->setFact(*en);
00509             auxAdded = true;
00510         }
00511         en++;
00512     }
00513 
00514     // if an auxiliary input atom was added, we have to recheck all output atoms
00515     if (auxAdded) {
00516         DBGLOG(DBG, "Auxiliary input changed");
00517         // recheck all output atoms
00518         bm::bvector<>::enumerator en = outputAtoms->getStorage().first();
00519         bm::bvector<>::enumerator en_end = outputAtoms->getStorage().end();
00520         while (en < en_end) {
00521             const IDAddress outputAtom = *en;
00522             const OrdinaryAtom& oatom = eatom->pluginAtom->getRegistry()->ogatoms.getByAddress(outputAtom);
00523             if (matchOutputAtom(oatom.tuple)) {
00524                 DBGLOG(DBG, "Output atom " << oatom.text << " matches the external atom");
00525                 maski->setFact(outputAtom);
00526             }
00527             else {
00528                 DBGLOG(DBG, "Output atom " << oatom.text << " does not match the external atom");
00529             }
00530             en++;
00531         }
00532     }
00533 }
00534 
00535 
00536 const InterpretationConstPtr ExternalAtomMask::getAuxInputMask() const
00537 {
00538     assert (!!auxInputMask && "auxInputMask not set");
00539     return auxInputMask;
00540 }
00541 
00542 
00543 DLVHEX_NAMESPACE_END
00544 
00545 
00546 // vim:expandtab:ts=4:sw=4:
00547 // mode: C++
00548 // End: