Skip to content

Commit

Permalink
Add preliminary binding example for systems framework.
Browse files Browse the repository at this point in the history
  • Loading branch information
EricCousineau-TRI committed Dec 13, 2017
1 parent c8a6b51 commit ec458cf
Show file tree
Hide file tree
Showing 9 changed files with 520 additions and 3 deletions.
7 changes: 4 additions & 3 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,10 @@ github_archive(

github_archive(
name = "pybind11",
repository = "RobotLocomotion/pybind11",
commit = "ffcf754ae9e766632610975d22372a86a7b63014",
sha256 = "7cd6f4efb02bf9ae17eeb2afba68023af913e61ae76e8b4254203d0eec019525", # noqa
# TODO(eric.cousineau): Merge this with RobotLocomotion.
repository = "EricCousineau-TRI/pybind11",
commit = "b7e92676552dc73613c8e18624091fa9ffd4fa54",
sha256 = "464d35d81c680ea251f3c09d6f1f2fef34e9696c49865a6cea193d6a0de9caf9", # noqa
build_file = "tools/workspace/pybind11/pybind11.BUILD.bazel",
)

Expand Down
115 changes: 115 additions & 0 deletions bindings/pydrake/systems/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# -*- python -*-

load("@drake//tools/install:install.bzl", "install")
load("//tools/lint:lint.bzl", "add_lint_tests")
load(
"//tools/skylark:pybind.bzl",
"drake_pybind_library",
"get_drake_pybind_installs",
"get_pybind_library_dest",
)
load(
"//tools/skylark:drake_py.bzl",
"drake_py_binary",
"drake_py_library",
"drake_py_test",
)
load("//tools/skylark:6996.bzl", "adjust_labels_for_drake_hoist")

package(default_visibility = adjust_labels_for_drake_hoist([
"//drake/bindings/pydrake:__subpackages__",
]))

drake_py_library(
name = "module_py",
srcs = ["__init__.py"],
deps = [
"//drake/bindings/pydrake:common_py",
# TODO(eric.cousineau): This does NOT include direct dependencies to
# submodule. This is to preserve C++-like namespaces.
],
)

drake_pybind_library(
name = "framework_py",
cc_so_name = "framework",
cc_srcs = ["framework_py.cc"],
py_deps = [
":module_py",
],
)

drake_pybind_library(
name = "primitives_py",
cc_so_name = "primitives",
cc_srcs = ["primitives_py.cc"],
py_deps = [
":framework_py",
":module_py",
],
)

drake_pybind_library(
name = "analysis_py",
cc_so_name = "analysis",
cc_srcs = ["analysis_py.cc"],
py_deps = [
":framework_py",
":module_py",
],
)

drake_py_library(
name = "drawing_py",
srcs = ["drawing.py"],
deps = [":module_py"],
# TODO(eric.cousineau): Expose information to allow `imports = ...` to be
# defined, rather than rely on `module_py`.
)

PYBIND_LIBRARIES = [
":analysis_py",
":framework_py",
":primitives_py",
]

PY_LIBRARIES = [
":drawing_py",
":module_py",
]

drake_py_library(
name = "systems",
deps = PYBIND_LIBRARIES + PY_LIBRARIES,
)

install(
name = "install",
targets = PY_LIBRARIES,
py_dest = get_pybind_library_dest(),
deps = get_drake_pybind_installs(PYBIND_LIBRARIES),
)

drake_py_test(
name = "system_general_test",
size = "small",
srcs = ["test/system_general_test.py"],
deps = [
":analysis_py",
":framework_py",
":primitives_py",
],
)

drake_py_binary(
name = "system_graphviz_example",
srcs = ["test/system_graphviz_example.py"],
deps = [
":analysis_py",
":drawing_py",
":framework_py",
":primitives_py",
],
)

add_lint_tests()
23 changes: 23 additions & 0 deletions bindings/pydrake/systems/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from __future__ import absolute_import, print_function

# TODO(eric.cousineau): Should we attempt to modularize this dependency graph?
# How about separating this setup (development) from a more pure approach
# (deployment)?

try:
from .analysis import *
except ImportError as e:
print(e)
pass

try:
from .framework import *
except ImportError as e:
print(e)
pass

try:
from .primitives import *
except ImportError as e:
print(e)
pass
25 changes: 25 additions & 0 deletions bindings/pydrake/systems/analysis_py.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <pybind11/pybind11.h>

#include "drake/systems/analysis/simulator.h"

namespace py = pybind11;

using std::unique_ptr;

PYBIND11_MODULE(analysis, m) {
// NOLINTNEXTLINE(build/namespaces): Emulate placement in namespace.
using namespace drake::systems;

auto py_iref = py::return_value_policy::reference_internal;

m.doc() = "Bindings for the analysis portion of the Systems framework.";

using T = double;

py::class_<Simulator<T>>(m, "Simulator")
.def(py::init<const System<T>&>())
.def(py::init<const System<T>&, unique_ptr<Context<T>>>())
.def("Initialize", &Simulator<T>::Initialize)
.def("StepTo", &Simulator<T>::StepTo)
.def("get_mutable_context", &Simulator<T>::get_mutable_context, py_iref);
}
33 changes: 33 additions & 0 deletions bindings/pydrake/systems/drawing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# @file
# Provides general visualization utilities. This is NOT related to `rendering`.
# @note This is an optional module, dependent on `pydot` and `matplotlib` being
# installed.

from StringIO import StringIO

import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import pydot


# TODO(eric.cousineau): Move `plot_graphviz` to something more accessible to
# `call_python_client`.


def plot_graphviz(dot_text):
"""Renders a DOT graph in matplotlib."""
# @ref https://stackoverflow.com/a/18522941/7829525
# Tried (reason ignored): pydotplus (`pydot` works), networkx
# (`read_dot` does not work robustly?), pygraphviz (coupled with
# `networkx`).
g = pydot.graph_from_dot_data(dot_text)
s = StringIO()
g.write_png(s)
s.seek(0)
plt.axis('off')
return plt.imshow(plt.imread(s), aspect="equal")


def plot_system_graphviz(system):
"""Renders a System's Graphviz representation in `matplotlib`. """
return plot_graphviz(system.GetGraphvizString())
151 changes: 151 additions & 0 deletions bindings/pydrake/systems/framework_py.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include <pybind11/eigen.h>
#include <pybind11/eval.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include "drake/common/nice_type_name.h"
#include "drake/systems/framework/abstract_values.h"
#include "drake/systems/framework/basic_vector.h"
#include "drake/systems/framework/context.h"
#include "drake/systems/framework/diagram.h"
#include "drake/systems/framework/diagram_builder.h"
#include "drake/systems/framework/leaf_context.h"
#include "drake/systems/framework/leaf_system.h"
#include "drake/systems/framework/output_port_value.h"
#include "drake/systems/framework/subvector.h"
#include "drake/systems/framework/supervector.h"
#include "drake/systems/framework/system.h"

namespace py = pybind11;

using std::make_unique;
using std::unique_ptr;
using std::vector;

PYBIND11_MODULE(framework, m) {
// NOLINTNEXTLINE(build/namespaces): Emulate placement in namespace.
using namespace drake;
// NOLINTNEXTLINE(build/namespaces): Emulate placement in namespace.
using namespace drake::systems;

// Aliases for commonly used return value policies.
// `py_ref` is used when `keep_alive` is explicitly used (e.g. for extraction
// methods, like `GetMutableSubsystemState`).
auto py_ref = py::return_value_policy::reference;
// `py_iref` is used when pointers / lvalue references are returned (no need
// for `keep_alive`, as it is implicit.
auto py_iref = py::return_value_policy::reference_internal;

m.doc() = "Bindings for the core Systems framework.";

// TODO(eric.cousineau): At present, we only bind doubles.
// In the future, we will bind more scalar types, and enable scalar
// conversion.
using T = double;

// TODO(eric.cousineau): Resolve `str_py` workaround.
auto str_py = py::eval("str");

// TODO(eric.cousineau): Show constructor, but somehow make sure `pybind11`
// knows this is abstract?
py::class_<System<T>>(m, "System")
.def("set_name", &System<T>::set_name)
.def("get_input_port", &System<T>::get_input_port, py_iref)
.def("get_output_port", &System<T>::get_output_port, py_iref)
.def("CreateDefaultContext", &System<T>::CreateDefaultContext)
.def("AllocateOutput", &System<T>::AllocateOutput)
.def(
"GetGraphvizString",
[str_py](const System<T>* self) {
// @note This is a workaround; for some reason,
// casting this using `py::str` does not work, but directly
// calling the Python function (`str_py`) does.
return str_py(self->GetGraphvizString());
});

py::class_<LeafSystem<T>, System<T>>(m, "LeafSystem");

py::class_<Context<T>>(m, "Context")
.def("FixInputPort",
py::overload_cast<int, unique_ptr<BasicVector<T>>>(
&Context<T>::FixInputPort))
.def("get_time", &Context<T>::get_time)
.def("Clone", &Context<T>::Clone)
.def("__copy__", &Context<T>::Clone)
.def("get_state", &Context<T>::get_state, py_iref)
.def("get_mutable_state", &Context<T>::get_mutable_state, py_iref);

py::class_<LeafContext<T>, Context<T>>(m, "LeafContext");

py::class_<Diagram<T>, System<T>>(m, "Diagram")
.def("GetMutableSubsystemState",
[](Diagram<T>* self, const System<T>& arg1, Context<T>* arg2)
-> auto&& {
// @note Use `auto&&` to get perfect forwarding.
// @note Compiler does not like `py::overload_cast` with this setup?
return self->GetMutableSubsystemState(arg1, arg2);
}, py_ref, py::keep_alive<0, 3>());

// Glue mechanisms.
py::class_<DiagramBuilder<T>>(m, "DiagramBuilder")
.def(py::init<>())
.def(
"AddSystem",
[](DiagramBuilder<T>* self, unique_ptr<System<T>> arg1) {
return self->AddSystem(std::move(arg1));
})
.def("Connect",
py::overload_cast<const OutputPort<T>&, const InputPortDescriptor<T>&>(
&DiagramBuilder<T>::Connect))
.def("ExportInput", &DiagramBuilder<T>::ExportInput)
.def("ExportOutput", &DiagramBuilder<T>::ExportOutput)
.def("Build", &DiagramBuilder<T>::Build)
.def("BuildInto", &DiagramBuilder<T>::BuildInto);

py::class_<OutputPort<T>>(m, "OutputPort");
py::class_<SystemOutput<T>>(m, "SystemOutput");

py::class_<InputPortDescriptor<T>>(m, "InputPortDescriptor");

// Value types.
py::class_<VectorBase<T>>(m, "VectorBase")
.def("CopyToVector", &VectorBase<T>::CopyToVector)
.def("SetFromVector", &VectorBase<T>::SetFromVector);

py::class_<BasicVector<T>, VectorBase<T>>(m, "BasicVector")
.def(py::init<VectorX<T>>())
.def("get_value", &BasicVector<T>::get_value);

py::class_<Supervector<T>, VectorBase<T>>(m, "Supervector");

py::class_<Subvector<T>, VectorBase<T>>(m, "Subvector");

// TODO(eric.cousineau): Interfacing with the C++ abstract value types may be
// a tad challenging. This should be more straightforward once
// scalar-type conversion is supported, as the template-exposure mechanisms
// should be relatively similar.
py::class_<AbstractValue>(m, "AbstractValue");

// Parameters.
// TODO(eric.cousineau): Fill this out.
py::class_<Parameters<T>>(m, "Parameters");

// State.
py::class_<State<T>>(m, "State")
.def(py::init<>())
.def("get_continuous_state",
&State<T>::get_continuous_state, py_iref)
.def("get_mutable_continuous_state",
&State<T>::get_mutable_continuous_state, py_iref);

// - Constituents.
py::class_<ContinuousState<T>>(m, "ContinuousState")
.def(py::init<>())
.def("get_vector", &ContinuousState<T>::get_vector, py_iref)
.def("get_mutable_vector",
&ContinuousState<T>::get_mutable_vector, py_iref);

py::class_<DiscreteValues<T>>(m, "DiscreteValues");

py::class_<AbstractValues>(m, "AbstractValues");
}
31 changes: 31 additions & 0 deletions bindings/pydrake/systems/primitives_py.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>

#include "drake/systems/primitives/adder.h"
#include "drake/systems/primitives/constant_value_source.h"
#include "drake/systems/primitives/constant_vector_source.h"
#include "drake/systems/primitives/integrator.h"

namespace py = pybind11;

PYBIND11_MODULE(primitives, m) {
// NOLINTNEXTLINE(build/namespaces): Emulate placement in namespace.
using namespace drake;
// NOLINTNEXTLINE(build/namespaces): Emulate placement in namespace.
using namespace drake::systems;

m.doc() = "Bindings for the primitives portion of the Systems framework.";

using T = double;

py::class_<ConstantVectorSource<T>, LeafSystem<T>>(m, "ConstantVectorSource")
.def(py::init<VectorX<T>>());

py::class_<ConstantValueSource<T>, LeafSystem<T>>(m, "ConstantValueSource");

py::class_<Adder<T>, LeafSystem<T>>(m, "Adder")
.def(py::init<int, int>());

py::class_<Integrator<T>, LeafSystem<T>>(m, "Integrator")
.def(py::init<int>());
}
Loading

0 comments on commit ec458cf

Please sign in to comment.