dlvhex  2.5.0
src/ExternalLearningHelper.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/ExternalLearningHelper.h"
00040 #include "dlvhex2/HexParser.h"
00041 #include "dlvhex2/Printer.h"
00042 #include "dlvhex2/InternalGrounder.h"
00043 #include "dlvhex2/Benchmarking.h"
00044 
00045 #include <boost/lexical_cast.hpp>
00046 #include <boost/algorithm/string/predicate.hpp>
00047 #include <boost/functional/hash.hpp>
00048 
00049 #include <unordered_set>
00050 
00051 #include <fstream>
00052 
00053 DLVHEX_NAMESPACE_BEGIN
00054 
00055 ExternalLearningHelper::DefaultInputNogoodProvider::DefaultInputNogoodProvider(bool negateMonotonicity) : negateMonotonicity(negateMonotonicity)
00056 {
00057 }
00058 
00059 
00060 bool ExternalLearningHelper::DefaultInputNogoodProvider::dependsOnOutputTuple() const
00061 {
00062     return false;
00063 }
00064 
00065 
00066 Nogood ExternalLearningHelper::DefaultInputNogoodProvider::operator()(const PluginAtom::Query& query, const ExtSourceProperties& prop, bool contained, const Tuple tuple, int* weakenedPremiseLiterals) const{
00067 
00068     DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(inputprovider, "InpNogoodProvider::operator()");
00069 
00070     // store for each predicate term ID the index of the corresponding parameter in input
00071     std::map<ID, int> inputPredicateTable;
00072     int index = 0;
00073     BOOST_FOREACH (ID inp, query.input) {
00074         inputPredicateTable[inp] = index++;
00075     }
00076 
00077     // find relevant input: by default, the predicate mask of the external source counts; this can however be overridden for queries
00078     bm::bvector<>::enumerator en = query.predicateInputMask == InterpretationPtr() ? query.ctx->registry()->eatoms.getByID(query.eatomID).getPredicateInputMask()->getStorage().first() : query.predicateInputMask->getStorage().first();
00079     bm::bvector<>::enumerator en_end = query.predicateInputMask == InterpretationPtr() ? query.ctx->registry()->eatoms.getByID(query.eatomID).getPredicateInputMask()->getStorage().end() : query.predicateInputMask->getStorage().end();
00080     Nogood extNgInput;
00081 
00082     while (en < en_end) {
00083         // get the predicate of the current input atom
00084         ID pred = query.interpretation->getRegistry()->ogatoms.getByID(ID(ID::MAINKIND_ATOM | ID::SUBKIND_ATOM_ORDINARYG, *en)).tuple[0];
00085 
00086         // find the parameter index of this atom
00087         int index = inputPredicateTable.find(pred)->second;
00088 
00089         // positive atoms are only required for non-antimonotonic input parameters
00090         // negative atoms are only required for non-monotonic input parameters
00091         // unassigned input atoms are not needed if the external source provides partial answers (i.e., works over partial interpretations)
00092         if (!prop.doesProvidePartialAnswer() || !query.assigned || query.assigned->getFact(*en)){
00093             if (query.interpretation->getFact(*en) != negateMonotonicity) {
00094                 // positive
00095                 if (!prop.isAntimonotonic(index) || !query.ctx->config.getOption("ExternalLearningMonotonicity")) {
00096                     extNgInput.insert(NogoodContainer::createLiteral(*en, query.interpretation->getFact(*en)));
00097                 }
00098             }
00099             else {
00100                 // negative
00101                 if (!prop.isMonotonic(index) || !query.ctx->config.getOption("ExternalLearningMonotonicity")) {
00102                     extNgInput.insert(NogoodContainer::createLiteral(*en, query.interpretation->getFact(*en)));
00103                 }
00104             }
00105         }else{
00106             if (!!query.assigned && !query.assigned->getFact(*en)){
00107                 if (weakenedPremiseLiterals != 0) {
00108                     DLVHEX_BENCHMARK_REGISTER_AND_COUNT(sidweakenednumber, "Weakened EA-nogood premises", 1);
00109                     *weakenedPremiseLiterals = *weakenedPremiseLiterals + 1;
00110                 }
00111             }
00112         }
00113         en++;
00114     }
00115     DBGLOG(DBG, "Input nogood: " << extNgInput.getStringRepresentation(query.ctx->registry()));
00116     return extNgInput;
00117 }
00118 
00119 
00120 Set<ID> ExternalLearningHelper::getOutputAtoms(const PluginAtom::Query& query, const PluginAtom::Answer& answer, bool sign)
00121 {
00122 
00123     Set<ID> out;
00124 
00125     // construct replacement atom
00126     OrdinaryAtom replacement(ID::MAINKIND_ATOM | ID::SUBKIND_ATOM_ORDINARYG | ID::PROPERTY_AUX | ID::PROPERTY_EXTERNALAUX);
00127     replacement.tuple.resize(1);
00128     replacement.tuple[0] = query.ctx->registry()->getAuxiliaryConstantSymbol(sign ? 'r' : 'n', query.ctx->registry()->eatoms.getByID(query.eatomID).predicate);
00129 
00130     if (query.ctx->config.getOption("IncludeAuxInputInAuxiliaries") && query.ctx->registry()->eatoms.getByID(query.eatomID).auxInputPredicate != ID_FAIL) {
00131         //      replacement.tuple.push_back(query.ctx->registry()->storeVariableTerm("X"));
00132         //      replacement.kind |= ID::SUBKIND_ATOM_ORDINARYN;
00133         replacement.tuple.push_back(query.ctx->registry()->eatoms.getByID(query.eatomID).auxInputPredicate);
00134     }
00135     replacement.tuple.insert(replacement.tuple.end(), query.input.begin(), query.input.end());
00136     int s = replacement.tuple.size();
00137 
00138     const std::vector<Tuple>& otuples = answer.get();
00139     BOOST_FOREACH (Tuple otuple, otuples) {
00140         replacement.tuple.resize(s);
00141         // add current output
00142         replacement.tuple.insert(replacement.tuple.end(), otuple.begin(), otuple.end());
00143         // get ID of this replacement atom
00144         ID idreplacement = NogoodContainer::createLiteral(query.ctx->registry()->storeOrdinaryAtom(replacement));
00145         out.insert(idreplacement);
00146     }
00147 
00148     return out;
00149 }
00150 
00151 
00152 ID ExternalLearningHelper::getOutputAtom(const PluginAtom::Query& query, Tuple otuple, bool sign)
00153 {
00154 
00155     bool ground = true;
00156     BOOST_FOREACH (ID o, otuple) if (o.isVariableTerm()) ground = false;
00157 
00158     // construct replacement atom with input from query
00159     OrdinaryAtom replacement(ID::MAINKIND_ATOM | ID::PROPERTY_AUX | ID::PROPERTY_EXTERNALAUX);
00160     if (ground) replacement.kind |= ID::SUBKIND_ATOM_ORDINARYG;
00161     else replacement.kind |= ID::SUBKIND_ATOM_ORDINARYN;
00162     replacement.tuple.resize(1);
00163     replacement.tuple[0] = query.ctx->registry()->getAuxiliaryConstantSymbol(sign ? 'r' : 'n', query.ctx->registry()->eatoms.getByID(query.eatomID).predicate);
00164     if (query.ctx->config.getOption("IncludeAuxInputInAuxiliaries") && query.ctx->registry()->eatoms.getByID(query.eatomID).auxInputPredicate != ID_FAIL) {
00165         //      replacement.tuple.push_back(query.ctx->registry()->storeVariableTerm("X"));
00166         //      replacement.kind |= ID::SUBKIND_ATOM_ORDINARYN;
00167         replacement.tuple.push_back(query.ctx->registry()->eatoms.getByID(query.eatomID).auxInputPredicate);
00168     }
00169     replacement.tuple.insert(replacement.tuple.end(), query.input.begin(), query.input.end());
00170     int s = replacement.tuple.size();
00171 
00172     // add output tuple
00173     replacement.tuple.insert(replacement.tuple.end(), otuple.begin(), otuple.end());
00174 
00175     ID idreplacement = NogoodContainer::createLiteral(query.ctx->registry()->storeOrdinaryAtom(replacement));
00176     return idreplacement;
00177 }
00178 
00179 
00180 #if 0
00181 ID ExternalLearningHelper::getOutputAtom(const PluginAtom::Query& query, const Tuple& ituple, const Tuple& otuple, bool sign)
00182 {
00183 
00184     bool ground = true;
00185     BOOST_FOREACH (ID i, ituple) if (i.isVariableTerm()) ground = false;
00186     BOOST_FOREACH (ID o, otuple) if (o.isVariableTerm()) ground = false;
00187 
00188     // construct replacement atom with input from query
00189     OrdinaryAtom replacement(ID::MAINKIND_ATOM | ID::PROPERTY_AUX | ID::PROPERTY_EXTERNALAUX);
00190     if (ground) replacement.kind |= ID::SUBKIND_ATOM_ORDINARYG;
00191     else replacement.kind |= ID::SUBKIND_ATOM_ORDINARYN;
00192     replacement.tuple.resize(1);
00193     replacement.tuple[0] = query.ctx->registry()->getAuxiliaryConstantSymbol(sign ? 'r' : 'n', query.ctx->registry()->eatoms.getByID(query.eatomID).predicate);
00194     if (query.ctx->config.getOption("IncludeAuxInputInAuxiliaries") && query.ctx->registry()->eatoms.getByID(query.eatomID).auxInputPredicate != ID_FAIL) {
00195         //      replacement.tuple.push_back(query.ctx->registry()->storeVariableTerm("X"));
00196         //      replacement.kind |= ID::SUBKIND_ATOM_ORDINARYN;
00197         replacement.tuple.push_back(query.ctx->registry()->eatoms.getByID(query.eatomID).auxInputPredicate);
00198     }
00199     replacement.tuple.insert(replacement.tuple.end(), ituple.begin(), ituple.end());
00200     int s = replacement.tuple.size();
00201 
00202     // add output tuple
00203     replacement.tuple.insert(replacement.tuple.end(), otuple.begin(), otuple.end());
00204 
00205     ID idreplacement = NogoodContainer::createLiteral(query.ctx->registry()->storeOrdinaryAtom(replacement));
00206     return idreplacement;
00207 }
00208 #endif
00209 
00210 ID ExternalLearningHelper::getIDOfLearningRule(ProgramCtx* ctx, std::string learningrule)
00211 {
00212 
00213     RegistryPtr reg = ctx->registry();
00214 
00215     // parse rule
00216     DBGLOG(DBG, "Parsing learning rule " << learningrule);
00217     InputProviderPtr ip(new InputProvider());
00218     ip->addStringInput(learningrule, "rule");
00219     ProgramCtx pc = *ctx;
00220     pc.edb = InterpretationPtr(new Interpretation(ctx->registry()));
00221     pc.idb.clear();
00222     ModuleHexParser hp;
00223     hp.parse(ip, pc);
00224 
00225     if(pc.edb->getStorage().count() > 0) {
00226         DBGLOG(DBG, "Learning Rule Error: Learning rule must not be a fact");
00227         return ID_FAIL;
00228     }
00229     else if (pc.idb.size() != 1) {
00230         DBGLOG(DBG, "Error: Got " << pc.idb.size() << " rules; must be 1");
00231         return ID_FAIL;
00232     }
00233     else {
00234         DBGLOG(DBG, "Got 1 learning rule");
00235         ID rid = pc.idb[0];
00236         const Rule& r = reg->rules.getByID(rid);
00237 
00238         // learning rules must not be constraints or disjunctive
00239         if (r.head.size() != 1) {
00240             DBGLOG(DBG, "Learning Rule Error: Learning rule is not ordinary (head size must be 1)");
00241             return ID_FAIL;
00242         }
00243 
00244         // learning rules must use only predicates "out" or "nout" (in head) and in[i] (in body)
00245         BOOST_FOREACH (ID hLit, r.head) {
00246             const OrdinaryAtom& oatom = hLit.isOrdinaryGroundAtom() ? reg->ogatoms.getByID(hLit) : reg->onatoms.getByID(hLit);
00247             std::string hPred = reg->terms.getByID(oatom.tuple[0]).getUnquotedString();
00248             if (hPred != "out" && hPred != "nout") {
00249                 DBGLOG(DBG, "Learning Rule Error: Head predicate of learning rule must be \"out\" or \"nout\"");
00250                 return ID_FAIL;
00251             }
00252         }
00253         BOOST_FOREACH (ID bLit, r.body) {
00254             const OrdinaryAtom& oatom = bLit.isOrdinaryGroundAtom() ? reg->ogatoms.getByID(bLit) : reg->onatoms.getByID(bLit);
00255             std::string bPred = reg->terms.getByID(oatom.tuple[0]).getUnquotedString();
00256 
00257             try
00258             {
00259                 if (boost::starts_with(bPred, "in")) {
00260                     boost::lexical_cast<int>(bPred.c_str() + 2);
00261                 }
00262                 else {
00263                     throw std::bad_cast();
00264                 }
00265             }
00266             catch (std::bad_cast) {
00267                 DBGLOG(DBG, "Learning Rule Error: Body predicates must be of kind \"in[integer]\"");
00268                 return ID_FAIL;
00269             }
00270         }
00271 
00272         return rid;
00273     }
00274 }
00275 
00276 
00277 void ExternalLearningHelper::learnFromInputOutputBehavior(const PluginAtom::Query& query, const PluginAtom::Answer& answer, const ExtSourceProperties& prop, NogoodContainerPtr nogoods, InputNogoodProviderConstPtr inp)
00278 {
00279     if (nogoods) {
00280         DBGLOG(DBG, "External Learning: IOBehavior" << (query.ctx->config.getOption("ExternalLearningMonotonicity") ? " by exploiting monotonicity" : ""));
00281         
00282         // containers for storing nogoods that still have to be minimized 
00283         SimpleNogoodContainer newNogoodsContainer;
00284         std::vector<std::pair<Nogood,ID>> newNogoods;
00285         
00286         Nogood extNgInput;
00287         int weakenedPremiseLiterals = 0;
00288         if (!inp->dependsOnOutputTuple()) extNgInput = (*inp)(query, prop, true, Tuple(), &weakenedPremiseLiterals);
00289         Set<ID> out = ExternalLearningHelper::getOutputAtoms(query, answer, false);
00290         BOOST_FOREACH (ID oid, out) {
00291             int weakenedPremiseLiterals2 = 0;
00292             Nogood extNg = !inp->dependsOnOutputTuple()
00293                 ? extNgInput
00294                 : (*inp)(query, prop, true, query.ctx->registry()->ogatoms.getByID(oid).tuple, &weakenedPremiseLiterals2);
00295             weakenedPremiseLiterals += weakenedPremiseLiterals2;
00296 
00297             extNg.insert(oid);
00298             DBGLOG(DBG, "Learned nogood " << extNg.getStringRepresentation(query.ctx->registry()) << " from input-output behavior");
00299 
00300             DLVHEX_BENCHMARK_REGISTER_AND_COUNT(sidweakenednumber, "EA-Nogoods from weakened intr.", (weakenedPremiseLiterals > 0 ? 1 : 0));
00301             
00302             if (query.ctx->config.getOption("MinimizeNogoods") && !query.ctx->config.getOption("MinimizeNogoodsOpt") && !inp->dependsOnOutputTuple() && prop.doesProvidePartialAnswer()) {
00303                 // if nogoods should be minimized store them in intermediary container
00304                 DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(sidmin, "Nogood minimization");
00305                 newNogoodsContainer.addNogood(extNg);
00306             } else if (query.ctx->config.getOption("MinimizeNogoods") && query.ctx->config.getOption("MinimizeNogoodsOpt") && !inp->dependsOnOutputTuple() && prop.doesProvidePartialAnswer()) {
00307                 DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(sidmin, "Nogood minimization");
00308                 // if answers w.r.t. the inputs should be cached, input and output atoms have to be stored separately
00309                 std::pair<Nogood,ID> newNogood(extNgInput,oid);
00310                 newNogoods.push_back(newNogood);
00311             } else {
00312                 nogoods->addNogood(extNg);
00313             }
00314         }
00315 
00316         // nogood minimization without caching answers of external atom
00317         if (query.ctx->config.getOption("MinimizeNogoods") && !query.ctx->config.getOption("MinimizeNogoodsOpt") && !inp->dependsOnOutputTuple() && prop.doesProvidePartialAnswer()) {
00318             DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(sidmin, "Nogood minimization");
00319             // iterate through all newly added nogoods
00320             for (int i = 0; i < newNogoodsContainer.getNogoodCount(); ++i) {
00321                 if (newNogoodsContainer.getNogood(i).size() <= query.ctx->config.getOption("MinimizationSize")) {
00322                     // copy the respective nogood
00323                     Nogood testNg = newNogoodsContainer.getNogood(i);
00324                     // store the ID of answer atom that should still be contained in answer after minimization
00325                     ID ansID;
00326 
00327                     BOOST_FOREACH (ID& iid, newNogoodsContainer.getNogood(i)) {
00328                         if (query.ctx->registry()->ogatoms.getIDByAddress(iid.address).isExternalAuxiliary()) {
00329                             ansID = iid;
00330                         }
00331                     }
00332                     
00333                     if (query.interpretation->getFact(ansID.address) || !query.ctx->config.getOption("MinimizeNogoodsOnConflict")) {
00334                         DBGLOG(DBG, "Conflicting nogood");
00335                         
00336                         testNg.erase(ansID);
00337 
00338                         InterpretationPtr interpretation(new Interpretation(query.interpretation->getRegistry()));
00339                         InterpretationPtr assigned(new Interpretation(query.interpretation->getRegistry()));
00340 
00341                         // only add true atoms from nogood to the query interpretation
00342                         set_iterator<ID> it = testNg.begin();
00343                         while (it != testNg.end()) {
00344                             if (!it->isNaf()) {
00345                                 interpretation->setFact(it->address);
00346                             }
00347                             assigned->setFact(it->address);
00348                             it++;
00349                         }
00350 
00351                         PluginAtom::Query qa = query;
00352                         qa.interpretation = interpretation;
00353                         qa.assigned = assigned;
00354 
00355                         // iteratively remove each literal from nogood
00356                         BOOST_FOREACH (ID& iid, newNogoodsContainer.getNogood(i)) {
00357                             // only for non-auxiliaries
00358                             if (iid != ansID) {
00359                                 PluginAtom::Answer ans;
00360 
00361                                 if (!iid.isNaf()) {
00362                                     interpretation->clearFact(iid.address);
00363                                 }
00364                                 assigned->clearFact(iid.address);
00365 
00366                                 // query
00367                                 //if (query.ctx->config.getOption("UseExtAtomCache")) query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieveCached(qa, ans, NogoodContainerPtr());
00368                 //else query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieve(qa, ans, NogoodContainerPtr());
00369                                 query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieveFacade(qa, ans, NogoodContainerPtr(), query.ctx->config.getOption("UseExtAtomCache"));
00370 
00371                                 // get all answer atoms
00372                                 Set<ID> ansout = ExternalLearningHelper::getOutputAtoms(qa, ans, false);
00373                                 // and check if expected answer is still contained
00374                                 if(!ansout.contains(ansID)) {
00375                                     // if it isn't, add the atom to the nogood again
00376                                     if (!iid.isNaf()) {
00377                                         interpretation->setFact(iid.address);
00378                                     }
00379                                     assigned->setFact(iid.address);
00380                                 } else {
00381                                     testNg.erase(iid);
00382                                 }
00383                             }
00384                         }
00385                         testNg.insert(ansID);
00386                     }
00387                     
00388                     // add minimized nogood
00389                     nogoods->addNogood(testNg);
00390                     DBGLOG(DBG, "Learned minimized nogood " << testNg.getStringRepresentation(query.ctx->registry()) << " from input-output behavior");
00391                 } else {
00392                     nogoods->addNogood(newNogoodsContainer.getNogood(i));
00393                 }
00394             }
00395         }
00396 
00397 
00398         // nogood minimization with caching answers of external atom:
00399     if (query.ctx->config.getOption("MinimizeNogoods") && query.ctx->config.getOption("MinimizeNogoodsOpt") && !inp->dependsOnOutputTuple() && prop.doesProvidePartialAnswer()) {
00400             DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(sidmin, "Nogood minimization");
00401             BOOST_FOREACH (ID& iid, extNgInput) {
00402                 // cache for answers of external atom
00403                 std::map<std::size_t, PluginAtom::Answer> externalEvaluationsCache;
00404 
00405                 for (int i = 0; i < newNogoods.size(); ++i) {
00406                     if ((query.interpretation->getFact(newNogoods[i].second.address) || !query.ctx->config.getOption("MinimizeNogoodsOnConflict"))
00407                             && (newNogoods[i].first.size() <= query.ctx->config.getOption("MinimizationSize"))) {
00408                         Nogood testNg = newNogoods[i].first;
00409 
00410                         testNg.erase(iid);
00411 
00412                         PluginAtom::Answer ans;
00413                         PluginAtom::Query qa = query;
00414 
00415                         if (externalEvaluationsCache.find(testNg.getHash()) != externalEvaluationsCache.end()) {
00416                             ans = externalEvaluationsCache[testNg.getHash()];
00417                         } else {
00418                             InterpretationPtr interpretation(new Interpretation(query.interpretation->getRegistry()));
00419                             InterpretationPtr assigned(new Interpretation(query.interpretation->getRegistry()));
00420 
00421                             // only add true atoms from nogood to the query interpretation
00422                             set_iterator<ID> it = testNg.begin();
00423                             while (it != testNg.end()) {
00424                                 if (!it->isNaf()) {
00425                                     interpretation->setFact(it->address);
00426                                 }
00427                                 assigned->setFact(it->address);
00428                                 it++;
00429                             }
00430 
00431                             qa.interpretation = interpretation;
00432                             qa.assigned = assigned;
00433 
00434                             // query
00435                             //if (query.ctx->config.getOption("UseExtAtomCache")) query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieveCached(qa, ans, NogoodContainerPtr());
00436                             //else query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieve(qa, ans, NogoodContainerPtr());
00437                             query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieveFacade(qa, ans, NogoodContainerPtr(), query.ctx->config.getOption("UseExtAtomCache"));
00438 
00439                             externalEvaluationsCache[testNg.getHash()] = ans;
00440                         }
00441                         // get all answer atoms
00442                         Set<ID> ansout = ExternalLearningHelper::getOutputAtoms(qa, ans, false);
00443                         // and check if expected answer is still contained
00444                         if(ansout.contains(newNogoods[i].second)) {
00445                             newNogoods[i].first = testNg;
00446                         }
00447                     }
00448                 }
00449             }
00450 
00451             for (int i = 0; i < newNogoods.size(); ++i) {
00452                 Nogood newNg = newNogoods[i].first;
00453                 newNg.insert(newNogoods[i].second);
00454                 DBGLOG(DBG, "Learned minimized nogood " << newNg.getStringRepresentation(query.ctx->registry()) << " from input-output behavior");
00455                 nogoods->addNogood(newNg);
00456             }
00457         }
00458     }
00459 }
00460 
00461 
00462 void ExternalLearningHelper::learnFromFunctionality(const PluginAtom::Query& query, const PluginAtom::Answer& answer, const ExtSourceProperties& prop, std::vector<Tuple>& recordedTuples, NogoodContainerPtr nogoods)
00463 {
00464 
00465     if (nogoods) {
00466         DBGLOG(DBG, "External Learning: Functionality");
00467 
00468         // there is a unique output
00469         const std::vector<Tuple>& otuples = answer.get();
00470 
00471         if (otuples.size() > 0) {
00472             ID uniqueOut = ExternalLearningHelper::getOutputAtom(query, otuples[0], true);
00473 
00474             // go through all output tuples which have been generated so far
00475             BOOST_FOREACH (Tuple t, recordedTuples) {
00476                 // compare the non-functional prefix
00477                 bool match = true;
00478                 for (int i = 0; i < prop.functionalStart; ++i) {
00479                     if (otuples[0][i] != t[i]) {
00480                         match = false;
00481                         break;
00482                     }
00483                 }
00484                 if (!match) continue;
00485 
00486                 ID id = ExternalLearningHelper::getOutputAtom(query, t, true);
00487                 if (id != uniqueOut) {
00488                     Nogood excludeOthers;
00489                     excludeOthers.insert(uniqueOut);
00490                     excludeOthers.insert(id);
00491                     DBGLOG(DBG, "Learned nogood " << excludeOthers.getStringRepresentation(query.ctx->registry()) << " from functionality");
00492                     nogoods->addNogood(excludeOthers);
00493                 }
00494             }
00495 
00496             // remember that otuples[0] was generated
00497             recordedTuples.push_back(otuples[0]);
00498         }
00499     }
00500 }
00501 
00502 
00503 void ExternalLearningHelper::learnFromNegativeAtoms(const PluginAtom::Query& query, const PluginAtom::Answer& answer, const ExtSourceProperties& prop, NogoodContainerPtr nogoods, InputNogoodProviderConstPtr inp)
00504 {
00505     // learning of negative information
00506     if (nogoods) {
00507         // transform output into set for faster lookup
00508          struct TupleHash {
00509              std::size_t operator() (const Tuple& t) const {
00510                  std::size_t seed = 0;
00511                  boost::hash_combine(seed, t);
00512                  return seed;
00513              }
00514         };
00515         Nogood extNgInput;
00516         int weakenedPremiseLiterals = 0;
00517         if (!inp->dependsOnOutputTuple()) extNgInput = (*inp)(query, prop, false, Tuple(), &weakenedPremiseLiterals);
00518 
00519         if (weakenedPremiseLiterals > 0){
00520             DLVHEX_BENCHMARK_REGISTER_AND_COUNT(sidweakenedpositive, "Positive gr.inst. after weakened EA-eval", answer.get().size());
00521             DLVHEX_BENCHMARK_REGISTER_AND_COUNT(sidweakenedunknown, "Unknown gr.inst. after weakened EA-eval", answer.getUnknown().size());
00522         }
00523 
00524         // containers for storing nogoods that still have to be minimized 
00525         SimpleNogoodContainer newNogoodsContainer;
00526         std::map<ID, Tuple> externalAuxiliaryTable;
00527         std::vector<std::pair<Nogood,ID> > newNogoods;
00528 
00529         // iterate over negative output atoms
00530         bm::bvector<>::enumerator en = query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->getReplacements()->mask()->getStorage().first();
00531         bm::bvector<>::enumerator en_end = query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->getReplacements()->mask()->getStorage().end();
00532         ID negOutPredicate = query.ctx->registry()->getAuxiliaryConstantSymbol('n', query.ctx->registry()->eatoms.getByID(query.eatomID).predicate);
00533         ID posOutPredicate = query.ctx->registry()->getAuxiliaryConstantSymbol('r', query.ctx->registry()->eatoms.getByID(query.eatomID).predicate);
00534 
00535         std::unordered_set<Tuple, TupleHash> toutput, tunknown;
00536         BOOST_FOREACH (Tuple t, answer.get()) toutput.insert(t);
00537         BOOST_FOREACH (Tuple t, answer.getUnknown()) tunknown.insert(t);
00538 
00539         while (en < en_end) {
00540             const OrdinaryAtom& atom = query.ctx->registry()->ogatoms.getByAddress(*en);
00541             if (atom.tuple[0] == negOutPredicate || atom.tuple[0] == posOutPredicate) {
00542                 bool paramMatch = true;
00543 
00544                 // compare auxiliary predicate input
00545                 int aux = 0;
00546                 if (query.ctx->config.getOption("IncludeAuxInputInAuxiliaries")) {
00547                     if (query.ctx->registry()->eatoms.getByID(query.eatomID).auxInputPredicate != ID_FAIL) {
00548                         aux = 1;
00549                         if (atom.tuple[1] != query.ctx->registry()->eatoms.getByID(query.eatomID).auxInputPredicate) paramMatch = false;
00550                     }
00551                 }
00552 
00553                 // compare other inputs
00554                 for (uint32_t i = 0; i < query.input.size(); i++) {
00555                     if (atom.tuple[aux + 1 + i] != query.input[i]) {
00556                         paramMatch = false;
00557                         break;
00558                     }
00559                 }
00560 
00561                 // compare arity: total number of input and output elements in the replacement atom is (atom.tuple.size() - 1 - aux)
00562                 if ((atom.tuple.size() - 1 - aux) != query.input.size() + query.pattern.size()) paramMatch = false;
00563 
00564                 if (paramMatch) {
00565                     // check if this tuple is _not_ in the answer (if the external atom provides partial answers, it also must not be in the unknown list)
00566                     Tuple t;
00567                     for (uint32_t i = aux + 1 + query.input.size(); i < atom.tuple.size(); i++) {
00568                         t.push_back(atom.tuple[i]);
00569                     }
00570 
00571 #ifndef NDEBUG
00572                     DBGLOG(DBG, "Output of external atom:");
00573                     BOOST_FOREACH (Tuple t, answer.get()){
00574                         DBGLOG(DBG, "+" << printManyToString<RawPrinter>(t, ",",  query.ctx->registry()));
00575                     }
00576                     BOOST_FOREACH (Tuple t, answer.getUnknown()){
00577                         DBGLOG(DBG, "~" << printManyToString<RawPrinter>(t, ",",  query.ctx->registry()));
00578                     }
00579 #endif
00580 
00581                 if (weakenedPremiseLiterals > 0){ DLVHEX_BENCHMARK_REGISTER_AND_COUNT(sidweakenedpositive, "Total gr.inst. after weakened EA-eval", 1); }
00582                 if (weakenedPremiseLiterals > 0 && toutput.find(t) == toutput.end() /*std::find(answer.get().begin(), answer.get().end(), t) == answer.get().end()*/ ) { DLVHEX_BENCHMARK_REGISTER_AND_COUNT(sidweakenedpositive, "Gr.inst. not in out after weakened EA-eval", 1); }
00583                 if (weakenedPremiseLiterals > 0 && (!prop.doesProvidePartialAnswer() || tunknown.find(t) == tunknown.end() /*std::find(answer.getUnknown().begin(), answer.getUnknown().end(), t) == answer.getUnknown().end()*/ )){ DLVHEX_BENCHMARK_REGISTER_AND_COUNT(sidweakenedpositive, "Gr.inst. not in unknown after weakened EA-eval", 1); }
00584 
00585                     if (toutput.find(t) == toutput.end() && //std::find(answer.get().begin(), answer.get().end(), t) == answer.get().end() &&
00586                         (!prop.doesProvidePartialAnswer() || tunknown.find(t) == tunknown.end())) { //std::find(answer.getUnknown().begin(), answer.getUnknown().end(), t) == answer.getUnknown().end())) {
00587                         // construct positive output atom
00588                         OrdinaryAtom posatom = atom;
00589                         posatom.tuple[0] = posOutPredicate;
00590                         ID posAtomID = query.ctx->registry()->storeOrdinaryGAtom(posatom);
00591 
00592                         // construct nogood
00593                         Nogood ng = !inp->dependsOnOutputTuple()
00594                             ? extNgInput
00595                             : (*inp)(query, prop, false, t);
00596 
00597                         if (query.ctx->config.getOption("MinimizeNogoods") && !inp->dependsOnOutputTuple() && prop.doesProvidePartialAnswer()) {
00598                             // store the output tuples of the external auxiliary atom for minimization queries
00599                             ID externalAuxiliaryID = NogoodContainer::createLiteral(posAtomID.address);
00600                             externalAuxiliaryTable[externalAuxiliaryID] = t;
00601                             
00602                             if (query.ctx->config.getOption("MinimizeNogoodsOpt")) {
00603                                 // if answers w.r.t. the inputs should be cached, input and output atoms have to be stored separately
00604                                 std::pair<Nogood,ID> newNogood(extNgInput,externalAuxiliaryID);
00605                                 newNogoods.push_back(newNogood);
00606                             } else {
00607                                 ng.insert(externalAuxiliaryID);
00608                                 newNogoodsContainer.addNogood(ng);
00609                             }
00610                         } else {
00611                             ng.insert(NogoodContainer::createLiteral(posAtomID.address));
00612                             nogoods->addNogood(ng);
00613                         }
00614                         DBGLOG(DBG, "Learned negative nogood " << ng.getStringRepresentation(query.ctx->registry()));
00615                         DLVHEX_BENCHMARK_REGISTER_AND_COUNT(sidweakenednumber, "EA-Nogoods from weakened intr.", (weakenedPremiseLiterals > 0 ? 1 : 0));
00616                     }
00617                 }
00618             }
00619             en++;
00620         }
00621 
00622         // nogood minimization without caching answers of external atom
00623         if (query.ctx->config.getOption("MinimizeNogoods") && !query.ctx->config.getOption("MinimizeNogoodsOpt") && !inp->dependsOnOutputTuple() && prop.doesProvidePartialAnswer()) {
00624             DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(sidmin, "Nogood minimization");
00625             // iterate through all newly added nogoods
00626             for (int i = 0; i < newNogoodsContainer.getNogoodCount(); ++i) {
00627                 if (newNogoodsContainer.getNogood(i).size() <= query.ctx->config.getOption("MinimizationSize")) {
00628                     // copy the respective nogood
00629                     Nogood testNg = newNogoodsContainer.getNogood(i);
00630                     // store the ID of answer atom that should still not be contained in answer after minimization
00631                     ID ansID;
00632 
00633                     BOOST_FOREACH (ID& iid, newNogoodsContainer.getNogood(i)) {
00634                         if (externalAuxiliaryTable.find(iid) != externalAuxiliaryTable.end()){
00635                             ansID = iid;
00636                         }
00637                     }
00638                     
00639                     if (query.interpretation->getFact(ansID.address) || !query.ctx->config.getOption("MinimizeNogoodsOnConflict")) {
00640                         DBGLOG(DBG, "Conflicting nogood");
00641                         
00642                         testNg.erase(ansID);
00643 
00644                         InterpretationPtr interpretation(new Interpretation(query.interpretation->getRegistry()));
00645                         InterpretationPtr assigned(new Interpretation(query.interpretation->getRegistry()));
00646 
00647                         // only add true atoms from nogood to the query interpretation
00648                         set_iterator<ID> it = testNg.begin();
00649                         while (it != testNg.end()) {
00650                             if (!it->isNaf()) {
00651                                 interpretation->setFact(it->address);
00652                             }
00653                             assigned->setFact(it->address);
00654                             it++;
00655                         }
00656 
00657                         PluginAtom::Query qa = query;
00658                         qa.interpretation = interpretation;
00659                         qa.assigned = assigned;
00660 
00661                         // iteratively remove each literal from nogood
00662                         BOOST_FOREACH (ID& iid, newNogoodsContainer.getNogood(i)) {
00663                             // only for non-auxiliaries
00664                             if (iid != ansID) {
00665                                 PluginAtom::Answer ans;
00666 
00667                                 if (!iid.isNaf()) {
00668                                     interpretation->clearFact(iid.address);
00669                                 }
00670                                 assigned->clearFact(iid.address);
00671 
00672                                 // query
00673                                 //if (query.ctx->config.getOption("UseExtAtomCache")) query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieveCached(qa, ans, NogoodContainerPtr());
00674                                 //else query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieve(qa, ans, NogoodContainerPtr());
00675                                 query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieveFacade(qa, ans, NogoodContainerPtr(), query.ctx->config.getOption("UseExtAtomCache"));
00676 
00677                                 Tuple t = externalAuxiliaryTable[ansID];
00678 
00679                                 // check if answer tuple is still false
00680                                 if ((std::find(ans.get().begin(), ans.get().end(), t) != ans.get().end()) ||
00681                                    (std::find(ans.getUnknown().begin(), ans.getUnknown().end(), t) != ans.getUnknown().end())) {
00682                                     if (!iid.isNaf()) {
00683                                         interpretation->setFact(iid.address);
00684                                     }
00685                                     assigned->setFact(iid.address);
00686                                 } else {
00687                                     testNg.erase(iid);
00688                                 }
00689                             }
00690                         }
00691                         testNg.insert(ansID);
00692                     }
00693                     
00694                     // add minimized nogood
00695                     nogoods->addNogood(testNg);
00696                     DBGLOG(DBG, "Learned minimized negative nogood " << testNg.getStringRepresentation(query.ctx->registry()) << " from input-output behavior");
00697                 } else {
00698                     nogoods->addNogood(newNogoodsContainer.getNogood(i));
00699                 }
00700             }
00701         }
00702         
00703         // nogood minimization with caching answers of external atom:
00704     if (query.ctx->config.getOption("MinimizeNogoods") && query.ctx->config.getOption("MinimizeNogoodsOpt") && !inp->dependsOnOutputTuple() && prop.doesProvidePartialAnswer()) {      
00705             DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(sidmin, "Nogood minimization");
00706 
00707             BOOST_FOREACH (ID& iid, extNgInput) {
00708                 // cache for answers of external atom
00709                 std::map<std::size_t, PluginAtom::Answer> externalEvaluationsCache;
00710 
00711                 for (int i = 0; i < newNogoods.size(); ++i) {
00712                     if ((query.interpretation->getFact(newNogoods[i].second.address) || !query.ctx->config.getOption("MinimizeNogoodsOnConflict"))
00713                             && (newNogoods[i].first.size() <= query.ctx->config.getOption("MinimizationSize"))) {
00714                         Nogood testNg = newNogoods[i].first;
00715 
00716                         testNg.erase(iid);
00717 
00718                         PluginAtom::Answer ans;
00719                         PluginAtom::Query qa = query;
00720 
00721                         if (externalEvaluationsCache.find(testNg.getHash()) != externalEvaluationsCache.end()) {
00722                             ans = externalEvaluationsCache[testNg.getHash()];
00723                         } else {
00724                             InterpretationPtr interpretation(new Interpretation(query.interpretation->getRegistry()));
00725                             InterpretationPtr assigned(new Interpretation(query.interpretation->getRegistry()));
00726 
00727                             // only add true atoms from nogood to the query interpretation
00728                             set_iterator<ID> it = testNg.begin();
00729                             while (it != testNg.end()) {
00730                                 if (!it->isNaf()) {
00731                                     interpretation->setFact(it->address);
00732                                 }
00733                                 assigned->setFact(it->address);
00734                                 it++;
00735                             }
00736 
00737                             qa.interpretation = interpretation;
00738                             qa.assigned = assigned;
00739 
00740                             // query
00741                             //if (query.ctx->config.getOption("UseExtAtomCache")) query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieveCached(qa, ans, NogoodContainerPtr());
00742                             //else query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieve(qa, ans, NogoodContainerPtr());
00743                             query.ctx->registry()->eatoms.getByID(query.eatomID).pluginAtom->retrieveFacade(qa, ans, NogoodContainerPtr(), query.ctx->config.getOption("UseExtAtomCache"));
00744 
00745                             externalEvaluationsCache[testNg.getHash()] = ans;
00746                         }
00747 
00748                         Tuple t = externalAuxiliaryTable[newNogoods[i].second];
00749 
00750                         // check if answer tuple is still false
00751                         if ((std::find(ans.get().begin(), ans.get().end(), t) == ans.get().end()) &&
00752                            (std::find(ans.getUnknown().begin(), ans.getUnknown().end(), t) == ans.getUnknown().end())) {
00753                             newNogoods[i].first = testNg;
00754                         }
00755                     }
00756                 }
00757             }
00758 
00759             for (int i = 0; i < newNogoods.size(); ++i) {
00760                 Nogood newNg = newNogoods[i].first;
00761                 newNg.insert(newNogoods[i].second);
00762                 DBGLOG(DBG, "Learned minimized nogood " << newNg.getStringRepresentation(query.ctx->registry()) << " from input-output behavior");
00763                 nogoods->addNogood(newNg);
00764             }
00765         }
00766     }
00767 }
00768 
00769 
00770 void ExternalLearningHelper::learnFromGroundRule(const PluginAtom::Query& query, ID groundRule, NogoodContainerPtr nogoods)
00771 {
00772 
00773     RegistryPtr reg = query.ctx->registry();
00774 
00775     if (nogoods) {
00776         DBGLOG(DBG, "External Learning: Ground Rule");
00777 
00778         const Rule& rule = query.ctx->registry()->rules.getByID(groundRule);
00779 
00780         Nogood ng;
00781         BOOST_FOREACH (ID hId, rule.head) {
00782             const OrdinaryAtom& oat = query.ctx->registry()->ogatoms.getByID(hId);
00783             Tuple t;
00784             t.insert(t.end(), oat.tuple.begin() + 1, oat.tuple.end());
00785             if (reg->terms.getByID(oat.tuple[0]).getUnquotedString() == "out") {
00786                 // output atom is positive, i.e. it must not be false
00787                 ng.insert(getOutputAtom(query, t, false));
00788             }
00789             else {
00790                 // output atom is negative, i.e. it must not be true
00791                 ng.insert(getOutputAtom(query, t, true));
00792             }
00793         }
00794         BOOST_FOREACH (ID bId, rule.body) {
00795             ng.insert(bId);
00796         }
00797         DBGLOG(DBG, "Learned nogood " << ng.getStringRepresentation(query.ctx->registry()) << " from rule");
00798         nogoods->addNogood(ng);
00799     }
00800 }
00801 
00802 
00803 void ExternalLearningHelper::learnFromRule(const PluginAtom::Query& query, ID rid, ProgramCtx* ctx, NogoodContainerPtr nogoods)
00804 {
00805 
00806     if (nogoods) {
00807         DBGLOG(DBG, "External Learning: Rule");
00808 
00809         // prepare map for replacing body predicates:
00810         // "in[i+1]" is replaced by the predicate passed as parameter number "i"
00811         std::map<ID, ID> predReplacementMap;
00812         for (uint32_t p = 0; p < query.input.size(); p++) {
00813             std::stringstream inPredStr;
00814             inPredStr << "in" << (p + 1);
00815             Term inPredTerm(ID::MAINKIND_TERM | ID::SUBKIND_TERM_CONSTANT, inPredStr.str());
00816             ID inPredID = query.ctx->registry()->storeTerm(inPredTerm);
00817             predReplacementMap[inPredID] = query.input[p];
00818         }
00819 
00820         DBGLOG(DBG, "Rewriting rule");
00821         const Rule& rule = query.ctx->registry()->rules.getByID(rid);
00822 
00823         Rule rrule(ID::MAINKIND_RULE | ID::SUBKIND_RULE_REGULAR);
00824         rrule.head = rule.head;
00825         BOOST_FOREACH (ID batom, rule.body) {
00826 
00827             const OrdinaryAtom& oatom = batom.isOrdinaryGroundAtom() ? query.ctx->registry()->ogatoms.getByID(batom) : query.ctx->registry()->onatoms.getByID(batom);
00828 
00829             // replace predicate name by parameter from query.input
00830             OrdinaryAtom roatom = oatom;
00831             bool found = false;
00832             for (uint32_t inp = 0; inp < query.input.size(); inp++) {
00833                 std::stringstream inPredStr;
00834                 inPredStr << "in" << (inp + 1);
00835                 Term inPredTerm(ID::MAINKIND_TERM | ID::SUBKIND_TERM_CONSTANT, inPredStr.str());
00836                 ID inPredID = query.ctx->registry()->storeTerm(inPredTerm);
00837 
00838                 if (roatom.tuple[0] == inPredID) {
00839                     roatom.tuple[0] = query.input[inp];
00840                     found = true;
00841                     break;
00842                 }
00843             }
00844             assert(found);
00845 
00846             ID batomId = batom.isOrdinaryGroundAtom() ? query.ctx->registry()->storeOrdinaryGAtom(roatom) : query.ctx->registry()->storeOrdinaryNAtom(roatom);
00847             ID mask(ID::MAINKIND_LITERAL, 0);
00848             if (batom.isNaf()) mask = mask | ID(ID::NAF_MASK, 0);
00849             if (batom.isOrdinaryGroundAtom()) mask = mask | ID(ID::SUBKIND_ATOM_ORDINARYG, 0);
00850             if (batom.isOrdinaryNongroundAtom()) mask = mask | ID(ID::SUBKIND_ATOM_ORDINARYN, 0);
00851             rrule.body.push_back(ID(mask.kind, batomId.address));
00852         }
00853         ID rruleId = query.ctx->registry()->storeRule(rrule);
00854 
00855         DBGLOG(DBG, "Building ASP Program");
00856         InterpretationConstPtr edb = query.interpretation;
00857         std::vector<ID> idb;
00858         idb.push_back(rruleId);
00859         OrdinaryASPProgram program(query.ctx->registry(), idb, edb);
00860 
00861         DBGLOG(DBG, "Grounding learning rule");
00862         GenuineGrounderPtr grounder = GenuineGrounderPtr(new InternalGrounder(*ctx, program, InternalGrounder::builtin));
00863         const OrdinaryASPProgram& gprogram = grounder->getGroundProgram();
00864 
00865         DBGLOG(DBG, "Generating nogoods for all ground rules");
00866         BOOST_FOREACH (ID rid, gprogram.idb) {
00867             learnFromGroundRule(query, rid, nogoods);
00868         }
00869     }
00870 }
00871 
00872 
00873 DLVHEX_NAMESPACE_END
00874 
00875 // vim:expandtab:ts=4:sw=4:
00876 // mode: C++
00877 // End:
00878 
00879