Fawkes API  Fawkes Development Version
stn.cpp
1 
2 /***************************************************************************
3  * stn.cpp - stn-generator
4  *
5  * Created: Sat May 6 20:16:21 2017
6  * Copyright 2017 Matthias Loebach
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Library General Public License for more details.
18  *
19  * Read the full text in the LICENSE.GPL file in the doc directory.
20  */
21 
22 #include "stn.h"
23 
24 #include <pddl_parser/pddl_parser.h>
25 
26 #include <bsoncxx/builder/basic/document.hpp>
27 #include <exception>
28 #include <fstream>
29 #include <iostream>
30 
31 namespace fawkes {
32 namespace stn {
33 
34 /** @class Stn "stn.h"
35  * A Simple Temporal Network.
36  */
37 
38 /** Constructor.
39  * @param logger The logger to log to.
40  */
41 Stn::Stn(fawkes::Logger *logger) : logger_(logger)
42 {
43 }
44 
45 /** Constructor.
46  * @param logger The logger to log to.
47  * @param classic_dom_path The path to the domain file to write to.
48  */
49 Stn::Stn(fawkes::Logger *logger, const std::string &classic_dom_path)
50 : logger_(logger), classic_dom_path_(classic_dom_path)
51 {
52  gen_classic_dom_ = true;
53 }
54 
55 /** Destructor */
57 {
58 }
59 
60 /** Add the given DomainAction to the STN.
61  * @param action The action to add.
62  */
63 void
64 Stn::add_domain_action(const DomainAction &action)
65 {
66  domain_actions_.push_back(action);
67 }
68 
69 /** Add a (grounded action).
70  * @param name The name of the action/operator.
71  * @param params The parameters of the action.
72  */
73 void
74 Stn::add_plan_action(const std::string &name, const std::string &params)
75 {
76  plan_actions_.push_back(plan_action{name, params});
77 }
78 
79 /** Set the initial state.
80  * The resulting initial state is the state after applying the effects of the
81  * given action.
82  * @param action The action whose effects define the initial state.
83  */
84 void
86 {
87  initial_state_ = action;
88 }
89 
90 /** Read the initial state from the given PDDL problem.
91  * @param pddl_problem_string the PDDL rpboelm as (unparsed) string.
92  */
93 void
94 Stn::read_initial_state(const std::string &pddl_problem_string)
95 {
97  pddl_parser::Problem prob = parser.parseProblem(pddl_problem_string);
98 
99  log_info("Parsing PDDL Problem for STN generation.");
100 
101  log_info("Parsed problem " + prob.name);
102  std::vector<stn::Predicate> init_predicates;
103  for (pddl_parser::Expression pred : prob.init) {
104  std::vector<std::string> attrs;
105  std::string log_string = "Adding init-predicate "
106  + boost::get<pddl_parser::Predicate>(pred.expression).function
107  + " with arguments:";
108  for (pddl_parser::Expression a :
109  boost::get<pddl_parser::Predicate>(pred.expression).arguments) {
110  attrs.push_back(boost::get<pddl_parser::Atom>(a.expression));
111  log_string += " " + boost::get<pddl_parser::Atom>(a.expression);
112  }
113  log_info(log_string);
114  stn::Predicate init_pred(boost::get<pddl_parser::Predicate>(pred.expression).function,
115  true,
116  attrs);
117  init_predicates.push_back(init_pred);
118  }
119  stn::StnAction init_action(prob.name, {}, init_predicates, std::string(""));
120  set_initial_state(init_action);
121 }
122 
123 /** Set the domain of the STN to the given PDDL domain.
124  * This parses the given domain and processes all actions in the domain.
125  * It also adds all temporal and conditional breakups defined in the domain to
126  * the STN.
127  * @param pddl_domain_string the PDDL domain as (unparsed) string.
128  */
129 void
130 Stn::set_pddl_domain(const std::string &pddl_domain_string)
131 {
133  pddl_parser::Domain dom = parser.parseDomain(pddl_domain_string);
134 
135  log_info("Loading extended PDDL domain into STN.");
136 
137  for (auto &action : dom.actions) {
138  log_info("Processing action " + action.name);
139  std::vector<std::string> params;
140  for (auto &param : action.action_params) {
141  params.push_back(param.first);
142  }
143  std::vector<Predicate> preconds;
144  build_pred_list(action.precondition, &preconds, true);
145  std::vector<Predicate> effects;
146  build_pred_list(action.effect, &effects, true);
147  int duration = (int)std::stof(boost::get<pddl_parser::Atom>(action.duration.expression));
148  std::vector<std::string> cond_breakups;
149  log_info(std::to_string(action.cond_breakup.expression.which()));
150  if (action.cond_breakup.expression.which() == 1) { // only if type is Expression
151  build_breakup_list(action.cond_breakup, &cond_breakups);
152  }
153  std::vector<std::string> temp_breakups;
154  if (action.temp_breakup.expression.which() == 1) { // only if type is Expression
155  build_breakup_list(action.temp_breakup, &temp_breakups);
156  }
157  DomainAction da(action.name, params, preconds, effects, duration, cond_breakups, temp_breakups);
158  domain_actions_.push_back(da);
159  }
160 
161  log_info("Initialized " + std::to_string(domain_actions_.size()) + " domain actions");
162 
163  if (gen_classic_dom_) {
164  log_info("Generation of classic domain file is configured, starting...");
165  generate_classic_pddl_domain(&dom, classic_dom_path_);
166  }
167 }
168 
169 /* For now this only works with the not and and operators
170  * to combine multiple predicates
171  */
172 void
173 Stn::build_pred_list(pddl_parser::Expression e, std::vector<Predicate> *preconds, bool condition)
174 {
175  pddl_parser::Atom function = boost::get<pddl_parser::Predicate>(e.expression).function;
176  if (function == "and" || function == "not") {
177  if (function == "not") {
178  condition = !condition;
179  }
180  for (auto &child : boost::get<pddl_parser::Predicate>(e.expression).arguments) {
181  build_pred_list(child, preconds, condition);
182  }
183  } else {
184  std::vector<std::string> args;
185  for (auto &arg : boost::get<pddl_parser::Predicate>(e.expression).arguments) {
186  args.push_back(boost::get<std::string>(arg.expression));
187  }
188  Predicate p(boost::get<pddl_parser::Predicate>(e.expression).function, condition, args);
189  preconds->push_back(p);
190  log_info("Added " + boost::get<pddl_parser::Predicate>(e.expression).function);
191  }
192 }
193 
194 void
195 Stn::build_breakup_list(pddl_parser::Expression e, std::vector<std::string> *breakups)
196 {
197  pddl_parser::Atom function = boost::get<pddl_parser::Predicate>(e.expression).function;
198  // ignore negations, we only take the name into account
199  if (function == "and" || function == "not") {
200  for (auto &child : boost::get<pddl_parser::Predicate>(e.expression).arguments) {
201  build_breakup_list(child, breakups);
202  }
203  } else {
204  std::string pred_name = boost::get<pddl_parser::Predicate>(e.expression).function;
205  std::cout << "Adding breakup " << pred_name << std::endl;
206  breakups->push_back(pred_name);
207  }
208 }
209 
210 /** Regenerate the STN. */
211 void
213 {
214  stn_actions_.clear();
215  //stn_actions_.push_back(initial_state_);
216 
217  for (plan_action pa : plan_actions_) {
218  std::vector<DomainAction>::iterator it = domain_actions_.begin();
219  for (; it != domain_actions_.end(); ++it) {
220  if (it->getName() == pa.name) {
221  break;
222  }
223  }
224  if (it == domain_actions_.end())
225  throw("could not find fitting DomainAction");
226  DomainAction da = *(it);
227 
228  stn_actions_.push_back(da.generateStnAction(pa.name, pa.params));
229  }
230  std::cout << "Imported " << stn_actions_.size() << " actions into STN" << std::endl;
231 
232  for (int i = stn_actions_.size() - 1; i >= 0; i--) {
233  std::vector<StnAction> candidate_actions =
234  std::vector<StnAction>(stn_actions_.begin(), stn_actions_.begin() + i);
235  try {
236  stn_actions_.at(i).genConditionalActions(candidate_actions);
237  } catch (std::exception &e) {
238  std::cout << "ERROR stn.cpp:" << e.what() << std::endl;
239  }
240  }
241 
242  std::vector<Predicate> predicates;
243  for (std::vector<StnAction>::iterator it = stn_actions_.begin(); it != stn_actions_.end(); ++it) {
244  // add conditional edges
245  for (auto const &cond_action : it->condActionIds()) {
246  std::pair<StnAction, StnAction> edge(findActionById(cond_action), findActionById(it->id()));
247  cond_edges_.push_back(edge);
248  }
249  // add temporal edges
250  bool break_edge = false;
251  for (Predicate p : predicates) {
252  if (it->checkForBreakup(EdgeType::TEMPORAL, p)) {
253  break_edge = true;
254  break;
255  }
256  }
257  if (!break_edge && it != stn_actions_.begin()) {
258  std::pair<StnAction, StnAction> edge(findActionById((it - 1)->id()),
259  findActionById(it->id()));
260  temp_edges_.push_back(edge);
261  }
262  // handle predicates
263  for (Predicate p : it->effects()) {
264  if (p.condition()) {
265  std::vector<Predicate>::iterator it = std::find(predicates.begin(), predicates.end(), p);
266  if (it == predicates.end()) {
267  predicates.push_back(p);
268  //std::cout << "Added " << p;
269  }
270  } else {
271  //std::cout << "Check for erase: " << p;
272  Predicate neg_pred(p.name(), true, p.attrs());
273  std::vector<Predicate>::iterator it =
274  std::find(predicates.begin(), predicates.end(), neg_pred);
275  if (it != predicates.end()) {
276  //std::cout << "Erased " << (*it);
277  predicates.erase(it);
278  }
279  }
280  }
281  }
282  //generate missing temporal links
283  for (auto &a : stn_actions_) {
284  bool no_temp_edge = true;
285  for (auto &e : temp_edges_) {
286  if (e.first == a) {
287  no_temp_edge = false;
288  break;
289  }
290  }
291  if (no_temp_edge) {
292  for (auto &ce : cond_edges_) {
293  if (ce.first == a) {
294  std::pair<StnAction, StnAction> edge(a, ce.second);
295  temp_edges_.push_back(edge);
296  }
297  }
298  }
299  }
300 }
301 
302 /** Render a graph representation of the STN.
303  * This writes the graph representation to the file stn.png.
304  */
305 void
307 {
308  Agraph_t *G;
309  GVC_t * gvc;
310 
311  gvc = gvContext();
312  char graph_name[] = "STN";
313 
314  G = agopen(graph_name, Agdirected, 0);
315 
316  std::map<size_t, Agnode_t *> node_map;
317 
318  for (StnAction a : stn_actions_) {
319  std::string node_name = a.genGraphNodeName();
320  node_map.insert(std::make_pair(a.id(), agnode(G, (char *)node_name.c_str(), true)));
321  }
322 
323  std::vector<Agedge_t *> edge_list;
324  for (auto &edge : cond_edges_) {
325  Agnode_t *node1 = node_map.at(edge.first.id());
326  Agnode_t *node2 = node_map.at(edge.second.id());
327  Agedge_t *graph_edge = agedge(G, node1, node2, (char *)"conditional", true);
328  edge_list.push_back(graph_edge);
329 
330  std::string edge_label = edge.second.genConditionEdgeLabel(edge.first.id());
331  agsafeset(graph_edge,
332  (char *)"label",
333  agstrdup_html(G, (char *)edge_label.c_str()),
334  (char *)"");
335  agsafeset(graph_edge, (char *)"color", (char *)"red", (char *)"");
336  }
337 
338  for (auto &edge : temp_edges_) {
339  Agnode_t *node1 = node_map.at(edge.first.id());
340  Agnode_t *node2 = node_map.at(edge.second.id());
341  Agedge_t *graph_edge = agedge(G, node1, node2, (char *)"temporal", true);
342  edge_list.push_back(graph_edge);
343 
344  std::string edge_label = edge.second.genTemporalEdgeLabel();
345  agsafeset(graph_edge,
346  (char *)"label",
347  agstrdup_html(G, (char *)edge_label.c_str()),
348  (char *)"");
349  agsafeset(graph_edge, (char *)"color", (char *)"blue", (char *)"");
350  }
351 
352  gvLayout(gvc, G, "dot");
353  gvRenderFilename(gvc, G, "png", "stn.png");
354 
355  gvFreeLayout(gvc, G);
356  agclose(G);
357  gvFreeContext(gvc);
358 }
359 
360 /** Get a BSON representation of the STN.
361  * @return A vector of BSON objects, each element is an action.
362  */
363 std::vector<bsoncxx::document::value>
365 {
366  std::vector<bsoncxx::document::value> stn;
367  for (auto &action : stn_actions_) {
368  using namespace bsoncxx::builder;
369  basic::document bson_action;
370  bson_action.append(basic::kvp("id", static_cast<int64_t>(action.id())));
371  bson_action.append(basic::kvp("name", action.name()));
372  bson_action.append(basic::kvp("duration", static_cast<int64_t>(action.duration())));
373  bson_action.append(basic::kvp("cond-actions", [action](basic::sub_array cond_actions) {
374  for (auto &cond : action.condActionIds()) {
375  cond_actions.append(static_cast<int64_t>(cond));
376  }
377  }));
378  bson_action.append(basic::kvp("opts", [action](basic::sub_array opts) {
379  std::stringstream opts_ss(action.opts());
380  std::istream_iterator<std::string> end;
381  for (std::istream_iterator<std::string> it(opts_ss); it != end; it++) {
382  opts.append(*it);
383  }
384  }));
385  stn.push_back(bson_action.extract());
386  }
387  return stn;
388 }
389 
390 StnAction
391 Stn::findActionById(size_t id)
392 {
393  for (StnAction a : stn_actions_) {
394  if (a.id() == id) {
395  return a;
396  }
397  }
398  throw(" Action with id " + std::to_string(id) + " not found");
399 }
400 
401 void
402 Stn::log_warn(const std::string &s)
403 {
404  log(s, LogLevel::WARN);
405 }
406 
407 void
408 Stn::log_info(const std::string &s)
409 {
410  log(s, LogLevel::INFO);
411 }
412 
413 void
414 Stn::log_debug(const std::string &s)
415 {
416  log(s, LogLevel::DEBUG);
417 }
418 void
419 Stn::log(const std::string &s, Stn::LogLevel log_level)
420 {
421  std::string name = "STN";
422  switch (log_level) {
423  case LogLevel::WARN: logger_->log_warn(name.c_str(), "%s", s.c_str()); break;
424  case LogLevel::INFO: logger_->log_info(name.c_str(), "%s", s.c_str()); break;
425  case LogLevel::DEBUG: logger_->log_debug(name.c_str(), "%s", s.c_str()); break;
426  }
427 }
428 
429 void
430 Stn::generate_classic_pddl_domain(pddl_parser::Domain *dom, const std::string &classic_dom_path)
431 {
432  log_info("Writing domain to " + classic_dom_path);
433  std::ofstream out(classic_dom_path);
434 
435  out << "(define (domain " << dom->name << ")" << std::endl;
436 
437  out << "\t(:requirements";
438  for (auto &req : dom->requirements) {
439  out << " :" << req;
440  }
441  out << ")" << std::endl;
442 
443  out << "\t(:types" << std::endl;
444  for (auto &type : dom->types) {
445  out << "\t\t" << type.first << " - " << type.second << std::endl;
446  }
447  out << "\t)" << std::endl;
448 
449  out << "\t(:constants" << std::endl;
450  for (auto &const_type : dom->constants) {
451  out << "\t\t";
452  for (auto &constant : const_type.first) {
453  out << constant << " ";
454  }
455  out << "- " << const_type.second << std::endl;
456  }
457  out << "\t)" << std::endl;
458 
459  out << "\t(:predicates" << std::endl;
460  for (auto &predicate : dom->predicates) {
461  out << "\t\t(" << predicate.first;
462  for (auto &pred_type : predicate.second) {
463  out << " ?" << pred_type.first << " - " << pred_type.second;
464  }
465  out << ")" << std::endl;
466  }
467  out << "\t)" << std::endl;
468 
469  for (auto &action : dom->actions) {
470  out << "\t(:action " << action.name << std::endl;
471  out << "\t\t:parameters (";
472  for (auto &param : action.action_params) {
473  out << " ?" << param.first << " - " << param.second;
474  }
475  out << ")" << std::endl;
476  out << "\t\t:precondition" << std::endl << "\t\t\t";
477  output_pred_list(action.precondition, out);
478 
479  out << std::endl << "\t\t:effect" << std::endl << "\t\t\t";
480  output_pred_list(action.effect, out);
481 
482  out << std::endl << "\t)" << std::endl;
483  }
484 
485  out << ")";
486 
487  out.close();
488 }
489 
490 void
491 Stn::output_pred_list(pddl_parser::Expression e, std::ofstream &out)
492 {
493  pddl_parser::Atom function = boost::get<pddl_parser::Predicate>(e.expression).function;
494  if (function == "not" || function == "and") {
495  if (function == "not") {
496  out << "(not ";
497  } else if (function == "and") {
498  out << "(and ";
499  }
500  for (auto &child : boost::get<pddl_parser::Predicate>(e.expression).arguments) {
501  output_pred_list(child, out);
502  }
503  out << ") ";
504  } else {
505  out << "(" << boost::get<pddl_parser::Predicate>(e.expression).function;
506  for (auto &arg : boost::get<pddl_parser::Predicate>(e.expression).arguments) {
507  out << " " << boost::get<std::string>(arg.expression);
508  }
509  out << ")";
510  }
511 }
512 
513 } // namespace stn
514 } // namespace fawkes
Interface for logging.
Definition: logger.h:42
virtual void log_debug(const char *component, const char *format,...)=0
Log debug message.
virtual void log_warn(const char *component, const char *format,...)=0
Log warning message.
virtual void log_info(const char *component, const char *format,...)=0
Log informational message.
A representation of an action used by the STN generator.
Definition: domain_action.h:40
StnAction generateStnAction(const std::string &name, const std::string &params)
Generate an StnAction from the DomainAction.
A representation of a Predicate in the STN.
Definition: predicate.h:33
An action representation within an STN.
Definition: stn_action.h:41
Stn(fawkes::Logger *logger)
Constructor.
Definition: stn.cpp:41
void add_plan_action(const std::string &name, const std::string &params)
Add a (grounded action).
Definition: stn.cpp:74
virtual ~Stn()
Destructor.
Definition: stn.cpp:56
void set_pddl_domain(const std::string &pddl_domain_string)
Set the domain of the STN to the given PDDL domain.
Definition: stn.cpp:130
std::vector< bsoncxx::document::value > get_bson()
Get a BSON representation of the STN.
Definition: stn.cpp:364
void read_initial_state(const std::string &pddl_problem_string)
Read the initial state from the given PDDL problem.
Definition: stn.cpp:94
void generate()
Regenerate the STN.
Definition: stn.cpp:212
void set_initial_state(const StnAction &action)
Set the initial state.
Definition: stn.cpp:85
void drawGraph()
Render a graph representation of the STN.
Definition: stn.cpp:306
Parse a PDDL domain file or problem.
Definition: pddl_parser.h:34
static Domain parseDomain(const std::string &pddl_domain)
Parse the PDDL domain.
Definition: pddl_parser.cpp:72
static Problem parseProblem(const std::string &pddl_problem)
Parse the PDDL problem.
Fawkes library namespace.
A structured representation of a PDDL domain.
Definition: pddl_ast.h:157
std::vector< Action > actions
A list of actions defined in the domain.
Definition: pddl_ast.h:173
std::string name
The name of the domain.
Definition: pddl_ast.h:159
pairs_multi_consts constants
A typed list of constants defined in the domain.
Definition: pddl_ast.h:165
pairs_type types
A list of types with their super types.
Definition: pddl_ast.h:163
std::vector< predicate_type > predicates
A list of predicate names in the domain, including the types of their arguments.
Definition: pddl_ast.h:169
std::vector< std::string > requirements
A list of PDDL features required by the domain.
Definition: pddl_ast.h:161
A PDDL Expression.
Definition: pddl_ast.h:78
expression_t expression
The expression formula.
Definition: pddl_ast.h:82
A structured representation of a PDDL problem.
Definition: pddl_ast.h:180
std::string name
The name of the problem.
Definition: pddl_ast.h:182
std::vector< Expression > init
A list of facts that are initially true.
Definition: pddl_ast.h:188