Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pybind11 playground #152

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.1)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

set(CMAKE_CXX_FLAGS "-O3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

project(pydownward)

include_directories("${CMAKE_SOURCE_DIR}/src/search/")
include_directories("${CMAKE_SOURCE_DIR}/src/search/ext/")
include_directories("${CMAKE_SOURCE_DIR}/python")


file (GLOB SOURCE_FILES "src/search/*.cc" "src/search/*/*.cc")
file (GLOB HEADER_FILES "src/search/*.h*" "src/search/*/*.h*")
file (GLOB PYTHON_FILES "python/*.cc" "python/*.h")

# Set up such that XCode organizes the files
#source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_FILES} ${HEADER_FILES} ${PYTHON_FILES} )

find_package(pybind11 REQUIRED)
pybind11_add_module(pydownward
${SOURCE_FILES}
${HEADER_FILES}
${PYTHON_FILES}
)

target_link_libraries(pydownward PUBLIC)

install(TARGETS pydownward
COMPONENT python
LIBRARY DESTINATION "${PYTHON_LIBRARY_DIR}"
)
62 changes: 62 additions & 0 deletions README_PYBIND.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
To install pybind11, use
apt install python3-pybind11

To test the current pybind setup, proceed as follows:

$ mkdir pybind-build
$ cd pybind-build
$ cmake .. -DPYTHON_LIBRARY_DIR="<absolutepathtorepositoryroot>/try-pybindings" -DPYTHON_EXECUTABLE="/usr/bin/python3"
(or wherever your python3 lives)
$ make -j8
$ make install
$ cd ../try-pybindings
$ ls
pydownward.cpython-310-x86_64-linux-gnu.so test.py
$ cat test.py

Put some output.sas file into the directory. The test only runs enforced
hill-climbing, which has a high likelihood to NOT solve a task. It can for
example solve the first driverlog task, so if in doubt, use that one.

Enjoy:
$ python -i test.py


Notes on the interface:

* We hacked the code in some places
- We made Heuristic::compute_heuristic public. This might be necessary for the
trampoline classes but have not looked for alternatives.
- We added a constructor Heuristic(task) that fixes some options (see
create_dummy_options_for_python_binding in heuristic.cc). The long term plan
would be t not pass the options object into the constructor and instead pass
individual parameters that are then also all exposed through the interface.
- Similarly, we added temporary constructors for the FF heuristic, search
engines, and EHC search.
- We added a global function get_root_task to access the global task. We have
to think about how to expose the task.

* The order of template parameters in py::class_<...> does not matter. Pybind
will figure out which parameter is which. We should agree on some default order.
For Heuristic, we use four parameters:
- Heuristic is the class to expose to Python
- std::shared_ptr<Heuristic> is the holder that is internally used for
reference counting, we use shared pointers to make this compatible with our
other features.
- Evaluator is the base class
- HeuristicTrampoline is a class used to handle function calls of virtual
functions. If we inherit from Heuristic in Python, the trampoline class is
responsible for forwarding calls to the Python class. See
https://pybind11.readthedocs.io/en/stable/advanced/classes.html

* Trampoline classes (EvaluatorTrampoline, HeuristicTrampoline) are needed to
derive classes in Python. They are needed for all classes in the hierarchy. For
example, if we wanted to allow users to inherit from FFHeuristic, it would also
need a trampoline class. They have to define all virtual methods that we want to
make available in Python on every level (FFHeuristicTrampoline would have to
repeat the definition of compute_heuristic). It is not clear if the trampoline
for Evaluator is needed as long as no virtual method from Evaluator is in the
interface. Probably yes, because the constructor counts.

* py::make_iterator can be used to set up an iterable but py::keep_alive is
required to keep the iterator alive while it is used.
107 changes: 107 additions & 0 deletions python/test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <functional>
#include <fstream>
#include <vector>

#include "command_line.h"
#include "heuristic.h"
#include "search_engine.h"
#include "task_proxy.h"

#include "search_engines/enforced_hill_climbing_search.h"
#include "heuristics/ff_heuristic.h"
#include "tasks/root_task.h"
#include "task_utils/task_properties.h"
#include "utils/logging.h"
#include "utils/system.h"
#include "utils/timer.h"

namespace py = pybind11;

//py::object test(const std::string &sas_file, const std::string &cmd_line) {
void read_task(const std::string &sas_file) {
std::ifstream task_file(sas_file);
tasks::read_root_task(task_file);
}

class EvaluatorTrampoline : public Evaluator {
public:
using Evaluator::Evaluator;
};

class HeuristicTrampoline : public Heuristic {
public:
using Heuristic::Heuristic;

virtual int compute_heuristic(const State &ancestor_state) override {
PYBIND11_OVERRIDE_PURE(
int, /* Return type */
Heuristic, /* Parent class */
compute_heuristic, /* Name of function in C++ (must match Python name) */
ancestor_state /* Argument(s) */
);
}
};

void init_ff(py::module_ &m) {
py::options options;
options.disable_function_signatures();

py::class_<ff_heuristic::FFHeuristic, std::shared_ptr<ff_heuristic::FFHeuristic>, Heuristic>(m, "FFHeuristic")
.def(py::init<std::shared_ptr<AbstractTask>>(), py::arg("task"), py::doc(R"delimiter(
FFHeuristic
bla bla bla synopsis

Parameters
----------
:param AbstractTask task: optional task transformation for this heuristic
)delimiter"));
}

void init_ehc(py::module_ &m) {
py::options options;
options.disable_function_signatures();

py::class_<enforced_hill_climbing_search::EnforcedHillClimbingSearch, std::shared_ptr<enforced_hill_climbing_search::EnforcedHillClimbingSearch>>(m, "EHCSearch")
.def(py::init<const std::string &, int, double, int, std::shared_ptr<Evaluator>>())
.def("search", &enforced_hill_climbing_search::EnforcedHillClimbingSearch::search, py::doc("this has some effect"))
.def("found_solution", &enforced_hill_climbing_search::EnforcedHillClimbingSearch::found_solution)
.def("get_plan", &enforced_hill_climbing_search::EnforcedHillClimbingSearch::get_plan);
}

PYBIND11_MODULE(pydownward, m) {
m.doc() = "Gabi's pybind11 example plugin"; // Optional module docstring

py::options options;
options.disable_function_signatures();

m.def("read_task", &read_task, "Read the task from sas_file", py::arg("sas_file")="output.sas");

m.def("get_root_task", &tasks::get_root_task, "Get the root task");

py::class_<AbstractTask, std::shared_ptr<AbstractTask>>(m, "AbstractTask")
.def("get_operator_name", &AbstractTask::get_operator_name);

py::class_<OperatorID>(m, "OperatorID")
.def("get_index", &OperatorID::get_index);

py::class_<FactProxy>(m, "FactProxy")
.def("get_name", &FactProxy::get_name);

py::class_<State>(m, "State")
.def("__getitem__", [](State &self, unsigned index)
{ return self[index]; })
.def("__iter__", [](const State &s) { return py::make_iterator(begin(s), end(s)); }, py::keep_alive<0, 1>());

py::class_<Evaluator, std::shared_ptr<Evaluator>, EvaluatorTrampoline>(m, "Evaluator");

py::class_<Heuristic, std::shared_ptr<Heuristic>, Evaluator, HeuristicTrampoline>(m, "Heuristic")
.def(py::init<std::shared_ptr<AbstractTask>>())
.def("compute_heuristic", &Heuristic::compute_heuristic);

std::vector<std::function<void(py::module_ &)>> init_functions = {init_ff, init_ehc};
for(auto f : init_functions) {
f(m);
}
}
17 changes: 17 additions & 0 deletions src/search/heuristic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "task_utils/task_properties.h"
#include "tasks/cost_adapted_task.h"
#include "tasks/root_task.h"
#include "utils/logging.h"

#include <cassert>
#include <cstdlib>
Expand All @@ -22,6 +23,22 @@ Heuristic::Heuristic(const plugins::Options &opts)
task_proxy(*task) {
}


plugins::Options create_dummy_options_for_python_binding() {
plugins::Options opts;
opts.set<utils::Verbosity>("verbosity", utils::Verbosity::NORMAL);
opts.set_unparsed_config("Evaluator created from Python");
return opts;
}

Heuristic::Heuristic(shared_ptr<AbstractTask> task)
: Evaluator(create_dummy_options_for_python_binding(), true, true, true),
heuristic_cache(HEntry(NO_VALUE, true)),
cache_evaluator_values(true),
task(task),
task_proxy(*task) {
}

Heuristic::~Heuristic() {
}

Expand Down
5 changes: 3 additions & 2 deletions src/search/heuristic.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ class Heuristic : public Evaluator {

enum {DEAD_END = -1, NO_VALUE = -2};

virtual int compute_heuristic(const State &ancestor_state) = 0;

/*
Usage note: Marking the same operator as preferred multiple times
is OK -- it will only appear once in the list of preferred
Expand All @@ -74,8 +72,11 @@ class Heuristic : public Evaluator {

public:
explicit Heuristic(const plugins::Options &opts);
explicit Heuristic(std::shared_ptr<AbstractTask> task);
virtual ~Heuristic() override;

virtual int compute_heuristic(const State &ancestor_state) = 0;

virtual void get_path_dependent_evaluators(
std::set<Evaluator *> & /*evals*/) override {
}
Expand Down
17 changes: 17 additions & 0 deletions src/search/heuristics/ff_heuristic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ FFHeuristic::FFHeuristic(const plugins::Options &opts)
}
}

