Commit 18f34216 authored by Alejandro Suarez Hernandez's avatar Alejandro Suarez Hernandez
Browse files

Refactoring to SmartPtr completed

parent 5afe8a45
......@@ -44,27 +44,76 @@ abstract class declared in `src/imagine-planner.h`:
```c++
struct Problem
{
PlanState start;
GoalSpecification goal;
Domain domain;
PlanState::Ptr start;
GoalCondition::Ptr goal;
Domain::Ptr domain;
};
typedef std::pair<Operator,PlanState> Step;
typedef std::vector<Step> Plan;
class Plan : public Stringifiable
{
public:
typedef std::shared_ptr<const Plan> Ptr;
template<class Tree>
static Plan reconstruct_path(const Problem& problem, const Tree& tree, PlanState::Ptr endstate)
{ ... }
Plan(const Problem& problem={}, const std::vector<Operator>& ops={},
const std::vector<PlanState::Ptr>& states={});
virtual std::string to_str() const override;
bool validate(const PlanState::Ptr& state) const;
bool completed() const { return cursor_ == ops_.size(); }
const Operator& next_action();
void reset();
const std::vector<Operator>& operations() const;
const std::vector<PlanState::Ptr>& states() const;
private:
...
};
class Planner
{
public:
virtual Plan operator()(const Problem& problem) =0;
virtual ~Planner() {}
public:
enum Result{FOUND, UNSOLVABLE, NOT_FOUND};
typedef std::map<std::string, double> Statistics;
Result search(const Problem& problem);
const Statistics& get_stats() cons;
const Plan& get_plan() const;
virtual ~Planner() {};
protected:
virtual void do_search(const Problem& problem) =0;
...
private:
...
};
```
Planner implementations inherit from the `Planner` abstract class, providing a
definition for the function operator. A `Plan` consists of several `Step`s, being
one `Step` an operator and the state that is expected after the execution of this operator.
definition for the protected `do_search` operator. A `Plan` consists of a vector
of instantiated operators and a vector of states.
`PlanState` and `GoalSpecification` objects are simply collections of Predicates.
`PlanState` and `GoalCondition` objects are simply collections of Predicates.
The `Domain`'s instances are created providing the path and version of an ADES
database. Internally, the constructor method will read the whole database and
translate the operators' specifications into the internal representation of the
......@@ -229,7 +278,7 @@ an example on how to specify and solve problem:
```c++
(...)
problem.start = PlanState({Predicate("at", "lettuce", "east"),
problem.start = PlanState::create({Predicate("at", "lettuce", "east"),
Predicate("at", "wolf", "east"),
Predicate("at", "bunny", "east"),
Predicate("at", "boat", "east"),
......@@ -237,7 +286,7 @@ an example on how to specify and solve problem:
Predicate("side", "west"),
Predicate("forbidden", "lettuce", "bunny"),
Predicate("forbidden", "wolf", "bunny")});
problem.goal = GoalSpecification({Predicate("at", "lettuce", "west"),
problem.goal = GoalCondition::create({Predicate("at", "lettuce", "west"),
Predicate("at", "bunny", "west"),
Predicate("at", "wolf", "west"),
Predicate("at", "boat", "west")}, {});
......@@ -248,8 +297,17 @@ an example on how to specify and solve problem:
## To-do:
* Doxygen documentation
* PIMPL conversion?
* Arithmetic expressions
* Total effects (for all...)
* More complex GoalCondition (e.g. using query class)?
* Doxygen documentation
* IDS (Iterative Deepening Search)
* Skeleton of BFS (Best First Search) algorithm
* Infer domain static information from operators: type hierarchy, predicate
signatures, static predicates...
* State evaluators (novelty, hadd, hmax...)
* Planner automatic tests
[1]: https://github.com/lokalmatador/ades/
[2]: https://www.coolmath-games.com/0-wolf-sheep-cabbage
......
# driver source files
SET(sources types.cpp queries.cpp) # effects.cpp domains.cpp parsing.cpp imagine-planner.cpp)
SET(sources types.cpp queries.cpp effects.cpp parsing.cpp domains.cpp imagine-planner.cpp blindsearch.cpp)
# application header files
SET(headers types.h) # queries.h effects.h domains.h parsing.h imagine-planner.h)
SET(headers types.h queries.h effects.h parsing.h domains.h imagine-planner.h blindsearch.h)
# Ades
SET(CMAKE_MODULE_PATH /usr/local/lib/cmake/ades)
FIND_PACKAGE(ADES)
......
#include "blindsearch.h"
#include <iostream>
#include <queue>
#include <unordered_map>
namespace imagine_planner
{
void BreadthFirstSearch::do_search(const Problem& problem)
{
typedef std::pair<PlanState::Ptr, Operator> Preceding;
std::unordered_map<PlanState::Ptr, Preceding, PlanState::HashPtr, PlanState::EqPtr> tree;
std::queue<PlanState::Ptr> open;
open.push(problem.start);
tree[problem.start] = Preceding(nullptr, Operator());
int iteration = 0;
while (not (open.empty() or timeout() or memout(open.size())))
{
PlanState::Ptr state = open.front();
open.pop();
auto all_ops = problem.domain->instantiate_all(state);
stats_["expanded"] += 1;
stats_["generated"] += all_ops.size();
for (const Operator& op : all_ops)
{
auto successor = op(state);
if (not tree.count(successor))
{
tree[successor] = Preceding(state, op);
if ((*problem.goal)(*successor))
{
/* Goal condition satisfied */
complete_stats();
result_ = FOUND;
plan_ = Plan::reconstruct_path(problem, tree, successor);
return;
}
open.push(successor);
}
}
if (verbose_ > 0 and iteration%100 == 0)
{
/* log feedback */
std::cout << "#expanded nodes: " << stats_["expanded"]
<<", #generated nodes: " << stats_["generated"] << std::endl;
}
++iteration;
}
/* Either unsolvable problem or timeout/memout */
complete_stats();
if (open.empty()) result_ = UNSOLVABLE;
else
{
result_ = NOT_FOUND;
msg_ = timeout()? "timeout" : "memout";
}
}
bool BreadthFirstSearch::timeout() const
{
return timeout_ > 0 and elapsed() > timeout_;
}
bool BreadthFirstSearch::memout(std::size_t open_size) const
{
return max_nodes_ > 0 and open_size > max_nodes_;
}
void BreadthFirstSearch::complete_stats()
{
stats_["branching"] = stats_["generated"] / stats_["expanded"];
}
} /* end namespace */
#ifndef _LIBIMAGINE_PLANNER_BLINDSEARCH_H_
#define _LIBIMAGINE_PLANNER_BLINDSEARCH_H_
#include "imagine-planner.h"
namespace imagine_planner
{
class BreadthFirstSearch : public Planner
{
public:
BreadthFirstSearch(int verbose=0, double timeout=0, std::size_t max_nodes=0) :
verbose_(verbose), timeout_(timeout), max_nodes_(max_nodes) {}
protected:
virtual void do_search(const Problem& problem) override;
private:
bool timeout() const;
bool memout(std::size_t open_size) const;
void complete_stats();
int verbose_;
double timeout_;
std::size_t max_nodes_;
};
// [TO DO] Iterative Deepening Search
}
#endif
#include "domains.h"
#include "parsing.h"
#include <sstream>
#include <ades/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_)) {}
// Implementation of Operator's methods
OperatorSpecification::OperatorSpecification(const std::string& name,
const std::string& pre, const std::string& post) : name_(name), pre_(nullptr)
Operator::Operator(const std::string& name, const std::string& pre,
const std::string& eff) : name_(name)
{
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");
}
eff_ = parse_effects(eff);
}
std::string OperatorSpecification::to_str() const
std::string Operator::to_str() const
{
std::ostringstream oss;
oss << name_ << "\n Pre: " << pre_->to_str() << "\n Post: ";
bool first = true;
for (Effect* eff : post_)
if (is_ground())
{
if (not first) oss << ',';
oss << *eff;
first = false;
oss << name_ << "(";
bool first = true;
for (const auto& entry : *sigma_)
{
if (not first) oss << ",";
oss << entry.first << "=" << *entry.second;
first = false;
}
oss << ")";
}
else
{
oss << "Operator name: " << name_ << "\n"
<< "Precondition: " << (*pre_) << "\n"
<< "Effect: " << (*eff_) << "\n";
}
return oss.str();
}
std::vector<Operator> OperatorSpecification::instantiate(
const PlanState& state) const
PlanState::Ptr Operator::operator()(const PlanState::Ptr& state) const
{
std::vector<Operator> instantiations;
instantiations.reserve(50);
Query* query = (*pre_)(state);
while (query->next_solution())
if (not is_ground())
{
instantiations.push_back(Operator(this, query->get_solution()));
throw ImaginePlannerException("Trying to apply unground operator");
}
delete query;
return instantiations;
}
OperatorSpecification::~OperatorSpecification()
{
delete pre_;
for (Effect* eff : post_) delete eff;
auto new_state = std::make_shared<PlanState>(*state);
(*eff_)(*new_state, *sigma_);
return new_state;
}
// Implementation of Operator's methods
Operator::Operator(const OperatorSpecification* spec,
const Substitution& sigma) : spec_(spec), sigma_(sigma) {}
std::string Operator::to_str() const
bool Operator::check_precondition(const PlanState::Ptr& state) const
{
std::ostringstream oss;
oss << spec_->get_name() << '(';
bool first = true;
for (const auto& entry : sigma_)
if (not is_ground())
{
if (entry.first[0] != '_')
{
if (not first) oss << ',';
oss << entry.first << '=' << entry.second;
first = false;
}
throw ImaginePlannerException("Unground operator");
}
oss << ')';
return oss.str();
Query::Ptr query = pre_->bind(state);
query->reset(*sigma_);
return query->next_solution();
}
PlanState Operator::operator()(const PlanState& state) const
std::vector<Operator> Operator::instantiate(const PlanState::Ptr& state) const
{
PlanState result(state);
for (const Effect* eff : spec_->get_post())
std::vector<Operator> grops;
Query::Ptr query = pre_->bind(state);
while (query->next_solution())
{
(*eff)(result, sigma_);
grops.emplace_back(*this);
grops.back().sigma_.reset(new Substitution(query->get_solution()));
}
return result;
return grops;
}
// Implementation of Domain's methods
void Domain::load_ades()
Operator Operator::instantiate(const Substitution::Ptr& sigma)
{
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));
}
Operator grop(*this);
grop.sigma_ = sigma;
return grop;
}
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)
// Implementation of Domain's methods
Domain::Domain(const std::string& name, const std::string& ades_db,
std::size_t version_db)
: name_(name), ades_db_(ades_db), version_db_(version_db)
{
load_ades();
if (not ades_db.empty()) load_ades();
}
std::string Domain::to_str() const
{
std::ostringstream oss;
oss << "Domain:\nADES_DB: " << ades_db_ << " (v" << version_db_
oss << "Domain " << name_ << ":\nADES_DB: " << ades_db_ << " (v" << version_db_
<< ")\nOperators:";
for (const OperatorSpecification& opspec : available_operators_)
for (const Operator& op : ops_)
{
oss << '\n' << opspec;
oss << '\n' << op;
}
return oss.str();
}
......@@ -139,16 +110,34 @@ void Domain::set_ades_db(const std::string& ades_db, std::size_t version_db)
load_ades();
}
std::vector<Operator> Domain::instantiate_all(const PlanState& state) const
std::vector<Operator> Domain::instantiate_all(const PlanState::Ptr& state) const
{
std::vector<Operator> acc;
acc.reserve(50);
for (const OperatorSpecification& op : available_operators_)
std::vector<Operator> all;
for (const Operator& op : ops_)
{
std::vector<Operator> instantiations = op.instantiate(state);
acc.insert(acc.end(), instantiations.begin(), instantiations.end());
std::vector<Operator> grops = op.instantiate(state);
all.insert(all.end(), grops.begin(), grops.end());
}
return all;
}
void Domain::load_ades()
{
ades::AdesDB db(ades_db_, version_db_);
ops_.clear();
ops_.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;
ops_.emplace_back(name, pre, post);
}
return acc;
}
}
#ifndef _LIBIMAGINE_PLANNER_DOMAINS_H_
#define _LIBIMAGINE_PLANNER_DOMAINS_H_
#include "parsing.h"
#include "effects.h"
#include "queries.h"
namespace imagine_planner
{
class OperatorSpecification;
class Operator;
class Problem;
class Domain;
class OperatorSpecification : public Stringifiable
class Operator : public Stringifiable
{
private:
std::string name_;
QuerySpecification* pre_;
EffectV post_;
public:
OperatorSpecification(const OperatorSpecification& other);
typedef std::shared_ptr<const Operator> Ptr;
OperatorSpecification(const std::string& name, const std::string& pre,
const std::string& post);
Operator() {}
Operator(const std::string& name, const Query::Ptr& pre,
const Effect::Ptr& eff) :
name_(name), pre_(pre), eff_(eff), sigma_(nullptr) {}
Operator(const std::string& name, const std::string& pre,
const std::string& eff);
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_; }
const Query::Ptr& get_precondition() const { return pre_; }
std::vector<Operator> instantiate(const PlanState& state) const;
const Effect::Ptr& get_effect() const { return eff_; }
~OperatorSpecification();
};
const Substitution::Ptr& get_sigma() const { return sigma_; }
bool is_ground() const { return (bool)sigma_; }
class Operator : public Stringifiable
{
private:
const OperatorSpecification* spec_;
Substitution sigma_;
bool check_precondition(const PlanState::Ptr& state) const;
public:
PlanState::Ptr operator()(const PlanState::Ptr& state) const;
Operator(const OperatorSpecification* spec, const Substitution& sigma);
std::vector<Operator> instantiate(const PlanState::Ptr& state) const;
std::string to_str() const override;
Operator instantiate(const Substitution::Ptr& sigma);
PlanState operator()(const PlanState& state) const;
private:
std::string name_;
Query::Ptr pre_;
Effect::Ptr eff_;
Substitution::Ptr sigma_;
};
class Domain : public Stringifiable
{
private:
std::string ades_db_;
std::size_t version_db_;
std::vector<OperatorSpecification> available_operators_;
void load_ades();
public:
Domain();
typedef std::shared_ptr<Domain> Ptr;
Domain(const std::string& ades_db, std::size_t version_db=0);
Domain(const std::string& name="", const std::string& ades_db="",
std::size_t version_db=0);
std::string to_str() const;
virtual std::string to_str() const override;
const std::string& get_ades_db() const { return ades_db_; }
std::size_t get_version_db() const { return version_db_; }
void set_ades_db(const std::string& ades_db, std::size_t version_db=0);
const std::vector<OperatorSpecification>& get_available_operators() const
const std::vector<Operator>& get_operators() const
{
return available_operators_;
return ops_;
}
std::vector<Operator> instantiate_all(const PlanState& state) const;
std::vector<Operator> instantiate_all(const PlanState::Ptr& state) const;
private:
std::string name_, ades_db_;
std::size_t version_db_;
std::vector<Operator> ops_;
void