knowrob  2.1.0
A Knowledge Base System for Cognition-enabled Robots
PrologReasoner.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of KnowRob, please consult
3  * https://github.com/knowrob/knowrob for license details.
4  */
5 
6 #include <memory>
7 #include <filesystem>
8 
9 #include "knowrob/Logger.h"
10 #include "knowrob/reasoner/ReasonerManager.h"
11 #include "knowrob/reasoner/prolog/PrologReasoner.h"
12 #include "knowrob/reasoner/prolog/semweb.h"
13 #include "knowrob/terms/ListTerm.h"
14 #include "knowrob/formulas/Bottom.h"
15 #include "knowrob/queries/TokenQueue.h"
16 #include "knowrob/semweb/PrefixRegistry.h"
17 #include "knowrob/semweb/ImportHierarchy.h"
18 #include "knowrob/KnowledgeBase.h"
19 #include "knowrob/integration/prolog/PrologBackend.h"
20 #include "knowrob/reasoner/ReasonerError.h"
21 #include "knowrob/reasoner/prolog/semweb.h"
22 #include "knowrob/queries/AnswerNo.h"
23 #include "knowrob/terms/Numeric.h"
24 #include "knowrob/TimePoint.h"
25 
26 using namespace knowrob;
27 
28 static const int prologQueryFlags = PL_Q_CATCH_EXCEPTION | PL_Q_NODEBUG;
29 
30 #define PROLOG_REASONER_EVAL(goal) PROLOG_ENGINE_EVAL(getReasonerQuery(goal))
31 
32 // make reasoner type accessible
34 
36 
37 static inline std::shared_ptr<PrologReasoner> getPrologReasoner(term_t t_reasonerManager, term_t t_reasonerModule) {
38  auto definedReasoner = PrologReasoner::getDefinedReasoner(t_reasonerManager, t_reasonerModule);
39  if (!definedReasoner) {
40  KB_ERROR("unable to find reasoner with id '{}' (manager id: {}).",
41  *PrologTerm::toKnowRobTerm(t_reasonerModule),
42  *PrologTerm::toKnowRobTerm(t_reasonerManager));
43  return {};
44  }
45  auto reasoner = definedReasoner->value();
46  auto typedReasoner = std::dynamic_pointer_cast<PrologReasoner>(reasoner);
47  if (!typedReasoner) {
48  KB_ERROR("reasoner with id '{}' (manager id: {}) is not a mongolog reasoner.",
49  *PrologTerm::toKnowRobTerm(t_reasonerModule),
50  *PrologTerm::toKnowRobTerm(t_reasonerManager));
51  }
52  return typedReasoner;
53 }
54 
55 namespace knowrob::prolog {
56  static foreign_t
57  reasoner_define_relation4(term_t t_reasonerManager, term_t t_reasonerModule, term_t t_relation, term_t t_arity) {
58  auto reasoner = getPrologReasoner(t_reasonerManager, t_reasonerModule);
59  char *relationName;
60  int64_t arity;
61  if (reasoner &&
62  PL_get_atom_chars(t_relation, &relationName) &&
63  PL_get_int64(t_arity, &arity)) {
64  reasoner->defineRelation(PredicateIndicator(relationName, arity));
65  return TRUE;
66  } else {
67  return FALSE;
68  }
69  }
70 
71  static foreign_t
72  reasoner_define_class3(term_t t_reasonerManager, term_t t_reasonerModule, term_t t_class) {
73  auto reasoner = getPrologReasoner(t_reasonerManager, t_reasonerModule);
74  char *className;
75  if (reasoner && PL_get_atom_chars(t_class, &className)) {
76  reasoner->defineClass(IRIAtom::Tabled(className));
77  return TRUE;
78  } else {
79  return FALSE;
80  }
81  }
82 }
83 
85  // enable goal-driven reasoning features
88  // add data handler for prolog files
89  addDataHandler("prolog", [this]
90  (const DataSourcePtr &dataFile) { return consult(dataFile->uri()); });
91 }
92 
94 
96  static auto unload_f = "reasoner_unload";
98 }
99 
100 std::string_view PrologReasoner::callFunctor() {
101  static const auto reasoner_call_f = "reasoner_call";
102  return reasoner_call_f;
103 }
104 
106  return PrologTerm(callFunctor(), goal, PrologTerm::nil());
107 }
108 
110  // call PL_initialize
113 
114  if (!isKnowRobInitialized_) {
115  isKnowRobInitialized_ = true;
116 
117  PL_register_foreign("reasoner_define_relation_cpp", 4, (pl_function_t) prolog::reasoner_define_relation4, 0);
118  PL_register_foreign("reasoner_define_class_cpp", 3, (pl_function_t) prolog::reasoner_define_class3, 0);
119 
120  // auto-load some files into "user" module
122 
123  // load init file from reasoner directory
124  consult(std::filesystem::path("reasoner") / "prolog" / "__init__.pl", "user", false);
125 
126  // register RDF namespaces with Prolog.
127  // in particular the ones specified in settings are globally registered with PrefixRegistry.
128  static const auto register_prefix_i = "rdf_register_prefix";
129  for (auto &pair: PrefixRegistry::get()) {
130  const auto &uri = pair.first;
131  const auto &alias = pair.second;
132  PROLOG_REASONER_EVAL(PrologTerm(register_prefix_i, alias, uri));
133  }
134  }
135 
136  // load properties into the reasoner module.
137  // this is needed mainly for `reasoner_setting/2` that provides reasoner instance specific settings.
138  for (auto &pair: cfg) {
139  auto key_t = cfg.createKeyTerm(pair.first);
140  setReasonerSetting(key_t, pair.second);
141  }
142  // load reasoner default packages. this is usually the code that implements the reasoner.
144 
145  // this is needed to assert triple/3 predicate in the reasoner module
146  static auto reasoner_rdf_init_f = "reasoner_rdf_init";
147  PROLOG_REASONER_EVAL(PrologTerm(reasoner_rdf_init_f));
148 
149  return true;
150 }
151 
152 bool PrologReasoner::setReasonerSetting(const TermPtr &key, const TermPtr &valueString) {
153  static auto set_setting_f = "reasoner_set_setting";
154  return PROLOG_REASONER_EVAL(PrologTerm(set_setting_f, reasonerName(), key, valueString));
155 }
156 
158  static const auto b_setval_f = "b_setval";
159  static const auto reasoner_module_a = "reasoner_module";
160  static const auto reasoner_manager_a = "reasoner_manager";
161  auto managerID = std::make_shared<Integer>(reasonerManager().managerID());
162  return PrologTerm(b_setval_f, reasoner_module_a, reasonerName()) &
163  PrologTerm(b_setval_f, reasoner_manager_a, managerID) &
164  goal;
165 }
166 
167 std::shared_ptr<NamedReasoner> PrologReasoner::getDefinedReasoner(
168  const term_t &t_reasonerManager, const term_t &t_reasonerModule) {
169  int i_reasonerManager;
170  if (!PL_get_integer(t_reasonerManager, &i_reasonerManager)) return {};
171  auto reasonerManager = ReasonerManager::getManager(i_reasonerManager);
172  if (!reasonerManager) return {};
173 
174  char *reasonerModule;
175  if (PL_get_atom_chars(t_reasonerModule, &reasonerModule)) {
176  // remove "reasoner_" prefix from module name.
177  // the prefix is used to avoid name clashes.
178  if (strncmp(reasonerModule, "Reasoner_", 9) == 0) {
179  reasonerModule += 9;
180  }
181  return reasonerManager->getPluginWithID(reasonerModule);
182  } else {
183  return {};
184  }
185 }
186 
187 bool PrologReasoner::consult(const std::filesystem::path &uri, const char *module, bool doTransformQuery) {
188  static auto consult_f = "consult";
189  auto path = PrologEngine::getPrologPath(uri);
190 
191  return PrologEngine::eval([&]() {
192  PrologTerm plTerm(consult_f, path.native());
193  if (module) plTerm.setModule(module);
194  return getReasonerQuery(doTransformQuery ? transformGoal(plTerm) : plTerm);
195  });
196 }
197 
198 bool PrologReasoner::load_rdf_xml(const std::filesystem::path &rdfFile) {
199  static auto load_rdf_xml_f = "load_rdf_xml";
200  auto path = PrologEngine::getResourcePath(rdfFile);
201  return PROLOG_REASONER_EVAL(PrologTerm(load_rdf_xml_f, path.native(), reasonerName()));
202 }
203 
205  // context term options:
206  static const auto query_scope_f = "query_scope";
207  static const auto solution_scope_f = "solution_scope";
208 
209  // create runner that evaluates the goal in a thread with a Prolog engine.
210  // Note that this is needed because the current thread might not have
211  // a Prolog engine associated with it.
212  auto runner = std::make_shared<ThreadPool::LambdaRunner>(
213  [this, &query](const ThreadPool::LambdaRunner::StopChecker &hasStopRequest) {
214  PrologTerm queryFrame, answerFrame;
215  putQueryFrame(queryFrame, query->ctx()->selector);
216 
217  PrologTerm goal(query->formula());
218  // :- ContextTerm = [query_scope(...), solution_scope(Variable)]
219  PrologList contextTerm({
220  PrologTerm(query_scope_f, queryFrame),
221  PrologTerm(solution_scope_f, answerFrame)
222  });
223  // :- QueryGoal = ( b_setval(reasoner_module, reasonerName()),
224  // b_setval(reasoner_manager, managerID),
225  // reasoner_call(Goal, ContextTerm) ).
226  PrologTerm queryGoal = getReasonerQuery(
227  PrologTerm(callFunctor(), goal, contextTerm));
228 
229  auto qid = queryGoal.openQuery(prologQueryFlags);
230  bool hasSolution = false;
231  while (!hasStopRequest() && knowrob::PrologTerm::nextSolution(qid)) {
232  query->push(yes(query, goal, answerFrame));
233  hasSolution = true;
234  if (query->ctx()->queryFlags & QUERY_FLAG_ONE_SOLUTION) break;
235  }
236  PL_close_query(qid);
237  if (!hasSolution) query->push(no(query));
238  });
239 
240  // push goal and return
241  PrologEngine::pushGoal(runner, [query](const std::exception &e) {
242  KB_WARN("an exception occurred for prolog query ({}): {}.", *query->formula(), e.what());
243  throw;
244  });
245 
246  // evaluateQuery must be blocking -> wait on Prolog runner to finish
247  runner->join();
248 
249  return true;
250 }
251 
253  const PrologTerm &rdfGoal,
254  const PrologTerm &answerFrameTerm) {
255  KB_DEBUG("Prolog has a next solution.");
256 
257  // create bindings object
258  auto bindings = std::make_shared<Bindings>();
259  for (const auto &kv: rdfGoal.vars()) {
260  auto grounding = PrologTerm::toKnowRobTerm(kv.second);
261  if (grounding && grounding->termType() != TermType::VARIABLE) {
262  bindings->set(std::make_shared<Variable>(kv.first), PrologTerm::toKnowRobTerm(kv.second));
263  }
264  }
265 
266  // create an answer object given the bindings
267  auto yes = std::make_shared<AnswerYes>(bindings);
268  yes->setReasonerTerm(reasonerName());
269 
270  // set the solution scope, if reasoner specified it
271  auto answerFrame_rw = createAnswerFrame(answerFrameTerm);
272  GraphSelectorPtr answerFrame_ro;
273  if (answerFrame_rw) {
274  yes->setFrame(answerFrame_rw);
275  answerFrame_ro = answerFrame_rw;
276  } else {
277  answerFrame_ro = DefaultGraphSelector();
278  }
279 
280  // store instantiations of literals
281  auto &phi = query->formula();
282  for (auto &literal: phi->literals()) {
283  auto &p = literal->predicate();
284  auto p_instance = applyBindings(p, *yes->substitution());
285  yes->addGrounding(std::static_pointer_cast<Predicate>(p_instance), literal->isNegated(), answerFrame_ro);
286  }
287 
288  return yes;
289 }
290 
292  KB_DEBUG("Prolog has no solution.");
293  // if no solution was found, indicate that via a NegativeAnswer.
294  auto negativeAnswer = std::make_shared<AnswerNo>();
295  negativeAnswer->setReasonerTerm(reasonerName());
296  // however, as Prolog cannot proof negations such an answer is always not well-founded
297  // and can be overruled by a well-founded one.
298  negativeAnswer->setIsUncertain(true, std::nullopt);
299 
300  // add literals to the answer for which the query has failed.
301  // NOTE: At the moment negations will only appear in simple queries
302  // without connective operators, so no problem that below we can
303  // only handle queries with a single literal.
304  auto &literals = query->formula()->literals();
305  if (literals.size() == 1) {
306  auto &firstLiteral = *literals.begin();
307  negativeAnswer->addUngrounded(
308  std::static_pointer_cast<Predicate>(firstLiteral->predicate()),
309  firstLiteral->isNegated());
310  }
311 
312  return negativeAnswer;
313 }
314 
315 
317  // Map an GraphSelector to a Prolog dict term.
318  // The term has the form:
319  // { epistemicMode: knowledge|belief,
320  // temporalMode: sometimes|always,
321  // [agent: $name,]
322  // [since: $time,]
323  // [until: $time] }
324 
325  int numFrameKeys = 2;
326  if (frame.perspective) numFrameKeys += 1;
327  if (frame.begin.has_value()) numFrameKeys += 1;
328  if (frame.end.has_value()) numFrameKeys += 1;
329 
330  // option: frame($dictTerm)
331  atom_t scopeKeys[numFrameKeys];
332  auto scopeValues = PL_new_term_refs(numFrameKeys);
333 
334  // epistemicMode: knowledge|belief
335  static const auto epistemicMode_a = PL_new_atom("epistemicMode");
336  static const auto knowledge_a = PL_new_atom("knowledge");
337  static const auto belief_a = PL_new_atom("belief");
338 
339  bool isAboutBelief = (frame.uncertain);
340  int keyIndex = 0;
341  scopeKeys[keyIndex] = epistemicMode_a;
342  if (!PL_put_atom(scopeValues, isAboutBelief ? belief_a : knowledge_a)) return false;
343 
344  // temporalMode: sometimes|always
345  static const auto temporalMode_a = PL_new_atom("temporalMode");
346  static const auto sometimes_a = PL_new_atom("sometimes");
347  static const auto always_a = PL_new_atom("always");
348 
349  bool isAboutSomePast = (frame.occasional);
350  scopeKeys[++keyIndex] = temporalMode_a;
351  if (!PL_put_atom(scopeValues + keyIndex, isAboutSomePast ? sometimes_a : always_a)) return false;
352 
353  // agent: $name
354  if (frame.perspective) {
355  static const auto agent_a = PL_new_atom("agent");
356  scopeKeys[++keyIndex] = agent_a;
357  auto agent_iri = frame.perspective->iri();
358  if (!PL_put_atom_chars(scopeValues + keyIndex, agent_iri.data())) return false;
359  }
360 
361  // since: $name
362  if (frame.begin.has_value()) {
363  static const auto since_a = PL_new_atom("since");
364  scopeKeys[++keyIndex] = since_a;
365  if (!PL_put_float(scopeValues + keyIndex, frame.begin.value())) return false;
366  }
367  // until: $name
368  if (frame.end.has_value()) {
369  static const auto until_a = PL_new_atom("until");
370  scopeKeys[++keyIndex] = until_a;
371  if (!PL_put_float(scopeValues + keyIndex, frame.end.value())) return false;
372  }
373 
374  return PL_put_dict(frameTerm(), 0, numFrameKeys, scopeKeys, scopeValues);
375 }
376 
377 std::shared_ptr<GraphSelector> PrologReasoner::createAnswerFrame(const PrologTerm &plTerm) {
378  std::shared_ptr<GraphSelector> frame;
379 
380  if (PL_is_variable(plTerm())) {
381  // reasoner did not specify a solution scope
382  return {};
383  } else if (PL_is_dict(plTerm())) {
384  // the solution scope was generated as a Prolog dictionary
385  auto scope_val = PL_new_term_ref();
386 
387  frame = std::make_shared<GraphSelector>();
388 
389  // read "time" key, the value is expected to be a predicate `range(Since,Until)`
390  static const auto time_key = PL_new_atom("time");
391  if (PL_get_dict_key(time_key, plTerm(), scope_val)) {
392  term_t arg = PL_new_term_ref();
393  std::optional<TimePoint> v_since, v_until;
394  double val = 0.0;
395 
396  if (PL_get_arg(1, scope_val, arg) &&
397  PL_term_type(arg) == PL_FLOAT &&
398  PL_get_float(arg, &val) &&
399  val > 0.001) { frame->begin = val; }
400 
401  if (PL_get_arg(2, scope_val, arg) &&
402  PL_term_type(arg) == PL_FLOAT &&
403  PL_get_float(arg, &val)) { frame->end = val; }
404  }
405 
406  static const auto uncertain_key = PL_new_atom("uncertain");
407  if (PL_get_dict_key(uncertain_key, plTerm(), scope_val)) {
408  atom_t flagAtom;
409  if (PL_term_type(scope_val) == PL_ATOM && PL_get_atom(scope_val, &flagAtom)) {
410  if (flagAtom == PrologTerm::ATOM_true()) {
411  frame->uncertain = true;
412  }
413  }
414  }
415 
416  static const auto confidence_key = PL_new_atom("confidence");
417  if (PL_get_dict_key(confidence_key, plTerm(), scope_val)) {
418  double confidenceValue = 1.0;
419  if (PL_term_type(scope_val) == PL_FLOAT && PL_get_float(scope_val, &confidenceValue)) {
420  frame->confidence = confidenceValue;
421  if (confidenceValue > 0.999) {
422  frame->uncertain = false;
423  } else {
424  frame->uncertain = true;
425  }
426  }
427  }
428 
429  static const auto occasional_key = PL_new_atom("occasional");
430  if (PL_get_dict_key(occasional_key, plTerm(), scope_val)) {
431  atom_t flagAtom;
432  if (PL_term_type(scope_val) == PL_ATOM && PL_get_atom(scope_val, &flagAtom)) {
433  frame->occasional = (flagAtom == PrologTerm::ATOM_true());
434  }
435  }
436 
437  static const auto agent_key = PL_new_atom("agent");
438  if (PL_get_dict_key(agent_key, plTerm(), scope_val)) {
439  atom_t agentAtom;
440  if (PL_term_type(scope_val) == PL_ATOM && PL_get_atom(scope_val, &agentAtom)) {
441  frame->perspective = Perspective::get(PL_atom_chars(agentAtom));
442  }
443  }
444 
445  PL_reset_term_refs(scope_val);
446  } else {
447  KB_WARN("solution scope has an unexpected type (should be dict).");
448  }
449 
450  if (frame) {
451  return frame;
452  } else {
453  return {};
454  }
455 }
456 
457 std::list<TermPtr> PrologReasoner::runTests(const std::string &target) {
458  static const auto xunit_var = std::make_shared<Variable>("Term");
459  static const auto silent_flag = Atom::Tabled("silent");
460  static const auto xunit_opt = std::make_shared<Function>(Function("xunit_term", {xunit_var}));
461 
462  TermPtr pred = std::make_shared<Function>(Function(
463  "test_and_report", {
464  // unittest target
465  Atom::Tabled(target),
466  // options
467  std::make_shared<ListTerm>(ListTerm({xunit_opt, silent_flag}))
468  }));
469  auto solutions = PROLOG_ENGINE_ALL_SOL(getReasonerQuery(PrologTerm(pred)));
470 
471  std::list<TermPtr> output;
472  for (auto &solution: solutions) {
473  if (solution.count(*xunit_var) > 0) {
474  output.push_back(solution[*xunit_var]);
475  } else {
476  KB_WARN("Solution has no key '{}'.", xunit_var->name());
477  }
478  }
479  return output;
480 }
#define PROLOG_REASONER_EVAL(goal)
#define KB_DEBUG
Definition: Logger.h:25
#define KB_ERROR
Definition: Logger.h:28
#define KB_WARN
Definition: Logger.h:27
#define PROLOG_ENGINE_EVAL(term)
Definition: PrologEngine.h:128
#define PROLOG_ENGINE_ALL_SOL(term)
Definition: PrologEngine.h:130
#define KNOWROB_BUILTIN_REASONER(Name, Type)
static std::shared_ptr< knowrob::Atom > Tabled(std::string_view stringForm)
Definition: Atom.cpp:40
void addDataHandler(const std::string &format, const DataSourceLoader &fn)
void enableFeature(GoalDrivenReasonerFeature feature)
static std::shared_ptr< IRIAtom > Tabled(std::string_view stringForm)
Definition: IRIAtom.cpp:25
static std::shared_ptr< Perspective > get(std::string_view iri)
Definition: Perspective.cpp:31
static PluginManager< Reasoner > * getManager(uint32_t managerID)
Definition: PluginManager.h:63
std::shared_ptr< NamedPlugin< T > > getPluginWithID(std::string_view pluginID)
Definition: PluginManager.h:76
static PrefixRegistry & get()
static void initializeProlog()
static std::filesystem::path getPrologPath(const std::filesystem::path &filename)
static void pushGoal(const std::shared_ptr< ThreadPool::Runner > &goal, const ErrorHandler &errHandler)
static bool eval(const GoalFactory &goalFactory)
static std::filesystem::path getResourcePath(const std::filesystem::path &filename)
virtual void initializeReasonerStorage()
std::list< TermPtr > runTests(const std::string &target)
bool load_rdf_xml(const std::filesystem::path &rdfFile)
bool setReasonerSetting(const TermPtr &key, const TermPtr &valueString)
AnswerNoPtr no(const GoalPtr &query)
virtual std::string_view callFunctor()
virtual bool initializeGlobalPackages()
static bool isKnowRobInitialized_
PrologTerm transformGoal(const PrologTerm &goal)
bool consult(const std::filesystem::path &uri, const char *module={}, bool doTransformQuery=true)
AnswerYesPtr yes(const GoalPtr &query, const PrologTerm &rdfGoal, const PrologTerm &frameTerm)
virtual bool initializeDefaultPackages()
PrologTerm getReasonerQuery(const PrologTerm &goal)
static std::shared_ptr< NamedReasoner > getDefinedReasoner(const term_t &t_reasonerManager, const term_t &t_reasonerModule)
bool initializeReasoner(const PropertyTree &cfg) override
static bool putQueryFrame(PrologTerm &frameTerm, const GraphSelector &frame)
static std::shared_ptr< GraphSelector > createAnswerFrame(const PrologTerm &plTerm)
bool evaluate(GoalPtr query) override
static PrologTerm nil()
Definition: PrologTerm.cpp:90
void setModule(std::string_view module)
Definition: PrologTerm.h:166
static bool nextSolution(qid_t qid)
Definition: PrologTerm.cpp:163
static const atom_t & ATOM_true()
Definition: PrologTerm.cpp:881
TermPtr toKnowRobTerm() const
Definition: PrologTerm.cpp:691
auto & vars() const
Definition: PrologTerm.h:206
TermPtr createKeyTerm(std::string_view key) const
ReasonerManager & reasonerManager() const
Definition: Reasoner.cpp:24
auto & reasonerName() const
Definition: Reasoner.h:37
std::function< bool()> StopChecker
Definition: ThreadPool.h:152
TermRule & string()
Definition: terms.cpp:63
GraphSelectorPtr DefaultGraphSelector()
@ QUERY_FLAG_ONE_SOLUTION
Definition: QueryFlag.h:17
std::shared_ptr< Term > TermPtr
Definition: Term.h:117
std::shared_ptr< const GraphSelector > GraphSelectorPtr
Definition: GraphSelector.h:76
FirstOrderLiteralPtr applyBindings(const FirstOrderLiteralPtr &lit, const Bindings &bindings)
std::shared_ptr< const AnswerYes > AnswerYesPtr
Definition: AnswerYes.h:108
std::shared_ptr< DataSource > DataSourcePtr
Definition: DataSource.h:107
std::shared_ptr< Goal > GoalPtr
Definition: Goal.h:84
std::shared_ptr< const AnswerNo > AnswerNoPtr
Definition: AnswerNo.h:74
std::optional< double > end
Definition: GraphSelector.h:46
PerspectivePtr perspective
Definition: GraphSelector.h:30
std::optional< double > begin
Definition: GraphSelector.h:42