dlvhex
2.5.0
|
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