Commit 19041469 authored by Alejandro Suarez Hernandez's avatar Alejandro Suarez Hernandez
Browse files

Parser working. Functional planner that can read the operators from an ADES database

parent b37e9212
#edit the following line to add the librarie's header files
FIND_PATH(imagine-planner_INCLUDE_DIR imagine-planner.h /usr/include/iridrivers /usr/local/include/iridrivers)
FIND_PATH(imagine-planner_INCLUDE_DIR imagine-planner.h /usr/include/iridrivers/imagine_planner /usr/local/include/iridrivers/imagine_planner)
FIND_LIBRARY(imagine-planner_LIBRARY
NAMES imagine-planner
......
# driver source files
SET(sources imagine-planner.cpp types.cpp queries.cpp parsing.cpp)
SET(sources imagine-planner.cpp types.cpp queries.cpp effects.cpp domains.cpp parsing.cpp)
# application header files
SET(headers imagine-planner.h types.h queries.h parsing.h)
SET(headers imagine-planner.h types.h queries.h effects.h domains.h parsing.h)
# Ades
SET(CMAKE_MODULE_PATH /usr/lib/cmake/libades)
FIND_PACKAGE(libimagine-ades)
# Boost
FIND_PACKAGE(Boost COMPONENTS system filesystem unit_test_framework REQUIRED)
# Swi-pl
include(FindPkgConfig)
FIND_PACKAGE(Boost REQUIRED COMPONENTS system serialization filesystem unit_test_framework)
# locate the necessary dependencies
# add the necessary include directories
INCLUDE_DIRECTORIES(. ${Boost_INCLUDE_DIR})
INCLUDE_DIRECTORIES(. ${Boost_INCLUDE_DIR} ${LIBIMAGINE-ADES_INCLUDE_DIRS})
# create the shared library
ADD_LIBRARY(imagine-planner SHARED ${sources})
# link necessary libraries
TARGET_LINK_LIBRARIES(imagine-planner)
TARGET_LINK_LIBRARIES(imagine-planner ${LIBIMAGINE-ADES_LIBRARIES})
INSTALL(TARGETS imagine-planner
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib/iridrivers
ARCHIVE DESTINATION lib/iridrivers)
INSTALL(FILES ${headers} DESTINATION include/iridrivers)
INSTALL(FILES ${headers} DESTINATION include/iridrivers/imagine_planner)
INSTALL(FILES ../Findimagine-planner.cmake DESTINATION ${CMAKE_ROOT}/Modules/)
ADD_SUBDIRECTORY(examples)
#include "domains.h"
#include <sstream>
#include <libades/libades.h>
namespace imagine_planner
{
// Implementation of OperatorSpecification's methods
OperatorSpecification::OperatorSpecification(
const OperatorSpecification& other)
: name_(other.name_), pre_(other.pre_->clone()),
post_(clone_effects(other.post_)) {}
OperatorSpecification::OperatorSpecification(const std::string& name,
const std::string& pre, const std::string& post) : name_(name), pre_(nullptr)
{
pre_ = parse_query(pre);
post_ = parse_effects(post);
if (not pre_) throw ImaginePlannerException("Could not parse precondition");
if (post_.empty())
{
throw ImaginePlannerException("Could not parse postcondition");
}
}
std::string OperatorSpecification::to_str() const
{
std::ostringstream oss;
oss << name_ << "\n Pre: " << pre_->to_str() << "\n Post: ";
bool first = true;
for (Effect* eff : post_)
{
if (not first) oss << ',';
oss << *eff;
first = false;
}
return oss.str();
}
std::vector<Operator> OperatorSpecification::instantiate(
const PlanState& state) const
{
std::vector<Operator> instantiations;
instantiations.reserve(50);
Query* query = (*pre_)(state);
while (query->next_solution())
{
instantiations.push_back(Operator(this, query->get_solution()));
}
delete query;
return instantiations;
}
OperatorSpecification::~OperatorSpecification()
{
delete pre_;
for (Effect* eff : post_) delete eff;
}
// Implementation of Operator's methods
Operator::Operator(const OperatorSpecification* spec,
const Substitution& sigma) : spec_(spec), sigma_(sigma) {}
std::string Operator::to_str() const
{
std::ostringstream oss;
oss << spec_->get_name() << '(';
bool first = true;
for (const auto& entry : sigma_)
{
if (entry.first[0] != '_')
{
if (not first) oss << ',';
oss << entry.first << '=' << entry.second;
first = false;
}
}
oss << ')';
return oss.str();
}
PlanState Operator::operator()(const PlanState& state) const
{
PlanState result(state);
for (const Effect* eff : spec_->get_post())
{
(*eff)(result, sigma_);
}
return result;
}
// Implementation of Domain's methods
void Domain::load_ades()
{
ades::AdesDB db(ades_db_, version_db_);
available_operators_.clear();
available_operators_.reserve(db.getAdesNb());
for (ades::Ades& action : db.listAdes())
{
std::string name = action.getName();
auto ades_pc = action.getPreconditions();
auto ades_ef = action.getEffects();
if (ades_pc.empty()) throw ImaginePlannerException("0 preconditions");
if (ades_ef.empty()) throw ImaginePlannerException("0 effects");
// consider just the tail of the first precondition and the first effect
std::string pre = action.getPreconditions().begin()->second;
std::string post = action.getEffects().begin()->second;
available_operators_.push_back(OperatorSpecification(name, pre, post));
}
}
Domain::Domain() : ades_db_(""), version_db_(0) {}
Domain::Domain(const std::string& ades_db, std::size_t version_db)
: ades_db_(ades_db), version_db_(version_db)
{
load_ades();
}
std::string Domain::to_str() const
{
std::ostringstream oss;
oss << "Domain:\nADES_DB: " << ades_db_ << " (v" << version_db_
<< ")\nOperators:";
for (const OperatorSpecification& opspec : available_operators_)
{
oss << '\n' << opspec;
}
return oss.str();
}
void Domain::set_ades_db(const std::string& ades_db, std::size_t version_db)
{
ades_db_ = ades_db;
version_db_ = version_db;
load_ades();
}
std::vector<Operator> Domain::instantiate_all(const PlanState& state) const
{
std::vector<Operator> acc;
acc.reserve(50);
for (const OperatorSpecification& op : available_operators_)
{
std::vector<Operator> instantiations = op.instantiate(state);
acc.insert(acc.end(), instantiations.begin(), instantiations.end());
}
return acc;
}
}
#ifndef _LIBIMAGINE_PLANNER_DOMAINS_H_
#define _LIBIMAGINE_PLANNER_DOMAINS_H_
#include "parsing.h"
namespace imagine_planner
{
class OperatorSpecification;
class Operator;
class Problem;
class OperatorSpecification : public Stringifiable
{
private:
std::string name_;
QuerySpecification* pre_;
EffectV post_;
public:
OperatorSpecification(const OperatorSpecification& other);
OperatorSpecification(const std::string& name, const std::string& pre,
const std::string& post);
std::string to_str() const override;
const std::string& get_name() const { return name_; }
const QuerySpecification* get_pre() const { return pre_; }
const EffectV& get_post() const { return post_; }
std::vector<Operator> instantiate(const PlanState& state) const;
~OperatorSpecification();
};
class Operator : public Stringifiable
{
private:
const OperatorSpecification* spec_;
Substitution sigma_;
public:
Operator(const OperatorSpecification* spec, const Substitution& sigma);
std::string to_str() const override;
PlanState operator()(const PlanState& state) const;
};
class Domain : public Stringifiable
{
private:
std::string ades_db_;
std::size_t version_db_;
std::vector<OperatorSpecification> available_operators_;
void load_ades();
public:
Domain();
Domain(const std::string& ades_db, std::size_t version_db=0);
std::string to_str() const;
const std::string& get_ades_db() const { return ades_db_; }
void set_ades_db(const std::string& ades_db, std::size_t version_db=0);
const std::vector<OperatorSpecification>& get_available_operators() const
{
return available_operators_;
}
std::vector<Operator> instantiate_all(const PlanState& state) const;
};
}
#endif
#include "effects.h"
#include <sstream>
namespace imagine_planner
{
void throw_non_instantiated_error(const Predicate& predicate)
{
std::ostringstream oss;
oss << "The following terms are not ground: ";
bool first = true;
for (const TermWrapper& term : predicate.get_arguments())
{
if (not term.is_ground())
{
if (not first) oss << ", ";
oss << term;
first = false;
}
}
throw ImaginePlannerException(oss.str());
}
// AddEffect's methods
AddEffect::AddEffect(const Predicate& effect) : effect_(effect) {}
std::string AddEffect::to_str() const
{
return effect_.to_str();
}
void AddEffect::operator()(PlanState& state, const Substitution& sigma) const
{
Predicate predicate = sigma(effect_);
if (not predicate.is_ground()) throw_non_instantiated_error(predicate);
state.put(predicate);
}
// DeleteEffect's methods
DeleteEffect::DeleteEffect(const Predicate& effect) : effect_(effect) {}
std::string DeleteEffect::to_str() const
{
return std::string("\\+") + effect_.to_str();
}
void DeleteEffect::operator()(PlanState& state, const Substitution& sigma) const
{
Predicate predicate = sigma(effect_);
if (not predicate.is_ground()) throw_non_instantiated_error(predicate);
state.remove(predicate);
}
std::vector<Effect*> clone_effects(const std::vector<Effect*>& effects)
{
std::vector<Effect*> clone;
clone.reserve(effects.size());
for (Effect* eff : effects) clone.push_back(eff->clone());
return clone;
}
} /* end namespace imagine_planner */
#ifndef _LIBIMAGINE_PLANNER_EFFECTS_H_
#define _LIBIMAGINE_PLANNER_EFFECTS_H_
#define EFFECT_COMMON(Class)\
virtual Effect* clone() const override { return new Class(*this); };
#include "types.h"
namespace imagine_planner
{
class Effect;
class AddEffect;
class DeleteEffect;
void throw_non_instantiated_error(const Predicate& predicate);
class Effect : public Stringifiable
{
public:
virtual void operator()(PlanState& state,
const Substitution& sigma) const =0;
virtual ~Effect() {}
virtual Effect* clone() const =0;
};
class AddEffect : public Effect
{
private:
Predicate effect_;
public:
AddEffect(const Predicate& effect);
std::string to_str() const override;
virtual void operator()(PlanState& state,
const Substitution& sigma) const override;
EFFECT_COMMON(AddEffect);
};
class DeleteEffect : public Effect
{
private:
Predicate effect_;
public:
DeleteEffect(const Predicate& effect);
std::string to_str() const override;
virtual void operator()(PlanState& state,
const Substitution& sigma) const override;
EFFECT_COMMON(DeleteEffect);
};
typedef std::vector<Effect*> EffectV;
std::vector<Effect*> clone_effects(const std::vector<Effect*>& effects);
}
#endif
# create an example application
ADD_EXECUTABLE(imagine-planner_test imagine-planner_test.cpp)
# link necessary libraries
TARGET_LINK_LIBRARIES(imagine-planner_test imagine-planner)
TARGET_LINK_LIBRARIES(imagine-planner_test
imagine-planner
${Boost_LIBRARIES}
${LIBIMAGINE-ADES_LIBRARIES})
ADD_EXECUTABLE(types_test types_test.cpp)
TARGET_LINK_LIBRARIES(types_test
imagine-planner
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
${Boost_LIBRARIES})
ADD_EXECUTABLE(queries_test queries_test.cpp)
TARGET_LINK_LIBRARIES(queries_test
imagine-planner
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
${Boost_LIBRARIES})
ADD_EXECUTABLE(effects_test effects_test.cpp)
TARGET_LINK_LIBRARIES(effects_test
imagine-planner
${Boost_LIBRARIES})
ADD_EXECUTABLE(parsing_test parsing_test.cpp)
TARGET_LINK_LIBRARIES(parsing_test
imagine-planner
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
${Boost_LIBRARIES})
ADD_EXECUTABLE(domains_test domains_test.cpp)
TARGET_LINK_LIBRARIES(domains_test
imagine-planner
${Boost_LIBRARIES}
${LIBIMAGINE-ADES_LIBRARIES})
#define BOOST_TEST_MODULE Domains Test
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#include "domains.h"
#include <libades/libades.h>
#include <iostream>
using namespace imagine_planner;
#if 0 // Set to 1 if __PRETTY_FUNCTION__ macro is not available
#define __PRETTY_FUNCTION__ __func__
#endif
#if 1
#define INFO(x)\
std::cout << "\e[1m\e[32m\e[4m" << __PRETTY_FUNCTION__ << " [" << __LINE__ << "]" << "\e[24m: " << x << "\e[0m" << std::endl;
#else
#define INFO(x)
#endif
BOOST_AUTO_TEST_CASE(operators_test1)
{
PlanState state({Predicate("at", "lettuce", "east"),
Predicate("at", "wolf", "east"),
Predicate("at", "bunny", "east"),
Predicate("at", "boat", "east"),
Predicate("side", "east"),
Predicate("side", "west"),
Predicate("forbidden", "lettuce", "bunny"),
Predicate("forbidden", "wolf", "bunny")});
OperatorSpecification spec("load",
"at(boat,Side), at(What,Side), side(Side), !=(What,boat), \\+loaded(_)",
"\\+at(What,Side), loaded(What)");
INFO(spec);
std::vector<Operator> instantiations = spec.instantiate(state);
for (const Operator& op : instantiations)
{
INFO(op);
}
BOOST_REQUIRE_EQUAL(instantiations.size(), 3);
BOOST_CHECK_EQUAL(instantiations[0].to_str(), "load(Side=east,What=bunny)");
BOOST_CHECK_EQUAL(instantiations[1].to_str(), "load(Side=east,What=lettuce)");
BOOST_CHECK_EQUAL(instantiations[2].to_str(), "load(Side=east,What=wolf)");
PlanState after = instantiations[0](state);
INFO("Before " << instantiations[0] << ": " << state << ", after: " << after);
}
BOOST_AUTO_TEST_CASE(operators_test2)
{
PlanState state({Predicate("at", "lettuce", "east"),
Predicate("at", "wolf", "east"),
Predicate("at", "bunny", "east"),
Predicate("at", "boat", "east"),
Predicate("side", "east"),
Predicate("side", "west"),
Predicate("forbidden", "lettuce", "bunny"),
Predicate("forbidden", "wolf", "bunny")});
OperatorSpecification spec("switch_sides",
"at(boad,From),side(From),side(To),!=(From,To),\\+(forbidden(Obj1, Obj2),at(Obj1,From),at(Obj2,From))",
"\\+at(boat,From), at(boat,To)");
INFO(spec);
std::vector<Operator> instantiations = spec.instantiate(state);
for (const Operator& op : instantiations)
{
INFO(op);
}
BOOST_REQUIRE_EQUAL(instantiations.size(), 0);
}
BOOST_AUTO_TEST_CASE(operators_test3)
{
PlanState state({Predicate("at", "lettuce", "west"),
Predicate("at", "wolf", "west"),
Predicate("at", "bunny", "east"),
Predicate("at", "boat", "east"),
Predicate("side", "east"),
Predicate("side", "west"),
Predicate("forbidden", "lettuce", "bunny"),
Predicate("forbidden", "wolf", "bunny")});
OperatorSpecification spec("switch_sides",
"at(boat,From),side(From),side(To),!=(From,To),\\+(forbidden(Obj1, Obj2),at(Obj1,From),at(Obj2,From))",
"\\+at(boat,From), at(boat,To)");
INFO(spec);
std::vector<Operator> instantiations = spec.instantiate(state);
for (const Operator& op : instantiations)
{
INFO(op);
}
BOOST_REQUIRE_EQUAL(instantiations.size(), 1);
BOOST_CHECK_EQUAL(instantiations[0].to_str(), "switch_sides(From=east,To=west)");
}
void head_and_tail(const std::string& rule,
std::string& head,
std::string& tail)
{
std::size_t separator = rule.find(":-");
head = rule.substr(0, separator);
tail = separator == std::string::npos? "" : rule.substr(separator + 2);
std::cout << "head: " << head << "; tail: " << tail << std::endl;
}
ades::Ades new_ades(const std::string& name,
const std::vector<std::string>& preconditions,
const std::vector<std::string>& effects)
{
ades::Ades ret(name);
std::map<std::string, std::string> pc_map;
for (const std::string& pc : preconditions)
{
std::string head, tail;
head_and_tail(pc, head, tail);
pc_map.insert({head, tail});
}
ret.insertPreconditions(pc_map);
std::map<std::string, std::string> eff_map;
for (const std::string& eff : effects)
{
std::string head, tail;
head_and_tail(eff, head, tail);
eff_map.insert({head, tail});
}
ret.insertEffects(eff_map);
return ret;
}
void add_basic_actions(ades::AdesDB& db)
{
db.addAdes(new_ades("load",
{"load_pre(Side,What):-at(boat,Side), at(What,Side), side(Side), !=(What,boat), \\+loaded(_)"},
{"load_post(Side,What):-\\+at(What,Side), loaded(What)"}));
db.addAdes(new_ades("switch_sides",
{"switch_sides_pre(From,To):-at(boat,From),side(From),side(To),!=(From,To),\\+(forbidden(Obj1, Obj2),at(Obj1,From),at(Obj2,From))"},
{"switch_sides_post(From,To):-\\+at(boat,From),at(boat,To)"}));
db.addAdes(new_ades("unload",
{"unload_pre(Side,What):-at(boat,Side),side(Side),loaded(What)"},
{"unload_post(Side,What):-at(What,Side),\\+loaded(What)"}));
}
BOOST_AUTO_TEST_CASE(operator_loading)
{
std::string dbpath = std::string(std::getenv("HOME")) + "/DOMAINS_TEST";
{
ades::AdesDB db(dbpath, 0);
if (db.getAdesNb() == 0) add_basic_actions(db);
}
PlanState state({Predicate("at", "lettuce", "east"),