static plugins::Options mimic_options(shared_ptr<AbstractTask> task) {
plugins::Options opts;
opts.set<bool>("cache_estimates", true);
opts.set<shared_ptr<AbstractTask>>("transform", task);
opts.set<utils::Verbosity>("verbosity", utils::Verbosity::NORMAL);
opts.set_unparsed_config("Manual FF");
return opts;
}

FFHeuristic::FFHeuristic(shared_ptr<AbstractTask> task)
: AdditiveHeuristic(mimic_options(task)),
relaxed_plan(task_proxy.get_operators().size(), false) {
if (log.is_at_least_normal()) {
log << "Initializing FF heuristic..." << endl;
}
}

void FFHeuristic::mark_preferred_operators_and_relaxed_plan(
const State &state, PropID goal_id) {
Proposition *goal = get_proposition(goal_id);
Expand Down
1 change: 1 addition & 0 deletions src/search/heuristics/ff_heuristic.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class FFHeuristic : public additive_heuristic::AdditiveHeuristic {
virtual int compute_heuristic(const State &ancestor_state) override;
public:
explicit FFHeuristic(const plugins::Options &opts);
explicit FFHeuristic(std::shared_ptr<AbstractTask> task);
};
}

Expand Down
22 changes: 22 additions & 0 deletions src/search/search_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ successor_generator::SuccessorGenerator &get_successor_generator(
return successor_generator;
}

