dlvhex  2.5.0
src/ASPSolver_libclingo.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
00038 
00039 #ifdef HAVE_LIBCLINGO
00040 
00041 #include "dlvhex2/ASPSolver.h"
00042 #include "dlvhex2/PlatformDefinitions.h"
00043 #include "dlvhex2/Benchmarking.h"
00044 #include "dlvhex2/Printer.h"
00045 #include "dlvhex2/Registry.h"
00046 #include "dlvhex2/ProgramCtx.h"
00047 #include "dlvhex2/AnswerSet.h"
00048 
00049 #include <clingo/clingo_app.h>
00050 
00051 #include <boost/tokenizer.hpp>
00052 #include <boost/thread.hpp>
00053 #include <boost/shared_ptr.hpp>
00054 #include <boost/foreach.hpp>
00055 #include <list>
00056 
00057 DLVHEX_NAMESPACE_BEGIN
00058 
00059 namespace ASPSolver
00060 {
00061 
00062     //
00063     // ClingoSoftware
00064     //
00065     ClingoSoftware::Options::Options():
00066     ASPSolverManager::GenericOptions() {
00067     }
00068 
00069     ClingoSoftware::Options::~Options() {
00070     }
00071 
00072     namespace
00073     {
00074 
00075         class GringoPrinter:
00076         public RawPrinter
00077         {
00078             public:
00079                 typedef RawPrinter Base;
00080                 GringoPrinter(std::ostream& out, RegistryPtr registry):
00081                 RawPrinter(out, registry) {}
00082 
00083                 virtual void print(ID id) {
00084                     if( id.isRule() && id.isRegularRule() ) {
00085                         // disjunction in rule heads is | not v
00086                         const Rule& r = registry->rules.getByID(id);
00087                         printmany(r.head, " | ");
00088                         if( !r.body.empty() ) {
00089                             out << " :- ";
00090                             printmany(r.body, ", ");
00091                         }
00092                         out << ".";
00093                     }
00094                     else {
00095                         Base::print(id);
00096                     }
00097                 }
00098         };
00099 
00100         class ClingoResults:
00101         public ASPSolverManager::Results
00102         {
00103             public:
00104                 typedef std::list<AnswerSet::Ptr> Storage;
00105                 Storage answersets;
00106                 bool resetCurrent;
00107                 Storage::const_iterator current;
00108 
00109                 ClingoResults():
00110                 resetCurrent(true),
00111                     current() {}
00112                 virtual ~ClingoResults() {}
00113 
00114                 void add(AnswerSet::Ptr as) {
00115                     answersets.push_back(as);
00116 
00117                     // we do this because I'm not sure if a begin()==end() iterator
00118                     // becomes begin() or end() after insertion of the first element
00119                     // (this is the failsafe version)
00120                     if( resetCurrent ) {
00121                         current = answersets.begin();
00122                         resetCurrent = false;
00123                     }
00124                 }
00125 
00126                 virtual AnswerSet::Ptr getNextAnswerSet() {
00127                     // if no answer set was ever added, or we reached the end
00128                     if( (resetCurrent == true) ||
00129                     (current == answersets.end()) ) {
00130                         return AnswerSet::Ptr();
00131                     }
00132                     else {
00133                         Storage::const_iterator ret = current;
00134                         current++;
00135                         return *ret;
00136                     }
00137                 }
00138         };
00139 
00140         class ClaspTermination:
00141         public std::runtime_error
00142         {
00143             public:
00144                 ClaspTermination():
00145                 std::runtime_error("ClaspTermination") {
00146                 }
00147         };
00148 
00149         class MyClaspOutputFormat:
00150         public Clasp::OutputFormat
00151         {
00152             public:
00153                 typedef Clasp::OutputFormat Base;
00154                 ConcurrentQueueResults& results;
00155                 bool& shouldTerminate;
00156                 RegistryPtr registry;
00157                 InterpretationConstPtr mask;
00158 
00159                 MyClaspOutputFormat(
00160                     ConcurrentQueueResults& res,
00161                     bool& shouldTerminate,
00162                     RegistryPtr registry,
00163                     InterpretationConstPtr mask):
00164                 Base(),
00165                     results(res),
00166                     shouldTerminate(shouldTerminate),
00167                     registry(registry),
00168                 mask(mask) {
00169                 }
00170 
00171                 virtual ~MyClaspOutputFormat() {
00172                 }
00173 
00174                 virtual void printModel(
00175                 const Clasp::Solver& s, const Clasp::Enumerator&) {
00176                     ConcurrentQueueResults* results = &this->results;
00177                     DBGLOG(DBG,"getting model from clingo!");
00178 
00179                     if( shouldTerminate ) {
00180                         DBGLOG(DBG,"terminating (1) not enqueueing anything");
00181                         throw ClaspTermination();
00182                     }
00183 
00184                     AnswerSet::Ptr as(new AnswerSet(registry));
00185 
00186                     const Clasp::AtomIndex& index = *s.strategies().symTab;
00187                     for (Clasp::AtomIndex::const_iterator it = index.begin(); it != index.end(); ++it) {
00188                         if (s.value(it->second.lit.var()) == Clasp::trueValue(it->second.lit) && !it->second.name.empty()) {
00189                             const char* groundatom = it->second.name.c_str();
00190 
00191                             // try to do it via string (unstructured)
00192                             ID idga = registry->ogatoms.getIDByString(groundatom);
00193                             if( idga == ID_FAIL ) {
00194                                 // parse groundatom, register and store
00195                                 DBGLOG(DBG,"parsing clingo ground atom '" << groundatom << "'");
00196                                 OrdinaryAtom ogatom(ID::MAINKIND_ATOM | ID::SUBKIND_ATOM_ORDINARYG);
00197                                 ogatom.text = groundatom;
00198                                 {
00199                                     // create ogatom.tuple
00200                                     boost::char_separator<char> sep(",()");
00201                                     typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
00202                                     tokenizer tok(ogatom.text, sep);
00203                                     for(tokenizer::iterator it = tok.begin();
00204                                     it != tok.end(); ++it) {
00205                                         DBGLOG(DBG,"got token '" << *it << "'");
00206                                         Term term(ID::MAINKIND_TERM, *it);
00207                                         // the following takes care of int vs const/string
00208                                         ID id = registry->storeTerm(term);
00209                                         assert(id != ID_FAIL);
00210                                         assert(!id.isVariableTerm());
00211                                         if( id.isAuxiliary() )
00212                                             ogatom.kind |= ID::PROPERTY_AUX;
00213                                         ogatom.tuple.push_back(id);
00214                                     }
00215                                 }
00216                                 idga = registry->ogatoms.storeAndGetID(ogatom);
00217                             }
00218                             assert(idga != ID_FAIL);
00219                             as->interpretation->setFact(idga.address);
00220                         }
00221                     }
00222 
00223                     LOG(INFO,"got model from clingo: " << *as);
00224                     //std::cout << this << "CLINGO MODEL" << std::endl << *as << std::endl;
00225                     if( mask )
00226                         as->interpretation->getStorage() -= mask->getStorage();
00227                     results->enqueueAnswerset(as);
00228 
00229                     if( shouldTerminate ) {
00230                         DBGLOG(DBG,"terminating (2) not enqueueing anything");
00231                         throw ClaspTermination();
00232                     }
00233                 }
00234 
00235                 virtual void printStats(
00236                 const Clasp::SolverStatistics&, const Clasp::Enumerator&) {
00237                 }
00238         };
00239 
00240         class MyClingoApp:
00241         public ClingoApp<CLINGO>
00242         {
00243             public:
00244                 typedef ClingoApp<CLINGO> Base;
00245                 ConcurrentQueueResults& results;
00246                 bool& shouldTerminate;
00247 
00248                 MyClingoApp(ConcurrentQueueResults& res, bool& shouldTerminate):
00249                 Base(),
00250                     results(res),
00251                 shouldTerminate(shouldTerminate) {
00252                     DBGLOG(DBG,"MyClingoApp()");
00253                 }
00254 
00255                 ~MyClingoApp() {
00256                     DBGLOG(DBG,"~MyClingoApp()");
00257                 }
00258 
00259                 void solve(std::string& program, RegistryPtr registry, InterpretationConstPtr mask) {
00260                     ConcurrentQueueResults* results = &this->results;
00261                     try
00262                     {
00263 
00264                         // configure like a binary
00265                         {
00266                             char execname[] = "clingo_within_dlvhex";
00267                             char shiftopt[] = "--shift";
00268                             char allmodelsopt[] = "-n 0";
00269                             char noverboseopt[] = "--verbose=0";
00270                             char* argv[] = {
00271                                 execname,
00272                                 shiftopt,
00273                                 allmodelsopt,
00274                                 noverboseopt,
00275                                 NULL
00276                             };
00277                             int argc = 0;
00278                             while(argv[argc] != NULL)
00279                                 argc++;
00280                             DBGLOG(DBG,"passing " << argc << " arguments to gringo:" <<
00281                                 printrange(std::vector<const char*>(&argv[0], &argv[argc])));
00282                             if(!parse(argc, argv))
00283                                 throw std::runtime_error( messages.error.c_str() );
00284                         #ifndef NDEBUG
00285                             printWarnings();
00286                         #endif
00287                         }
00288 
00289                         // configure in out
00290                         Streams s;
00291                         LOG(DBG,"sending to clingo:" << std::endl << "===" << std::endl << program << std::endl << "===");
00292                         //std::cout << this << "CLINGO PROGRAM" << std::endl << program << std::endl;
00293                         s.appendStream(
00294                             Streams::StreamPtr(new std::istringstream(program)),
00295                             "dlvhex_to_clingo");
00296                         in_.reset(new FromGringo<CLINGO>(*this, s));
00297                         out_.reset(new MyClaspOutputFormat(*results, shouldTerminate, registry, mask));
00298 
00299                         Clasp::ClaspFacade clasp;
00300                         facade_ = &clasp;
00301                         clingo.iStats = false;
00302                         clasp.solve(*in_, config_, this);
00303                         DBGLOG(DBG,"clasp.solve finished normally");
00304                     }
00305                     catch(const ClaspTermination& e) {
00306                         WARNING("we should find another way (than throwing an exception) to abort model enumeration in clasp")
00307                             DBGLOG(DBG,"got ClaspTermination exception");
00308                     }
00309                     catch(const std::exception& e) {
00310                         std::cerr << "got clingo exception " << e.what() << std::endl;
00311                         throw;
00312                     }
00313                     catch(...) {
00314                         std::cerr << "got very strange clingo exception!" << std::endl;
00315                         throw;
00316                     }
00317                 }
00318         };
00319 
00320         //
00321         // ConcurrentQueueResultsImpl
00322         //
00323         // Delegate::Impl is used to prepare the result
00324         // this object would be destroyed long before the result will be destroyed
00325         // therefore its ownership is passed to the results
00326         //
00327         struct ConcurrentQueueResultsImpl:
00328         public ConcurrentQueueResults
00329         {
00330             public:
00331                 MyClingoApp myclingo;
00332                 ClingoSoftware::Options options;
00333                 OrdinaryASPProgram program;
00334                 bool shouldTerminate;
00335                 boost::thread answerSetProcessingThread;
00336 
00337             public:
00338                 ConcurrentQueueResultsImpl(
00339                     const ClingoSoftware::Options& options,
00340                     const OrdinaryASPProgram& program):
00341                 myclingo(*this, shouldTerminate),
00342                     ConcurrentQueueResults(),
00343                     options(options),
00344                     program(program),
00345                 shouldTerminate(false) {
00346                     DBGLOG(DBG,"libclingo ConcurrentQueueResultsImpl()" << this);
00347                 }
00348 
00349                 virtual ~ConcurrentQueueResultsImpl() {
00350                     DBGLOG(DBG,"libclingo ~ConcurrentQueueResultsImpl()" << this);
00351                     DBGLOG(DBG,"setting termination bool, emptying queue, and joining thread");
00352                     shouldTerminate = true;
00353                     queue->flush();
00354                     DBGLOG(DBG,"joining thread");
00355                     answerSetProcessingThread.join();
00356                     DBGLOG(DBG,"done");
00357                 }
00358 
00359                 void answerSetProcessingThreadFunc();
00360 
00361                 void startThread() {
00362                     DBGLOG(DBG,"starting answer set processing thread");
00363                     answerSetProcessingThread = boost::thread(
00364                         boost::bind(
00365                         &ConcurrentQueueResultsImpl::answerSetProcessingThreadFunc,
00366                         boost::ref(*this)));
00367                     DBGLOG(DBG,"started answer set processing thread");
00368                 }
00369         };
00370         typedef boost::shared_ptr<ConcurrentQueueResultsImpl>
00371             ConcurrentQueueResultsImplPtr;
00372 
00373         void ConcurrentQueueResultsImpl::answerSetProcessingThreadFunc() {
00374             WARNING("create multithreaded logger by using thread-local storage for logger indent")
00375                 DBGLOG(DBG,"[" << this << "]" " starting libclingo answerSetProcessingThreadFunc");
00376             try
00377             {
00378                 // output program to stream
00379                 WARNING("TODO handle program.maxint for clingo")
00380                     std::string str;
00381                 {
00382                     DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(sidprepare,"prepare clingo input");
00383 
00384                     std::ostringstream programStream;
00385                     GringoPrinter printer(programStream, program.registry);
00386 
00387                     if( program.edb != 0 ) {
00388                         // print edb interpretation as facts
00389                         program.edb->printAsFacts(programStream);
00390                         programStream << "\n";
00391                     }
00392 
00393                     printer.printmany(program.idb, "\n");
00394                     programStream << std::endl;
00395                     str = programStream.str();
00396                 }
00397 
00398                 // start solver (this creates results in callback)
00399                 myclingo.solve(str, program.registry, program.mask);
00400                 DBGLOG(DBG,"[" << this << "]" "myclingo.solve terminated regularly");
00401 
00402                 if( !shouldTerminate ) {
00403                     // enqueue regular end
00404                     enqueueEnd();
00405                 }
00406             }
00407             catch(const GeneralError& e) {
00408                 std::stringstream s;
00409                 s << "libclingo: got GeneralError exception " << e.getErrorMsg();
00410                 LOG(ERROR, "[" << this << "]" + s.str());
00411                 enqueueException(s.str());
00412             }
00413             catch(const std::exception& e) {
00414                 std::stringstream s;
00415                 s << "libclingo: got std::exception " << e.what();
00416                 LOG(ERROR, "[" << this << "]" + s.str());
00417                 enqueueException(s.str());
00418             }
00419             catch(...) {
00420                 std::stringstream s;
00421                 s << "libclingo: got other exception";
00422                 LOG(ERROR, "[" << this << "]" + s.str());
00423                 enqueueException(s.str());
00424             }
00425             DBGLOG(DBG,"[" << this << "]" "exiting answerSetProcessingThreadFunc");
00426         }
00427 
00428     }
00429 
00430     struct ClingoSoftware::Delegate::Impl
00431     {
00432         ConcurrentQueueResultsImplPtr results;
00433         const Options& options;
00434         Impl(const Options& options):
00435         results(), options(options) {}
00436         ~Impl() { }
00437     };
00438 
00439     ClingoSoftware::Delegate::Delegate(const Options& options):
00440     pimpl(new Impl(options)) {
00441     }
00442 
00443     ClingoSoftware::Delegate::~Delegate() {
00444     }
00445 
00446     /*
00447     void
00448     ClingoSoftware::Delegate::useInputProviderInput(InputProvider& inp, RegistryPtr reg)
00449     {
00450       throw std::runtime_error("TODO implement ClingoSoftware::Delegate::useInputProviderInput(const InputProvider& inp, RegistryPtr reg)");
00451     }
00452     */
00453 
00454     void
00455     ClingoSoftware::Delegate::useASTInput(const OrdinaryASPProgram& program) {
00456         DLVHEX_BENCHMARK_REGISTER_AND_SCOPE(sid,"ClingoSoftware useASTInput");
00457 
00458         pimpl->results.reset(
00459             new ConcurrentQueueResultsImpl(pimpl->options, program));
00460         pimpl->results->startThread();
00461     }
00462 
00463     ASPSolverManager::ResultsPtr
00464     ClingoSoftware::Delegate::getResults() {
00465         return pimpl->results;
00466     }
00467 
00468 }                                // namespace ASPSolver
00469 
00470 
00471 DLVHEX_NAMESPACE_END
00472 #endif                           // HAVE_LIBCLINGO
00473 
00474 
00475 // vim:expandtab:ts=4:sw=4:
00476 // mode: C++
00477 // End: