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 */
56 Stn::~Stn()
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
85 Stn::set_initial_state(const StnAction &action)
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).function
107  + " with arguments:";
108  for (pddl_parser::Expression a : boost::get<pddl_parser::Predicate>(pred).arguments) {
109  attrs.push_back(boost::get<pddl_parser::Atom>(a));
110  log_string += " " + boost::get<pddl_parser::Atom>(a);
111  }
112  log_info(log_string);
113  stn::Predicate init_pred(boost::get<pddl_parser::Predicate>(pred).function, true, attrs);
114  init_predicates.push_back(init_pred);
115  }
116  stn::StnAction init_action(prob.name, {}, init_predicates, std::string(""));
117  set_initial_state(init_action);
118 }
119 
120 /** Set the domain of the STN to the given PDDL domain.
121  * This parses the given domain and processes all actions in the domain.
122  * It also adds all temporal and conditional breakups defined in the domain to
123  * the STN.
124  * @param pddl_domain_string the PDDL domain as (unparsed) string.
125  */
126 void
127 Stn::set_pddl_domain(const std::string &pddl_domain_string)
128 {
130  pddl_parser::Domain dom = parser.parseDomain(pddl_domain_string);
131 
132  log_info("Loading extended PDDL domain into STN.");
133 
134  for (auto &action : dom.actions) {
135  log_info("Processing action " + action.name);
136  std::vector<std::string> params;
137  for (auto &param : action.action_params) {
138  params.push_back(param.first);
139  }
140  std::vector<Predicate> preconds;
141  build_pred_list(action.precondition, &preconds, true);
142  std::vector<Predicate> effects;
143  build_pred_list(action.effect, &effects, true);
144  int duration = action.duration;
145  std::vector<std::string> cond_breakups;
146  log_info(std::to_string(action.cond_breakup.which()));
147  if (action.cond_breakup.which() == 1) { // only if type is Expression
148  build_breakup_list(action.cond_breakup, &cond_breakups);
149  }
150  std::vector<std::string> temp_breakups;
151  if (action.temp_breakup.which() == 1) { // only if type is Expression
152  build_breakup_list(action.temp_breakup, &temp_breakups);
153  }
154  DomainAction da(action.name, params, preconds, effects, duration, cond_breakups, temp_breakups);
155  domain_actions_.push_back(da);
156  }
157 
158  log_info("Initialized " + std::to_string(domain_actions_.size()) + " domain actions");
159 
160  if (gen_classic_dom_) {
161  log_info("Generation of classic domain file is configured, starting...");
162  generate_classic_pddl_domain(&dom, classic_dom_path_);
163  }
164 }
165 
166 /* For now this only works with the not and and operators
167  * to combine multiple predicates
168  */
169 void
170 Stn::build_pred_list(pddl_parser::Expression e, std::vector<Predicate> *preconds, bool condition)
171 {
172  pddl_parser::Atom function = boost::get<pddl_parser::Predicate>(e).function;
173  if (function == "and" || function == "not") {
174  if (function == "not") {
175  condition = !condition;
176  }
177  for (auto &child : boost::get<pddl_parser::Predicate>(e).arguments) {
178  build_pred_list(child, preconds, condition);
179  }
180  } else {
181  std::vector<std::string> args;
182  for (auto &arg : boost::get<pddl_parser::Predicate>(e).arguments) {
183  args.push_back(boost::get<std::string>(arg));
184  }
185  Predicate p(boost::get<pddl_parser::Predicate>(e).function, condition, args);
186  preconds->push_back(p);
187  log_info("Added " + boost::get<pddl_parser::Predicate>(e).function);
188  }
189 }
190 
191 void
192 Stn::build_breakup_list(pddl_parser::Expression e, std::vector<std::string> *breakups)
193 {
194  pddl_parser::Atom function = boost::get<pddl_parser::Predicate>(e).function;
195  // ignore negations, we only take the name into account
196  if (function == "and" || function == "not") {
197  for (auto &child : boost::get<pddl_parser::Predicate>(e).arguments) {
198  build_breakup_list(child, breakups);
199  }
200  } else {
201  std::string pred_name = boost::get<pddl_parser::Predicate>(e).function;
202  std::cout << "Adding breakup " << pred_name << std::endl;
203  breakups->push_back(pred_name);
204  }
205 }
206 
207 /** Regenerate the STN. */
208 void
209 Stn::generate()
210 {
211  stn_actions_.clear();
212  //stn_actions_.push_back(initial_state_);
213 
214  for (plan_action pa : plan_actions_) {
215  std::vector<DomainAction>::iterator it = domain_actions_.begin();
216  for (; it != domain_actions_.end(); ++it) {
217  if (it->getName() == pa.name) {
218  break;
219  }
220  }
221  if (it == domain_actions_.end())
222  throw("could not find fitting DomainAction");
223  DomainAction da = *(it);
224 
225  stn_actions_.push_back(da.generateStnAction(pa.name, pa.params));
226  }
227  std::cout << "Imported " << stn_actions_.size() << " actions into STN" << std::endl;
228 
229  for (int i = stn_actions_.size() - 1; i >= 0; i--) {
230  std::vector<StnAction> candidate_actions =
231  std::vector<StnAction>(stn_actions_.begin(), stn_actions_.begin() + i);
232  try {
233  stn_actions_.at(i).genConditionalActions(candidate_actions);
234  } catch (std::exception &e) {
235  std::cout << "ERROR stn.cpp:" << e.what() << std::endl;
236  }
237  }
238 
239  std::vector<Predicate> predicates;
240  for (std::vector<StnAction>::iterator it = stn_actions_.begin(); it != stn_actions_.end(); ++it) {
241  // add conditional edges
242  for (auto const &cond_action : it->condActionIds()) {
243  std::pair<StnAction, StnAction> edge(findActionById(cond_action), findActionById(it->id()));
244  cond_edges_.push_back(edge);
245  }
246  // add temporal edges
247  bool break_edge = false;
248  for (Predicate p : predicates) {
249  if (it->checkForBreakup(EdgeType::TEMPORAL, p)) {
250  break_edge = true;
251  break;
252  }
253  }
254  if (!break_edge && it != stn_actions_.begin()) {
255  std::pair<StnAction, StnAction> edge(findActionById((it - 1)->id()),
256  findActionById(it->id()));
257  temp_edges_.push_back(edge);
258  }
259  // handle predicates
260  for (Predicate p : it->effects()) {
261  if (p.condition()) {
262  std::vector<Predicate>::iterator it = std::find(predicates.begin(), predicates.end(), p);
263  if (it == predicates.end()) {
264  predicates.push_back(p);
265  //std::cout << "Added " << p;
266  }
267  } else {
268  //std::cout << "Check for erase: " << p;
269  Predicate neg_pred(p.name(), true, p.attrs());
270  std::vector<Predicate>::iterator it =
271  std::find(predicates.begin(), predicates.end(), neg_pred);
272  if (it != predicates.end()) {
273  //std::cout << "Erased " << (*it);
274  predicates.erase(it);
275  }
276  }
277  }
278  }
279  //generate missing temporal links
280  for (auto &a : stn_actions_) {
281  bool no_temp_edge = true;
282  for (auto &e : temp_edges_) {
283  if (e.first == a) {
284  no_temp_edge = false;
285  break;
286  }
287  }
288  if (no_temp_edge) {
289  for (auto &ce : cond_edges_) {
290  if (ce.first == a) {
291  std::pair<StnAction, StnAction> edge(a, ce.second);
292  temp_edges_.push_back(edge);
293  }
294  }
295  }
296  }
297 }
298 
299 /** Render a graph representation of the STN.
300  * This writes the graph representation to the file stn.png.
301  */
302 void
303 Stn::drawGraph()
304 {
305  Agraph_t *G;
306  GVC_t * gvc;
307 
308  gvc = gvContext();
309  char graph_name[] = "STN";
310 
311  G = agopen(graph_name, Agdirected, 0);
312 
313  std::map<size_t, Agnode_t *> node_map;
314 
315  for (StnAction a : stn_actions_) {
316  std::string node_name = a.genGraphNodeName();
317  node_map.insert(std::make_pair(a.id(), agnode(G, (char *)node_name.c_str(), true)));
318  }
319 
320  std::vector<Agedge_t *> edge_list;
321  for (auto &edge : cond_edges_) {
322  Agnode_t *node1 = node_map.at(edge.first.id());
323  Agnode_t *node2 = node_map.at(edge.second.id());
324  Agedge_t *graph_edge = agedge(G, node1, node2, (char *)"conditional", true);
325  edge_list.push_back(graph_edge);
326 
327  std::string edge_label = edge.second.genConditionEdgeLabel(edge.first.id());
328  agsafeset(graph_edge,
329  (char *)"label",
330  agstrdup_html(G, (char *)edge_label.c_str()),
331  (char *)"");
332  agsafeset(graph_edge, (char *)"color", (char *)"red", (char *)"");
333  }
334 
335  for (auto &edge : temp_edges_) {
336  Agnode_t *node1 = node_map.at(edge.first.id());
337  Agnode_t *node2 = node_map.at(edge.second.id());
338  Agedge_t *graph_edge = agedge(G, node1, node2, (char *)"temporal", true);
339  edge_list.push_back(graph_edge);
340 
341  std::string edge_label = edge.second.genTemporalEdgeLabel();
342  agsafeset(graph_edge,
343  (char *)"label",
344  agstrdup_html(G, (char *)edge_label.c_str()),
345  (char *)"");
346  agsafeset(graph_edge, (char *)"color", (char *)"blue", (char *)"");
347  }
348 
349  gvLayout(gvc, G, "dot");
350  gvRenderFilename(gvc, G, "png", "stn.png");
351 
352  gvFreeLayout(gvc, G);
353  agclose(G);
354  gvFreeContext(gvc);
355 }
356 
357 /** Get a BSON representation of the STN.
358  * @return A vector of BSON objects, each element is an action.
359  */
360 std::vector<bsoncxx::document::value>
361 Stn::get_bson()
362 {
363  std::vector<bsoncxx::document::value> stn;
364  for (auto &action : stn_actions_) {
365  using namespace bsoncxx::builder;
366  basic::document bson_action;
367  bson_action.append(basic::kvp("id", static_cast<int64_t>(action.id())));
368  bson_action.append(basic::kvp("name", action.name()));
369  bson_action.append(basic::kvp("duration", static_cast<int64_t>(action.duration())));
370  bson_action.append(basic::kvp("cond-actions", [action](basic::sub_array cond_actions) {
371  for (auto &cond : action.condActionIds()) {
372  cond_actions.append(static_cast<int64_t>(cond));
373  }
374  }));
375  bson_action.append(basic::kvp("opts", [action](basic::sub_array opts) {
376  std::stringstream opts_ss(action.opts());
377  std::istream_iterator<std::string> end;
378  for (std::istream_iterator<std::string> it(opts_ss); it != end; it++) {
379  opts.append(*it);
380  }
381  }));
382  stn.push_back(bson_action.extract());
383  }
384  return stn;
385 }
386 
387 StnAction
388 Stn::findActionById(size_t id)
389 {
390  for (StnAction a : stn_actions_) {
391  if (a.id() == id) {
392  return a;
393  }
394  }
395  throw(" Action with id " + std::to_string(id) + " not found");
396 }
397 
398 void
399 Stn::log_warn(const std::string &s)
400 {
401  log(s, LogLevel::WARN);
402 }
403 
404 void
405 Stn::log_info(const std::string &s)
406 {
407  log(s, LogLevel::INFO);
408 }
409 
410 void
411 Stn::log_debug(const std::string &s)
412 {
413  log(s, LogLevel::DEBUG);
414 }
415 void
416 Stn::log(const std::string &s, Stn::LogLevel log_level)
417 {
418  std::string name = "STN";
419  switch (log_level) {
420  case LogLevel::WARN: logger_->log_warn(name.c_str(), "%s", s.c_str()); break;
421  case LogLevel::INFO: logger_->log_info(name.c_str(), "%s", s.c_str()); break;
422  case LogLevel::DEBUG: logger_->log_debug(name.c_str(), "%s", s.c_str()); break;
423  }
424 }
425 
426 void
427 Stn::generate_classic_pddl_domain(pddl_parser::Domain *dom, const std::string &classic_dom_path)
428 {
429  log_info("Writing domain to " + classic_dom_path);
430  std::ofstream out(classic_dom_path);
431 
432  out << "(define (domain " << dom->name << ")" << std::endl;
433 
434  out << "\t(:requirements";
435  for (auto &req : dom->requirements) {
436  out << " :" << req;
437  }
438  out << ")" << std::endl;
439 
440  out << "\t(:types" << std::endl;
441  for (auto &type : dom->types) {
442  out << "\t\t" << type.first << " - " << type.second << std::endl;
443  }
444  out << "\t)" << std::endl;
445 
446  out << "\t(:constants" << std::endl;
447  for (auto &const_type : dom->constants) {
448  out << "\t\t";
449  for (auto &constant : const_type.first) {
450  out << constant << " ";
451  }
452  out << "- " << const_type.second << std::endl;
453  }
454  out << "\t)" << std::endl;
455 
456  out << "\t(:predicates" << std::endl;
457  for (auto &predicate : dom->predicates) {
458  out << "\t\t(" << predicate.first;
459  for (auto &pred_type : predicate.second) {
460  out << " ?" << pred_type.first << " - " << pred_type.second;
461  }
462  out << ")" << std::endl;
463  }
464  out << "\t)" << std::endl;
465 
466  for (auto &action : dom->actions) {
467  out << "\t(:action " << action.name << std::endl;
468  out << "\t\t:parameters (";
469  for (auto &param : action.action_params) {
470  out << " ?" << param.first << " - " << param.second;
471  }
472  out << ")" << std::endl;
473  out << "\t\t:precondition" << std::endl << "\t\t\t";
474  output_pred_list(action.precondition, out);
475 
476  out << std::endl << "\t\t:effect" << std::endl << "\t\t\t";
477  output_pred_list(action.effect, out);
478 
479  out << std::endl << "\t)" << std::endl;
480  }
481 
482  out << ")";
483 
484  out.close();
485 }
486 
487 void
488 Stn::output_pred_list(pddl_parser::Expression e, std::ofstream &out)
489 {
490  pddl_parser::Atom function = boost::get<pddl_parser::Predicate>(e).function;
491  if (function == "not" || function == "and") {
492  if (function == "not") {
493  out << "(not ";
494  } else if (function == "and") {
495  out << "(and ";
496  }
497  for (auto &child : boost::get<pddl_parser::Predicate>(e).arguments) {
498  output_pred_list(child, out);
499  }
500  out << ") ";
501  } else {
502  out << "(" << boost::get<pddl_parser::Predicate>(e).function;
503  for (auto &arg : boost::get<pddl_parser::Predicate>(e).arguments) {
504  out << " " << boost::get<std::string>(arg);
505  }
506  out << ")";
507  }
508 }
509 
510 } // namespace stn
511 } // namespace fawkes
pddl_parser::Domain
Definition: pddl_ast.h:104
pddl_parser::Problem
Definition: pddl_ast.h:125
pddl_parser::Domain::name
std::string name
The name of the domain.
Definition: pddl_ast.h:107
pddl_parser::PddlParser::parseDomain
static Domain parseDomain(const std::string pddl_domain)
Parse the PDDL domain.
Definition: pddl_parser.cpp:52
pddl_parser::Domain::types
pairs_type types
A list of types with their super types.
Definition: pddl_ast.h:111
fawkes::stn::Stn::Stn
Stn(fawkes::Logger *logger)
Constructor.
Definition: stn.cpp:51
pddl_parser::Problem::name
std::string name
The name of the problem.
Definition: pddl_ast.h:128
pddl_parser::Domain::constants
pairs_multi_consts constants
A typed list of constants defined in the domain.
Definition: pddl_ast.h:113
pddl_parser::Domain::predicates
std::vector< predicate_type > predicates
A list of predicate names in the domain, including the types of their arguments.
Definition: pddl_ast.h:117
fawkes::Logger
Definition: logger.h:41
fawkes
pddl_parser::Domain::actions
std::vector< Action > actions
A list of actions defined in the domain.
Definition: pddl_ast.h:119
pddl_parser::PddlParser::parseProblem
static Problem parseProblem(const std::string pddl_problem)
Parse the PDDL problem.
Definition: pddl_parser.cpp:79
fawkes::stn::Predicate
Definition: predicate.h:42
fawkes::stn::StnAction
Definition: stn_action.h:50
fawkes::stn::DomainAction
Definition: domain_action.h:49
pddl_parser::Domain::requirements
std::vector< std::string > requirements
A list of PDDL features required by the domain.
Definition: pddl_ast.h:109
pddl_parser::Problem::init
std::vector< Expression > init
A list of facts that are initially true.
Definition: pddl_ast.h:134
pddl_parser::PddlParser
Definition: pddl_parser.h:38