diff --git a/Findimagine-planner.cmake b/Findimagine-planner.cmake index eb6d0cfe7686f5a3bc3d25e750f0b55b49d4e0a4..1a0ccf0e5f02818c6c9965077a670c71d5c6f65e 100644 --- a/Findimagine-planner.cmake +++ b/Findimagine-planner.cmake @@ -1,5 +1,5 @@ #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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 226d711e2cbedb4ca44518d695becec2b61e8299..595b7f15da77c67130912bc7e02b6bd32d77a2fe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,23 +1,24 @@ # 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) diff --git a/src/domains.cpp b/src/domains.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dda282c1f4bfb8bc11972e8003c2f097936f9388 --- /dev/null +++ b/src/domains.cpp @@ -0,0 +1,154 @@ +#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; +} + +} diff --git a/src/domains.h b/src/domains.h new file mode 100644 index 0000000000000000000000000000000000000000..0c0c3bd8deb53db83977ce6d2e7979cf4a965f1c --- /dev/null +++ b/src/domains.h @@ -0,0 +1,89 @@ +#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 diff --git a/src/effects.cpp b/src/effects.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aac731b455c2525da2b5acb11a609b765def1552 --- /dev/null +++ b/src/effects.cpp @@ -0,0 +1,64 @@ +#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 */ diff --git a/src/effects.h b/src/effects.h new file mode 100644 index 0000000000000000000000000000000000000000..0ac5254b2c9bd59dd5b7c5f58ef8a90eb08d1fde --- /dev/null +++ b/src/effects.h @@ -0,0 +1,72 @@ +#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 diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index dd6dd89c4c842afdb3c2396ee643052fd1d23683..25564458e2d57f4d9a37c896b18d6c540f817b9b 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -1,26 +1,34 @@ # 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}) diff --git a/src/examples/domains_test.cpp b/src/examples/domains_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2faeb499b9b1d35082079ed715e53aa1051bdbc3 --- /dev/null +++ b/src/examples/domains_test.cpp @@ -0,0 +1,218 @@ +#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"), + 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")}); + //GoalSpecification goal({Predicate("at", "lettuce", "west"), + //Predicate("at", "bunny", "west"), + //Predicate("at", "wolf", "west"), + //Predicate("at", "boat", "west")}, {}); + Domain domain(dbpath); + INFO(domain); + INFO("state: " << state); + std::vector<Operator> instantiations = domain.instantiate_all(state); + for (const Operator op : instantiations) + { + INFO(op); + } +} + +BOOST_AUTO_TEST_CASE(instantiations1) +{ + 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"), + 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")}); + Domain domain(dbpath); + INFO(domain); + INFO("state: " << state); + std::vector<Operator> instantiations = domain.instantiate_all(state); + for (const Operator op : instantiations) + { + INFO(op); + } +} + +BOOST_AUTO_TEST_CASE(instantiations2) +{ + 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", "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")}); + Domain domain(dbpath); + INFO(domain); + INFO("state: " << state); + std::vector<Operator> instantiations = domain.instantiate_all(state); + for (const Operator op : instantiations) + { + INFO(op); + } +} + diff --git a/src/examples/effects_test.cpp b/src/examples/effects_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..21c5c11218ee24bc84d5b7d3595c300848585c1f --- /dev/null +++ b/src/examples/effects_test.cpp @@ -0,0 +1,53 @@ +#define BOOST_TEST_MODULE Effects test +#define BOOST_TEST_DYN_LINK +#include <boost/test/unit_test.hpp> + +#include "effects.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(simple_add_effect_test1) +{ + PlanState state({Predicate("p","a","b"), Predicate("p","a","c"), + Predicate("p","d","a"), Predicate("q","w"), Predicate("q", "s")}); + PlanState goal({Predicate("p","a","b"), Predicate("p","a","c"), + Predicate("p","d","a"), Predicate("q","w"), Predicate("q", "s"), + Predicate("r", "a", "b", 1.0)}); + INFO(state); + AddEffect ef1(Predicate("r", "X", "Y", "Z")); + PlanState state2(state); + BOOST_CHECK_THROW(ef1(state2, Substitution({{"X", TermWrapper("a")}})), + ImaginePlannerException); + ef1(state2, Substitution({{"X", TermWrapper("a")}, + {"Y", TermWrapper("b")}, + {"Z", TermWrapper(1.0)}})); + BOOST_CHECK_EQUAL(state2, goal); +} + +BOOST_AUTO_TEST_CASE(simple_delete_effect_test1) +{ + PlanState state({Predicate("p","a","b"), Predicate("p","a",1.0), + Predicate("p","d","a"), Predicate("q","w"), Predicate("q", "s")}); + PlanState goal({Predicate("p","a","b"), Predicate("p","d","a"), + Predicate("q","w"), Predicate("q", "s")}); + INFO(state); + DeleteEffect ef1(Predicate("p", "X", "Y")); + PlanState state2(state); + BOOST_CHECK_THROW(ef1(state2, Substitution({{"X", TermWrapper("a")}})), + ImaginePlannerException); + ef1(state2, Substitution({{"X", TermWrapper("a")}, + {"Y", TermWrapper(1.0+5e-10)}})); + BOOST_CHECK_EQUAL(state2, goal); +} + diff --git a/src/examples/imagine-planner_test.cpp b/src/examples/imagine-planner_test.cpp index bb65efe713e047d3c947f29602ca6e032713fc83..1bb23e2855611e6896ee6844c2f900e0d97c9df0 100644 --- a/src/examples/imagine-planner_test.cpp +++ b/src/examples/imagine-planner_test.cpp @@ -1,5 +1,193 @@ #include "imagine-planner.h" +#include <libades/libades.h> +#include <iostream> +#include <unordered_set> +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 + +class IDSPlanner : public Planner +{ + private: + + typedef std::unordered_set<PlanState, std::hash<Hashable>> ClosedSet; + + const Problem* problem_; + ClosedSet seen_; + int max_depth_; + double elapsed_; + + bool ids(const PlanState& state, Plan& plan, std::size_t depth) + { + if (problem_->goal(state)) return true; + if (depth == 0) return false; + seen_.insert(state); + std::vector<Operator> instantiations = problem_->domain.instantiate_all(state); + for (const Operator& op : instantiations) + { + PlanState next = op(state); + if (seen_.count(next)) continue; + plan.push_back({op, next}); + if (ids(next, plan, depth-1)) return true; + plan.pop_back(); + } + seen_.erase(state); + return false; + } + + public: + + IDSPlanner(int max_depth=100) : max_depth_(100) {} + + virtual Plan operator()(const Problem& problem) override + { + Plan plan; + problem_ = &problem; + StopWatch sw; + for (int depth = 0; depth < max_depth_; ++depth) + { + if (ids(problem_->start, plan, depth)) break; + } + elapsed_ = sw.time(); + seen_.clear(); + return plan; + } + + double get_elapsed() const { return elapsed_; } + +}; + +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); +} + +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, const std::string& domain="boat") +{ + // domains: {boat, blocks} + if (domain == "boat") + { + 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)"})); + } + else if (domain == "blocks") + { + db.addAdes(new_ades("pick_from", + {"pick_from_pre(Block,Where):-\\+holding(_),on(Block,Where),\\+on(_,Block)"}, + {"pick_from_post(Block,Where):-\\+on(Block,Where),holding(Block)"})); + db.addAdes(new_ades("leave_on", + {"leave_on_pre(Block,Where):-holding(Block),object(Where),\\+(!=(Where,table),on(_,Where))"}, + {"leave_on_post(Block,Where):-\\+holding(Block),on(Block,Where)"})); + } + else if (domain == "blocks_traditional") + { + db.addAdes(new_ades("unstack", + {"unstack_pre(X,Y):-\\+holding(_),on(X,Y),!=(Y,table),\\+on(_,X)"}, + {"unstack_post(X,Y):-holding(X),\\+on(X,Y)"})); + db.addAdes(new_ades("pick", + {"pick_pre(X):-\\+holding(_),on(X,table),\\+on(_,X)"}, + {"pick_post(X):-holding(X),\\+on(X,table)"})); + db.addAdes(new_ades("stack", + {"stack_pre(X,Y):-holding(X),object(Y),!=(Y,table),\\+on(_,Y)"}, + {"stack_post(X,Y):-\\+holding(X),on(X,Y)"})); + db.addAdes(new_ades("leave", + {"leave_pre(X):-holding(X)"}, + {"leave_post(X):-\\+holding(X),on(X,table)"})); + } +} int main(int argc, char *argv[]) { + std::string domain_name = argc < 2? "boat" : argv[1]; + std::string dbpath = std::string(std::getenv("HOME")) + "/ADES_DOMAINS/" + domain_name + "_DOMAIN"; + { + ades::AdesDB db(dbpath, 0); + if (db.getAdesNb() == 0) add_basic_actions(db, domain_name); + } + Problem problem; + if (domain_name == "boat") + { + problem.start = PlanState({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")}); + problem.goal = GoalSpecification({Predicate("at", "lettuce", "west"), + Predicate("at", "bunny", "west"), + Predicate("at", "wolf", "west"), + Predicate("at", "boat", "west")}, {}); + } + else if (domain_name == "blocks" or domain_name == "blocks_traditional") + { + problem.start = PlanState({Predicate("object", "a"), + Predicate("object", "b"), + Predicate("object", "c"), + Predicate("object", "d"), + Predicate("object", "table"), + Predicate("on", "b", "d"), + Predicate("on", "d", "c"), + Predicate("on", "c", "table"), + Predicate("on", "a", "table")}); + problem.goal = GoalSpecification({Predicate("on", "a", "b"), + Predicate("on", "b", "table"), + Predicate("on", "c", "d"), + Predicate("on", "d", "table")}, {}); + } + else + { + INFO("INVALID DOMAIN: " << domain_name); + } + problem.domain = Domain(dbpath, 0); + IDSPlanner planner(50); + Plan plan = planner(problem); + INFO("Initial state: " << problem.start); + INFO("Plan found:\n" << plan); + INFO("Elapsed (ms): " << planner.get_elapsed()*1000); } diff --git a/src/examples/parsing_test.cpp b/src/examples/parsing_test.cpp index 6d7422c4c8a3aa0dcf4eafece2891ae6dc355192..3abd834c668281192095afb6d015de99b0d9c92a 100644 --- a/src/examples/parsing_test.cpp +++ b/src/examples/parsing_test.cpp @@ -31,6 +31,20 @@ std::ostream& operator<<(std::ostream& os, const TokenV& tokens) return os; } +std::ostream& operator<<(std::ostream& os, const std::vector<Effect*>& effects) +{ + bool first = true; + os << '['; + for (const Effect* effect : effects) + { + if (not first) os << ", "; + os << *effect; + first = false; + } + os << ']'; + return os; +} + BOOST_AUTO_TEST_CASE(spaces) { std::string str(" Hello ( world) "); @@ -73,70 +87,55 @@ BOOST_AUTO_TEST_CASE(test_parse_simple_expressions) std::string str2("\\+at(X, Y)"); std::string str3("(at(X, Y))"); std::string str4("\\+(at(X, Y))"); - QuerySpecification* q1 = parse_expression(str1); - if (q1) - { - INFO("q1: " << *q1); - delete q1; - } - else INFO("q1: none"); - QuerySpecification* q2 = parse_expression(str2); - if (q2) - { - INFO("q2: " << *q2); - delete q2; - } - else INFO("q2: none"); - QuerySpecification* q3 = parse_expression(str3); - if (q3) - { - INFO("q3: " << *q3); - delete q3; - } - else INFO("q3: none"); - QuerySpecification* q4 = parse_expression(str4); - if (q4) - { - INFO("q4: " << *q4); - delete q4; - } - else INFO("q4: none"); + QuerySpecification* q1 = parse_query(str1); + BOOST_REQUIRE(q1); + BOOST_CHECK_EQUAL(q1->to_str(), "at(X,Y)"); + delete q1; + QuerySpecification* q2 = parse_query(str2); + BOOST_REQUIRE(q2); + BOOST_CHECK_EQUAL(q2->to_str(), "\\+at(X,Y)"); + delete q2; + QuerySpecification* q3 = parse_query(str3); + BOOST_REQUIRE(q3); + BOOST_CHECK_EQUAL(q3->to_str(), "at(X,Y)"); + delete q3; + QuerySpecification* q4 = parse_query(str4); + BOOST_REQUIRE(q4); + BOOST_CHECK_EQUAL(q4->to_str(), "\\+at(X,Y)"); + delete q4; } BOOST_AUTO_TEST_CASE(test_parse_complex_expressions) { std::string str1("at(X, Y), at(Z, Y)"); - std::string str2("at(X, Y), at(Z, Y), \\+at(boat, Y)"); + std::string str2("at(X, Y), at(Z, Y), !=(boat, X)"); std::string str3("fixes(X, Y), \\+(precedes(_1, X), occluded_by(X, _2))"); std::string str4("\\+(\\+q(X), \\+q(Y), \\+r())"); - QuerySpecification* q1 = parse_expression(str1); - if (q1) - { - INFO("q1: " << *q1); - delete q1; - } - else INFO("q1: none"); - QuerySpecification* q2 = parse_expression(str2); - if (q2) - { - INFO("q2: " << *q2); - delete q2; - } - else INFO("q2: none"); - QuerySpecification* q3 = parse_expression(str3); - if (q3) - { - INFO("q3: " << *q3); - delete q3; - } - else INFO("q3: none"); - QuerySpecification* q4 = parse_expression(str4); - if (q4) - { - INFO("q4: " << *q4); - delete q4; - } - else INFO("q4: none"); + QuerySpecification* q1 = parse_query(str1); + BOOST_REQUIRE(q1); + BOOST_CHECK_EQUAL(q1->to_str(), "at(X,Y),at(Z,Y)"); + delete q1; + QuerySpecification* q2 = parse_query(str2); + BOOST_REQUIRE(q2); + BOOST_CHECK_EQUAL(q2->to_str(), "at(X,Y),at(Z,Y),!=(boat,X)"); + delete q2; + QuerySpecification* q3 = parse_query(str3); + BOOST_REQUIRE(q3); + BOOST_CHECK_EQUAL(q3->to_str(), "fixes(X,Y),\\+(precedes(_1,X),occluded_by(X,_2))"); + delete q3; + QuerySpecification* q4 = parse_query(str4); + BOOST_REQUIRE(q4); + BOOST_CHECK_EQUAL(q4->to_str(), "\\+(\\+q(X),\\+q(Y),\\+r())"); + delete q4; } +BOOST_AUTO_TEST_CASE(test_parse_effects) +{ + std::string str1("\\+at(X, Y), at(X, Z)"); + std::vector<Effect*> effects = parse_effects(str1); + INFO(effects); + BOOST_REQUIRE_EQUAL(effects.size(), 2); + BOOST_CHECK_EQUAL(effects[0]->to_str(), "\\+at(X,Y)"); + BOOST_CHECK_EQUAL(effects[1]->to_str(), "at(X,Z)"); +} diff --git a/src/examples/queries_test.cpp b/src/examples/queries_test.cpp index e86e3c675a2dc180f206eb7df1dfe11f930a8e16..cf50c57870a825bb844bd22c1d7939a216a8eea7 100644 --- a/src/examples/queries_test.cpp +++ b/src/examples/queries_test.cpp @@ -64,5 +64,23 @@ BOOST_AUTO_TEST_CASE(and_query_test_sat) BOOST_REQUIRE(and1->next_solution()); BOOST_CHECK_EQUAL(and1->get_solution().to_str(), "{\n X -> d,\n Y -> a\n}"); BOOST_REQUIRE(not and1->next_solution()); + delete and1; +} + +BOOST_AUTO_TEST_CASE(comparison_query_test_sat) +{ + PlanState state({Predicate("p","a","b"), Predicate("p","a","c"), + Predicate("p", "e", "d"), Predicate("p","d","a"), Predicate("q","b")}); + INFO("state: " << state); + SimpleQuery* q1 = new SimpleQuery(state, Predicate("p", "X", "Y")); + ComparisonQuery* q2 = new ComparisonQuery(state, Predicate("<", "X", "Y")); + AndQuery* and1 = new AndQuery(q1, q2); + INFO("and1: " << *and1); + BOOST_REQUIRE(and1->next_solution()); + BOOST_CHECK_EQUAL(and1->get_solution().to_str(), "{\n X -> a,\n Y -> b\n}"); + BOOST_REQUIRE(and1->next_solution()); + BOOST_CHECK_EQUAL(and1->get_solution().to_str(), "{\n X -> a,\n Y -> c\n}"); + BOOST_REQUIRE(not and1->next_solution()); + delete and1; } diff --git a/src/imagine-planner.cpp b/src/imagine-planner.cpp index 3ec12725d53d977af12b82479d4359ab9507d26b..c30e4cfde3c985552402f208593ccb61815e4466 100644 --- a/src/imagine-planner.cpp +++ b/src/imagine-planner.cpp @@ -1,10 +1,31 @@ #include "imagine-planner.h" -CImaginePlanner::CImaginePlanner() +namespace imagine_planner { + +std::ostream& operator<<(std::ostream& os, const Plan& plan) +{ + bool first = true; + for (const Step& step : plan) + { + if (not first) os << ",\n"; + os << step.first << " -> " << step.second; + first = false; + } + return os; } - -CImaginePlanner::~CImaginePlanner() + +StopWatch::StopWatch() : tic_(std::clock()) {} + +void StopWatch::reset() { + tic_ = std::clock(); +} + +double StopWatch::time() const +{ + return (std::clock() - tic_)/(double)(CLOCKS_PER_SEC); +} + } diff --git a/src/imagine-planner.h b/src/imagine-planner.h index 22c2a6fa6049aeb6874914318805913c2c15124f..3d9327c919f5143f7e9164dbacdffb1ab4c13165 100644 --- a/src/imagine-planner.h +++ b/src/imagine-planner.h @@ -1,11 +1,49 @@ -#ifndef _IMAGINE_PLANNER_H_ -#define _IMAGINE_PLANNER_H_ +#ifndef _LIBIMAGINE_PLANNER_H_ +#define _LIBIMAGINE_PLANNER_H_ -class CImaginePlanner +#include "domains.h" +#include <ctime> +#include <utility> + +namespace imagine_planner +{ + +struct Problem +{ + PlanState start; + GoalSpecification goal; + Domain domain; +}; + +typedef std::pair<Operator, PlanState> Step; +typedef std::vector<Step> Plan; + + +class Planner { public: - CImaginePlanner(); - ~CImaginePlanner(); + + virtual Plan operator()(const Problem& problem) =0; + + virtual ~Planner() {}; }; +class StopWatch +{ + private: + std::clock_t tic_; + + public: + + StopWatch(); + + void reset(); + + double time() const; +}; + +std::ostream& operator<<(std::ostream& os, const Plan& plan); + +} /* end of imagine_planner namespace */ + #endif diff --git a/src/parsing.cpp b/src/parsing.cpp index 49677a1bf2694abeb28eb451d862d963e4b0f54c..1e6e75988e4cde54b2e7dc98b73026506c972647 100644 --- a/src/parsing.cpp +++ b/src/parsing.cpp @@ -1,7 +1,6 @@ #include "parsing.h" #include <cctype> #include <sstream> -#include <iostream> namespace imagine_planner { @@ -20,6 +19,12 @@ bool is_number(const std::string& str) } } +bool is_comparison_sign(const std::string& str) +{ + return str == "==" or str == ">" or str == "<" or + str == "!=" or str == "=<" or str == ">="; +} + std::string remove_spaces(const std::string& in) { std::ostringstream oss; @@ -144,7 +149,7 @@ Predicate* parse_predicate(const TokenV& tokens, std::size_t& cursor) return predicate; } -QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor) +QuerySpecification* parse_query(const TokenV& tokens, std::size_t& cursor) { enum State {EXPECTING_PRED_OR_PAR, EXPECTING_PAR_EXPR, EXPECTING_PAR_CLOSE, EXPECTING_COMMA_OR_END, EXPECTING_END_EXPR, OK, KO}; @@ -155,11 +160,9 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor) while (state != OK and state != KO) { const Token& token = tokens[idx]; - std::cout << token.first << '(' << token.second << ')' << std::endl; switch (state) { case EXPECTING_PRED_OR_PAR: - std::cout << "EXPECTING_PRED_OR_PAR" << std::endl; if (token.first == "NOT") { negated = not negated; @@ -168,7 +171,9 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor) } else if (Predicate* predicate = parse_predicate(tokens, idx)) { - query = new SimpleQuerySpecification(*predicate); + query = is_comparison_sign(predicate->get_name())? + (QuerySpecification*)new ComparisonQuerySpecification(*predicate) : + (QuerySpecification*)new SimpleQuerySpecification(*predicate); query = negated? new NotQuerySpecification(query) : query; state = EXPECTING_COMMA_OR_END; delete predicate; @@ -181,8 +186,7 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor) else state = KO; break; case EXPECTING_PAR_EXPR: - std::cout << "EXPECTING_PAR_EXPR" << std::endl; - if ((query = parse_expression(tokens, idx))) + if ((query = parse_query(tokens, idx))) { query = negated? new NotQuerySpecification(query) : query; state = EXPECTING_PAR_CLOSE; @@ -190,7 +194,6 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor) else state = KO; break; case EXPECTING_PAR_CLOSE: - std::cout << "EXPECTING_PAR_CLOSE" << std::endl; if (token.first == "PAR_CLOSE") { state = EXPECTING_COMMA_OR_END; @@ -199,7 +202,6 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor) else state = KO; break; case EXPECTING_COMMA_OR_END: - std::cout << "EXPECTING_COMMA_OR_END" << std::endl; if (token.first == "COMMA") { state = EXPECTING_END_EXPR; @@ -208,8 +210,7 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor) else state = OK; break; case EXPECTING_END_EXPR: - std::cout << "EXPECTING_END_EXPR" << std::endl; - if (QuerySpecification* query_snd = parse_expression(tokens, idx)) + if (QuerySpecification* query_snd = parse_query(tokens, idx)) { query = new AndQuerySpecification(query, query_snd); state = OK; @@ -222,26 +223,76 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor) } if (state == OK) { - std::cout << "OK" << std::endl; cursor = idx; } - else + else if (query) { - std::cout << "KO" << std::endl; - if (query) - { - delete query; - query = nullptr; - } + delete query; + query = nullptr; } return query; } -QuerySpecification* parse_expression(const std::string& str) +QuerySpecification* parse_query(const std::string& str) { TokenV tokens = decompose(str); std::size_t cursor = 0; - return parse_expression(tokens, cursor); + return parse_query(tokens, cursor); +} + +Effect* parse_next_effect(const TokenV& tokens, std::size_t& cursor) +{ + enum State {EXPECTING_ADD_OR_DEL, OK, KO}; + State state = EXPECTING_ADD_OR_DEL; + bool delete_effect = false; + Effect* effect = nullptr; + std::size_t idx = cursor; + while (state != OK and state != KO) + { + const Token& token = tokens[idx]; + switch (state) + { + case EXPECTING_ADD_OR_DEL: + if (token.first == "NOT") + { + delete_effect = true; + ++idx; + } + else if (token.first == "COMMA") ++idx; // ignore commas + else if (Predicate* predicate = parse_predicate(tokens, idx)) + { + effect = delete_effect? + (Effect*)new DeleteEffect(*predicate) : + (Effect*)new AddEffect(*predicate); + delete predicate; + state = OK; + } + else state = KO; + break; + default: + /* do nothing <*/ + break; + } + } + if (state == OK) cursor = idx; + else if (effect) + { + delete effect; + effect = nullptr; + } + return effect; +} + +EffectV parse_effects(const std::string& str) +{ + TokenV tokens = decompose(str); + std::size_t cursor = 0; + EffectV effects; + while (Effect* effect = parse_next_effect(tokens, cursor)) + { + effects.push_back(effect); + } + return effects; } } diff --git a/src/parsing.h b/src/parsing.h index c89d6f2f4fbcfeac0a18afb7bafb6693824e4e39..f3d4f77fce62be79085bea1d654444117f48bed8 100644 --- a/src/parsing.h +++ b/src/parsing.h @@ -2,6 +2,7 @@ #define _LIBIMAGINE_PLANNER_PARSING_H_ #include "queries.h" +#include "effects.h" namespace imagine_planner { @@ -11,15 +12,21 @@ typedef std::vector<Token> TokenV; bool is_number(const std::string& str); +bool is_comparison_sign(const std::string& str); + std::string remove_spaces(const std::string& in); TokenV decompose(const std::string& in); Predicate* parse_predicate(const TokenV& tokens, std::size_t& cursor); -QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor); +QuerySpecification* parse_query(const TokenV& tokens, std::size_t& cursor); + +QuerySpecification* parse_query(const std::string& str); + +Effect* parse_next_effect(const TokenV& tokens, std::size_t& cursor); -QuerySpecification* parse_expression(const std::string& str); +std::vector<Effect*> parse_effects(const std::string& str); } /* end namespace imagine_planner */ diff --git a/src/queries.cpp b/src/queries.cpp index 3566caf868ffff48cab415dce7db43807abde301..69ed107679e452f986c93353e61b58b223cbe771 100644 --- a/src/queries.cpp +++ b/src/queries.cpp @@ -3,6 +3,39 @@ namespace imagine_planner { +// ComparisonQuery methods + +ComparisonQuery::ComparisonQuery(const PlanState&, const Predicate& op, + const Substitution& sigma0) : op_(op), sigma0_(sigma0) {} + +std::string ComparisonQuery::to_str() const +{ + return op_.to_str(); +} + +bool ComparisonQuery::next_solution() +{ + if (done_) return false; + done_ = true; + Predicate op_sub = sigma0_(op_); + if (not op_sub.is_ground() or op_sub.arity() != 2) return false; + const TermWrapper& arg1 = op_sub.get_arguments()[0]; + const TermWrapper& arg2 = op_sub.get_arguments()[1]; + if (op_sub.get_name() == "==") return arg1 == arg2; + else if (op_sub.get_name() == "!=") return arg1 != arg2; + else if (op_sub.get_name() == "<") return arg1 < arg2; + else if (op_sub.get_name() == ">") return arg1 > arg2; + else if (op_sub.get_name() == "=<") return arg1 <= arg2; + else if (op_sub.get_name() == ">=") return arg1 >= arg2; + return false; +} + +void ComparisonQuery::reset(const Substitution& sigma0) +{ + sigma0_ = sigma0; + done_ = false; +} + // Simple query methods bool SimpleQuery::unify(const Predicate& goal) @@ -74,6 +107,10 @@ NotQuery::NotQuery(Query* q) : query_(q), done_(false) {} std::string NotQuery::to_str() const { + if (dynamic_cast<const AndQuery*>(query_)) + { + return std::string("\\+(") + query_->to_str() + ')'; + } return std::string("\\+") + query_->to_str(); } @@ -102,7 +139,7 @@ AndQuery::AndQuery(Query* q1, Query* q2) : std::string AndQuery::to_str() const { - return std::string(1, '(') + q1_->to_str() + ',' + q2_->to_str() + ')'; + return q1_->to_str() + ',' + q2_->to_str(); } bool AndQuery::next_solution() @@ -130,6 +167,22 @@ AndQuery::~AndQuery() delete q2_; } +// ComparisonQuerySpecification methods + +std::string ComparisonQuerySpecification::to_str() const +{ + return op_.to_str(); +} + +ComparisonQuerySpecification::ComparisonQuerySpecification( + const Predicate& op) : op_(op) {} + +Query* ComparisonQuerySpecification::operator()(const PlanState& state, + const Substitution& sigma0) const +{ + return new ComparisonQuery(state, op_, sigma0); +} + // SimpleQuerySpecification methods std::string SimpleQuerySpecification::to_str() const @@ -149,46 +202,85 @@ Query* SimpleQuerySpecification::operator()(const PlanState& state, // NotQuerySpecification methods -std::string NotQuerySpecification::to_str() const +void NotQuerySpecification::copy_other(const NotQuerySpecification& other) { - return std::string("\\+") + query_->to_str(); + query_ = other.query_->clone(); } -NotQuerySpecification::~NotQuerySpecification() +NotQuerySpecification::NotQuerySpecification(const NotQuerySpecification& other) { - delete query_; + copy_other(other); } NotQuerySpecification::NotQuerySpecification(QuerySpecification* query) : query_(query) {} +NotQuerySpecification& NotQuerySpecification::operator=( + const NotQuerySpecification& other) +{ + copy_other(other); + return *this; +} + +std::string NotQuerySpecification::to_str() const +{ + if (dynamic_cast<const AndQuerySpecification*>(query_)) + { + return std::string("\\+(") + query_->to_str() + ')'; + } + return std::string("\\+") + query_->to_str(); +} + Query* NotQuerySpecification::operator()(const PlanState& state, const Substitution& sigma0) const { return new NotQuery((*query_)(state, sigma0)); } +NotQuerySpecification::~NotQuerySpecification() +{ + delete query_; +} + // AndQuerySpecification methods -std::string AndQuerySpecification::to_str() const +void AndQuerySpecification::copy_other(const AndQuerySpecification& other) { - return std::string(1,'(') + q1_->to_str() + ',' + q2_->to_str() + ')'; + q1_ = other.q1_->clone(); + q2_ = other.q2_->clone(); } -AndQuerySpecification::~AndQuerySpecification() +AndQuerySpecification::AndQuerySpecification(const AndQuerySpecification& other) { - delete q1_; - delete q2_; + copy_other(other); +} + +AndQuerySpecification& AndQuerySpecification::operator=( + const AndQuerySpecification& other) +{ + copy_other(other); + return *this; } AndQuerySpecification::AndQuerySpecification(QuerySpecification* q1, QuerySpecification* q2) : q1_(q1), q2_(q2) {} +std::string AndQuerySpecification::to_str() const +{ + return q1_->to_str() + ',' + q2_->to_str(); +} + Query* AndQuerySpecification::operator()(const PlanState& state, const Substitution& sigma0) const { return new AndQuery((*q1_)(state, sigma0), (*q2_)(state, sigma0)); } +AndQuerySpecification::~AndQuerySpecification() +{ + delete q1_; + delete q2_; +} + } diff --git a/src/queries.h b/src/queries.h index 86f6976174299c996e60387d9e8909777bfb5b69..bc6538499837d6ff927d9fd5b3b016f2d366e5c3 100644 --- a/src/queries.h +++ b/src/queries.h @@ -1,11 +1,25 @@ #ifndef _LIBIMAGINE_PLANNER_QUERIES_H_ #define _LIBIMAGINE_PLANNER_QUERIES_H_ +#define QUERY_SPEC_COMMON(Class)\ + virtual QuerySpecification* clone() const override { return new Class(*this); }; + #include "types.h" namespace imagine_planner { +class Query; +class ComparisonQuery; +class SimpleQuery; +class NotQuery; +class AndQuery; +class QuerySpecification; +class ComparisonQuerySpecification; +class SimpleQuerySpecification; +class NotQuerySpecification; +class AndQuerySpecification; + class Query : public Stringifiable { public: @@ -21,6 +35,29 @@ class Query : public Stringifiable }; +class ComparisonQuery : public Query +{ + private: + Predicate op_; + Substitution sigma0_; + bool done_; + + public: + ComparisonQuery(const PlanState&, const Predicate& op, + const Substitution& sigma0=Substitution()); + + virtual std::string to_str() const override; + + virtual bool next_solution() override; + + virtual const Substitution& get_solution() const override { return sigma0_; } + + virtual const Substitution& get_sigma0() const override { return sigma0_; } + + virtual void reset(const Substitution& sigma0) override; + +}; + class SimpleQuery : public Query { private: @@ -115,8 +152,30 @@ class QuerySpecification : public Stringifiable const Substitution& sigma0=Substitution()) const=0; virtual ~QuerySpecification() {} + + virtual QuerySpecification* clone() const =0; +}; + +class ComparisonQuerySpecification : public QuerySpecification +{ + private: + Predicate op_; + + public: + virtual std::string to_str() const override; + + ComparisonQuerySpecification(const Predicate& op); + + const Predicate& get_op() const { return op_; } + + virtual Query* operator()(const PlanState& state, + const Substitution& sigma0=Substitution()) const override; + + QUERY_SPEC_COMMON(ComparisonQuerySpecification); }; +// Query specifications: they don't need a state to be instantiated + class SimpleQuerySpecification : public QuerySpecification { private: @@ -127,8 +186,12 @@ class SimpleQuerySpecification : public QuerySpecification SimpleQuerySpecification(const Predicate& pattern); + const Predicate& get_pattern() const { return pattern_; } + virtual Query* operator()(const PlanState& state, const Substitution& sigma0=Substitution()) const override; + + QUERY_SPEC_COMMON(SimpleQuerySpecification); }; class NotQuerySpecification : public QuerySpecification @@ -136,15 +199,23 @@ class NotQuerySpecification : public QuerySpecification private: QuerySpecification* query_; + void copy_other(const NotQuerySpecification& other); + public: - virtual std::string to_str() const override; + NotQuerySpecification(const NotQuerySpecification& other); NotQuerySpecification(QuerySpecification* query); + NotQuerySpecification& operator=(const NotQuerySpecification& other); + + virtual std::string to_str() const override; + virtual Query* operator()(const PlanState& state, const Substitution& sigma0=Substitution()) const override; virtual ~NotQuerySpecification() override; + + QUERY_SPEC_COMMON(NotQuerySpecification); }; class AndQuerySpecification : public QuerySpecification @@ -152,15 +223,24 @@ class AndQuerySpecification : public QuerySpecification private: QuerySpecification* q1_; QuerySpecification* q2_; + + void copy_other(const AndQuerySpecification& other); + public: - virtual std::string to_str() const override; + AndQuerySpecification(const AndQuerySpecification& other); AndQuerySpecification(QuerySpecification* q1, QuerySpecification* q2); + AndQuerySpecification& operator=(const AndQuerySpecification& other); + + virtual std::string to_str() const override; + virtual Query* operator()(const PlanState& state, const Substitution& sigma0=Substitution()) const override; virtual ~AndQuerySpecification() override; + + QUERY_SPEC_COMMON(AndQuerySpecification); }; } /* end namespace imagine-planner */ diff --git a/src/types.cpp b/src/types.cpp index 704e501ae6cb903faddeaedc979a00b08a37b79f..b1890b1e84ce22d87852e5a9552ab6a723cfaf28 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -318,6 +318,50 @@ bool PlanState::subset_of(const PlanState& other) const return true; } +// Implementation of GoalSpecification's methods + +GoalSpecification::GoalSpecification() {} + +GoalSpecification::GoalSpecification(const std::vector<Predicate>& must_appear, + const std::vector<Predicate>& cannot_appear) + : must_appear_(must_appear), cannot_appear_(cannot_appear) {} + +std::string GoalSpecification::to_str() const +{ + std::ostringstream oss; + bool first = true; + oss << "Must appear: ["; + for (const Predicate& pred : must_appear_) + { + if (not first) oss << ','; + oss << pred; + first = false; + } + oss << "]\nCannot appear: ["; + first = true; + for (const Predicate& pred : cannot_appear_) + { + if (not first) oss << ','; + oss << pred; + first = false; + } + oss << ']'; + return oss.str(); +} + +bool GoalSpecification::operator()(const PlanState& state) const +{ + for (const Predicate& pred : must_appear_) + { + if (not state.has(pred)) return false; + } + for (const Predicate& pred : cannot_appear_) + { + if (state.has(pred)) return false; + } + return true; +} + // Implementation of stream operator std::ostream& operator<<(std::ostream& os, const Stringifiable& strable) diff --git a/src/types.h b/src/types.h index 24b9f45d8076ab6b543b0782e8185f3e24e7b12d..3235a535f3d709bf90ff904b9b161600258a1053 100644 --- a/src/types.h +++ b/src/types.h @@ -7,17 +7,41 @@ virtual std::string type() const override { return #Class; }\ virtual Term* clone() const override { return new Class(*this); }; +#include <exception> #include <functional> +#include <map> #include <memory> #include <ostream> +#include <set> #include <string> #include <vector> -#include <map> -#include <set> namespace imagine_planner { +class ImaginePlannerException; +class Stringifiable; +class Hashable; +class Term; +class LiteralTerm; +class NumberTerm; +class TermWrapper; +class Predicate; +class Substitution; +class PlanState; +class GoalSpecification; + +class ImaginePlannerException : public std::exception +{ + private: + std::string msg_; + + public: + explicit ImaginePlannerException(const std::string& msg) : msg_(msg) {} + + virtual const char* what() const throw() { return msg_.c_str(); } +}; + /** * @brief Abstract class (interface-like) that simply declares the to_str * method (to be implemented by concrete classes). @@ -330,6 +354,23 @@ class PlanState : public Stringifiable, }; +class GoalSpecification : public Stringifiable +{ + private: + std::vector<Predicate> must_appear_, cannot_appear_; + + public: + GoalSpecification(); + + GoalSpecification(const std::vector<Predicate>& must_appear, + const std::vector<Predicate>& cannot_appear); + + std::string to_str() const override; + + bool operator()(const PlanState& state) const; + +}; + /** * @brief Stream operator that conveniently puts a Stringifiable object into * an output stream.