SearchEngine::SearchEngine(const std::string &description, OperatorCost cost_type, double max_time, int cost_bound)
: description(description),
status(IN_PROGRESS),
solution_found(false),
task(tasks::g_root_task),
task_proxy(*task),
log(utils::g_log), // TODO the options version can set the verbosity level
state_registry(task_proxy),
successor_generator(get_successor_generator(task_proxy, log)),
search_space(state_registry, log),
statistics(log),
cost_type(cost_type),
is_unit_cost(task_properties::is_unit_cost(task_proxy)),
max_time(max_time) {
if (cost_bound < 0) {
cerr << "error: negative cost bound " << cost_bound << endl;
utils::exit_with(ExitCode::SEARCH_INPUT_ERROR);
}
bound = cost_bound;
task_properties::print_variable_statistics(task_proxy);
}

SearchEngine::SearchEngine(const plugins::Options &opts)
: description(opts.get_unparsed_config()),
status(IN_PROGRESS),
Expand Down
1 change: 1 addition & 0 deletions src/search/search_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class SearchEngine {
int get_adjusted_cost(const OperatorProxy &op) const;
public:
SearchEngine(const plugins::Options &opts);
SearchEngine(const std::string &description, OperatorCost cost_type, double max_time, int cost_bound);
virtual ~SearchEngine();
virtual void print_statistics() const = 0;
virtual void save_plan_if_necessary();
Expand Down
33 changes: 33 additions & 0 deletions src/search/search_engines/enforced_hill_climbing_search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,39 @@ EnforcedHillClimbingSearch::EnforcedHillClimbingSearch(
opts, use_preferred, preferred_usage)->create_edge_open_list();
}


// TODO we do not support preferred operators, selection of preferred usage
// TODO verbosity fixed to NORMAL
EnforcedHillClimbingSearch::EnforcedHillClimbingSearch(const std::string &description,
int cost_type, double max_time, int cost_bound,
shared_ptr<Evaluator> h)
: SearchEngine(description, OperatorCost(cost_type), max_time, cost_bound),
evaluator(h),
preferred_operator_evaluators(vector<shared_ptr<Evaluator>>()),
preferred_usage(PreferredUsage::RANK_PREFERRED_FIRST),
current_eval_context(state_registry.get_initial_state(), &statistics),
current_phase_start_g(-1),
num_ehc_phases(0),
last_num_expanded(-1) {
for (const shared_ptr<Evaluator> &eval : preferred_operator_evaluators) {
eval->get_path_dependent_evaluators(path_dependent_evaluators);
}
evaluator->get_path_dependent_evaluators(path_dependent_evaluators);

State initial_state = state_registry.get_initial_state();
for (Evaluator *evaluator : path_dependent_evaluators) {
evaluator->notify_initial_state(initial_state);
}
use_preferred = find(preferred_operator_evaluators.begin(),
preferred_operator_evaluators.end(), evaluator) !=
preferred_operator_evaluators.end();

plugins::Options options;
options.set("verbosity", utils::Verbosity::NORMAL);
open_list = create_ehc_open_list_factory(
options, use_preferred, preferred_usage)->create_edge_open_list();
}

EnforcedHillClimbingSearch::~EnforcedHillClimbingSearch() {
}

Expand Down
3 changes: 3 additions & 0 deletions src/search/search_engines/enforced_hill_climbing_search.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class EnforcedHillClimbingSearch : public SearchEngine {

public:
explicit EnforcedHillClimbingSearch(const plugins::Options &opts);
EnforcedHillClimbingSearch(const std::string &description,
int cost_type, double max_time, int cost_bound,
std::shared_ptr<Evaluator> h);
virtual ~EnforcedHillClimbingSearch() override;

virtual void print_statistics() const override;
Expand Down
5 changes: 5 additions & 0 deletions src/search/tasks/root_task.cc
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,11 @@ void read_root_task(istream &in) {
g_root_task = make_shared<RootTask>(in);
}

shared_ptr<AbstractTask> get_root_task() {
return g_root_task;
}


class RootTaskFeature : public plugins::TypedFeature<AbstractTask, AbstractTask> {
public:
RootTaskFeature() : TypedFeature("no_transform") {
Expand Down
Loading