From fa4231ef92e918090592e20e310058653a21b3de Mon Sep 17 00:00:00 2001
From: bcumming <bcumming@cscs.ch>
Date: Wed, 5 Jun 2019 11:58:15 +0200
Subject: [PATCH] move python wrapper for schedules to their own files

---
 .ycm_extra_conf.py         |   4 +
 python/CMakeLists.txt      |   1 +
 python/context.cpp         |  15 +--
 python/conversion.hpp      |  16 +++
 python/error.hpp           |  16 +--
 python/event_generator.cpp | 229 +----------------------------------
 python/event_generator.hpp |   6 +-
 python/pyarb.cpp           |   5 +-
 python/recipe.cpp          |  11 +-
 python/schedule.cpp        | 236 +++++++++++++++++++++++++++++++++++++
 python/schedule.hpp        |  79 +++++++++++++
 11 files changed, 356 insertions(+), 262 deletions(-)
 create mode 100644 python/schedule.cpp
 create mode 100644 python/schedule.hpp

diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
index f3ea3705..27093e79 100644
--- a/.ycm_extra_conf.py
+++ b/.ycm_extra_conf.py
@@ -51,6 +51,10 @@ flags = [
     '-I',
     'arbor',
     '-I',
+    'arbor/include',
+    '-I',
+    'build/arbor/include',
+    '-I',
     'ext/json/single_include',
     '-I',
     'build/include',
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index 8065522a..9a000b3e 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -23,6 +23,7 @@ add_library(pyarb MODULE
     mpi.cpp
     pyarb.cpp
     recipe.cpp
+    schedule.cpp
     strings.cpp
 )
 
diff --git a/python/context.cpp b/python/context.cpp
index 04cb3ab0..bab2b278 100644
--- a/python/context.cpp
+++ b/python/context.cpp
@@ -8,7 +8,6 @@
 
 #include <pybind11/pybind11.h>
 
-
 #include "context.hpp"
 #include "conversion.hpp"
 #include "error.hpp"
@@ -20,25 +19,21 @@
 
 namespace pyarb {
 
-namespace {
-auto is_nonneg_int = [](int n){ return n>=0; };
-}
-
 // A Python shim that holds the information that describes an arb::proc_allocation.
 struct proc_allocation_shim {
     arb::util::optional<int> gpu_id = {};
     int num_threads = 1;
 
-    proc_allocation_shim(): proc_allocation_shim(1, pybind11::none()) {}
-
     proc_allocation_shim(int threads, pybind11::object gpu) {
         set_num_threads(threads);
         set_gpu_id(gpu);
     }
 
+    proc_allocation_shim(): proc_allocation_shim(1, pybind11::none()) {}
+
     // getter and setter (in order to assert when being set)
     void set_gpu_id(pybind11::object gpu) {
-        gpu_id = py2optional<int>(gpu, "gpu_id must be None, or a non-negative integer", is_nonneg_int);
+        gpu_id = py2optional<int>(gpu, "gpu_id must be None, or a non-negative integer", is_nonneg());
     };
 
     void set_num_threads(int threads) {
@@ -133,7 +128,7 @@ void register_contexts(pybind11::module& m) {
                 const char* gpu_err_str = "gpu_id must be None, or a non-negative integer";
                 const char* mpi_err_str = "mpi must be None, or an MPI communicator";
 
-                auto gpu_id = py2optional<int>(gpu, gpu_err_str, is_nonneg_int);
+                auto gpu_id = py2optional<int>(gpu, gpu_err_str, is_nonneg());
                 arb::proc_allocation alloc(threads, gpu_id.value_or(-1));
 
                 if (can_convert_to_mpi_comm(mpi)) {
@@ -153,7 +148,7 @@ void register_contexts(pybind11::module& m) {
 #else
         .def(pybind11::init(
             [](int threads, pybind11::object gpu){
-                auto gpu_id = py2optional<int>(gpu, "gpu_id must be None, or a non-negative integer", is_nonneg_int);
+                auto gpu_id = py2optional<int>(gpu, "gpu_id must be None, or a non-negative integer", is_nonneg());
                 return context_shim(arb::make_context(arb::proc_allocation(threads, gpu_id.value_or(-1))));
             }),
              "threads"_a=1, "gpu_id"_a=pybind11::none(),
diff --git a/python/conversion.hpp b/python/conversion.hpp
index 811eb74e..ad0ae582 100644
--- a/python/conversion.hpp
+++ b/python/conversion.hpp
@@ -2,11 +2,27 @@
 
 #include <pybind11/pybind11.h>
 #include <pybind11/pytypes.h>
+#include <pybind11/stl.h>
 
+#include <arbor/util/optional.hpp>
 #include "error.hpp"
 
+// from https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html?highlight=boost%3A%3Aoptional#c-17-library-containers
+namespace pybind11 { namespace detail {
+    template <typename T>
+    struct type_caster<arb::util::optional<T>>: optional_caster<arb::util::optional<T>> {};
+}}
+
 namespace pyarb {
 
+struct is_nonneg {
+    template<typename T>
+    constexpr
+    bool operator()(const T& v) {
+        return v>=T(0);
+    }
+};
+
 // A helper function for converting from a Python object to a C++ optional wrapper.
 // Throws an runtime_error exception with msg if either the Python object
 // can't be converted to type T, or if the predicate is false for the value.
diff --git a/python/error.hpp b/python/error.hpp
index fbcd5ba7..f3b0cb6e 100644
--- a/python/error.hpp
+++ b/python/error.hpp
@@ -3,18 +3,6 @@
 #include <stdexcept>
 #include <string>
 
-#include <arbor/util/optional.hpp>
-
-#include <pybind11/pybind11.h>
-#include <pybind11/pytypes.h>
-#include <pybind11/stl.h>
-
-// from https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html?highlight=boost%3A%3Aoptional#c-17-library-containers
-namespace pybind11 { namespace detail {
-    template <typename T>
-    struct type_caster<arb::util::optional<T>> : optional_caster<arb::util::optional<T>> {};
-}}
-
 namespace pyarb {
 
 // Python wrapper errors
@@ -22,9 +10,11 @@ namespace pyarb {
 struct pyarb_error: std::runtime_error {
     pyarb_error(const std::string& what_msg):
         std::runtime_error(what_msg) {}
+    pyarb_error(const char* what_msg):
+        std::runtime_error(what_msg) {}
 };
 
-static
+inline
 void assert_throw(bool pred, const char* msg) {
     if (!pred) throw pyarb_error(msg);
 }
diff --git a/python/event_generator.cpp b/python/event_generator.cpp
index 9344a3fd..96505ba9 100644
--- a/python/event_generator.cpp
+++ b/python/event_generator.cpp
@@ -4,6 +4,7 @@
 
 #include <arbor/common_types.hpp>
 #include <arbor/schedule.hpp>
+#include <arbor/util/optional.hpp>
 
 #include <pybind11/pybind11.h>
 #include <pybind11/pytypes.h>
@@ -12,135 +13,10 @@
 #include "conversion.hpp"
 #include "error.hpp"
 #include "event_generator.hpp"
+#include "schedule.hpp"
 
 namespace pyarb {
 
-namespace {
-auto is_nonneg = [](auto&& t){ return t>=0.; };
-}
-
-// A Python shim that holds the information that describes an
-// arb::regular_schedule. This is wrapped in pybind11, and users constructing
-// a regular_schedule in python are manipulating this type. This is converted to
-// an arb::regular_schedule when a C++ recipe is created from a Python recipe.
-struct regular_schedule_shim {
-    using time_type = arb::time_type;
-    using opt_time_type = arb::util::optional<time_type>;
-
-    opt_time_type tstart = {};
-    opt_time_type tstop = {};
-    time_type dt = 0;
-
-    regular_schedule_shim() = default;
-
-    regular_schedule_shim(pybind11::object t0, time_type deltat, pybind11::object t1) {
-        set_tstart(t0);
-        set_tstop(t1);
-        set_dt(deltat);
-    }
-
-    // getter and setter (in order to assert when being set)
-    void set_tstart(pybind11::object t) {
-        tstart = py2optional<time_type>(t, "tstart must a non-negative number, or None", is_nonneg);
-    };
-    void set_tstop(pybind11::object t) {
-        tstop = py2optional<time_type>(t, "tstop must a non-negative number, or None", is_nonneg);
-    };
-    void set_dt(time_type delta_t) {
-        pyarb::assert_throw(is_nonneg(delta_t), "dt must be a non-negative number");
-        dt = delta_t;
-    };
-
-    opt_time_type get_tstart() const { return tstart; }
-    opt_time_type get_dt()     const { return dt; }
-    opt_time_type get_tstop()  const { return tstop; }
-
-    arb::schedule schedule() const {
-        return arb::regular_schedule(
-                tstart.value_or(arb::terminal_time),
-                dt,
-                tstop.value_or(arb::terminal_time));
-    }
-
-};
-
-// A Python shim for arb::explicit_schedule.
-// This is wrapped in pybind11, and users constructing an explicit_schedule in
-// Python are manipulating this type. This is converted to an
-// arb::explicit_schedule when a C++ recipe is created from a Python recipe.
-
-struct explicit_schedule_shim {
-    using time_type = arb::time_type;
-
-    std::vector<time_type> times;
-
-    explicit_schedule_shim() = default;
-
-    explicit_schedule_shim(std::vector<time_type> t) {
-        set_times(t);
-    }
-
-    // getter and setter (in order to assert when being set)
-    void set_times(std::vector<time_type> t) {
-        times = std::move(t);
-        // Sort the times in ascending order if necessary
-        if (!std::is_sorted(times.begin(), times.end())) {
-            std::sort(times.begin(), times.end());
-        }
-        // Assert that there are no negative times
-        if (times.size()) {
-            pyarb::assert_throw(is_nonneg(times[0]),
-                    "explicit time schedule can not contain negative values");
-        }
-    };
-
-    std::vector<time_type> get_times() const { return times; }
-
-    arb::schedule schedule() const {
-        return arb::explicit_schedule(times);
-    }
-};
-
-// A Python shim for arb::poisson_schedule.
-// This is wrapped in pybind11, and users constructing a poisson_schedule in
-// Python are manipulating this type. This is converted to an
-// arb::poisson_schedule when a C++ recipe is created from a Python recipe.
-
-struct poisson_schedule_shim {
-    using rng_type = std::mt19937_64;
-    using time_type = arb::time_type;
-
-    time_type tstart = arb::terminal_time;
-    time_type freq = 10.;
-    rng_type::result_type seed = 0;
-
-    poisson_schedule_shim() = default;
-
-    poisson_schedule_shim(time_type ts, time_type f, rng_type::result_type s) {
-        set_tstart(ts);
-        set_freq(f);
-        seed = s;
-    }
-
-    void set_tstart(time_type t) {
-        pyarb::assert_throw(is_nonneg(t), "tstart must be a non-negative number");
-        tstart = t;
-    };
-
-    void set_freq(time_type f) {
-        pyarb::assert_throw(is_nonneg(f), "frequency must be a non-negative number");
-        freq = f;
-    };
-
-    const time_type get_tstart() const { return tstart; }
-    const time_type get_freq() const { return freq; }
-
-    arb::schedule schedule() const {
-        // convert frequency to kHz.
-        return arb::poisson_schedule(tstart, freq/1000., rng_type(seed));
-    }
-};
-
 template <typename Sched>
 event_generator_shim make_event_generator(
         arb::cell_member_type target,
@@ -150,110 +26,9 @@ event_generator_shim make_event_generator(
     return event_generator_shim(target, weight, sched.schedule());
 }
 
-// Helper template for printing C++ optional types in Python.
-// Prints either the value, or None if optional value is not set.
-template <typename T>
-std::string to_string(const arb::util::optional<T>& o, std::string unit) {
-    if (!o) return "None";
-
-    std::stringstream s;
-    s << *o << " " << unit;
-    return s.str();
-}
-
-std::string schedule_regular_string(const regular_schedule_shim& r) {
-    std::stringstream s;
-    s << "<regular_schedule: "
-      << "tstart " << to_string(r.tstart, "ms") << ", "
-      << "dt " << r.dt << " ms, "
-      << "tstop " << to_string(r.tstop, "ms") << ">";
-    return s.str();
-};
-
-std::string schedule_explicit_string(const explicit_schedule_shim& e) {
-    std::stringstream s;
-    s << "<explicit_schedule: times [";
-    bool first = true;
-    for (auto t: e.times) {
-        if (!first) {
-            s << " ";
-        }
-        s << t;
-        first = false;
-    }
-    s << "] ms>";
-    return s.str();
-};
-
-std::string schedule_poisson_string(const poisson_schedule_shim& p) {
-    std::stringstream s;
-    s << "<poisson_schedule: tstart " << p.tstart << " ms"
-      << ", freq " << p.freq << " Hz"
-      << ", seed " << p.seed << ">";
-    return s.str();
-};
-
 void register_event_generators(pybind11::module& m) {
     using namespace pybind11::literals;
-    using time_type = arb::time_type;
-
-    // Regular schedule
-    pybind11::class_<regular_schedule_shim> regular_schedule(m, "regular_schedule",
-        "Describes a regular schedule with multiples of dt within the interval [tstart, tstop).");
-
-    regular_schedule
-        .def(pybind11::init<pybind11::object, time_type, pybind11::object>(),
-            "tstart"_a = pybind11::none(), "dt"_a = 0., "tstop"_a = pybind11::none(),
-            "Construct a regular schedule with arguments:\n"
-            "  tstart: The delivery time of the first event in the sequence (in ms, default None).\n"
-            "  dt:     The interval between time points (in ms, default 0).\n"
-            "  tstop:  No events delivered after this time (in ms, default None).")
-        .def_property("tstart", &regular_schedule_shim::get_tstart, &regular_schedule_shim::set_tstart,
-            "The delivery time of the first event in the sequence (in ms).")
-        .def_property("tstop", &regular_schedule_shim::get_tstop, &regular_schedule_shim::set_tstop,
-            "No events delivered after this time (in ms).")
-        .def_property("dt", &regular_schedule_shim::get_dt, &regular_schedule_shim::set_dt,
-            "The interval between time points (in ms).")
-        .def("__str__", &schedule_regular_string)
-        .def("__repr__",&schedule_regular_string);
-
-    // Explicit schedule
-    pybind11::class_<explicit_schedule_shim> explicit_schedule(m, "explicit_schedule",
-        "Describes an explicit schedule at a predetermined (sorted) sequence of times.");
-
-    explicit_schedule
-        .def(pybind11::init<>(),
-            "Construct an empty explicit schedule.\n")
-        .def(pybind11::init<std::vector<time_type>>(),
-            "times"_a,
-            "Construct an explicit schedule with argument:\n"
-            "  times: A list of times (in ms, default []).")
-        .def_property("times", &explicit_schedule_shim::get_times, &explicit_schedule_shim::set_times,
-            "A list of times (in ms).")
-        .def("__str__", &schedule_explicit_string)
-        .def("__repr__",&schedule_explicit_string);
-
-    // Poisson schedule
-    pybind11::class_<poisson_schedule_shim> poisson_schedule(m, "poisson_schedule",
-        "Describes a schedule according to a Poisson process.");
-
-    poisson_schedule
-        .def(pybind11::init<time_type, time_type, std::mt19937_64::result_type>(),
-            "tstart"_a = 0., "freq"_a = 10., "seed"_a = 0,
-            "Construct a Poisson schedule with arguments:\n"
-            "  tstart: The delivery time of the first event in the sequence (in ms, default 0 ms).\n"
-            "  freq:   The expected frequency (in Hz, default 10 Hz).\n"
-            "  seed:   The seed for the random number generator (default 0).")
-        .def_property("tstart", &poisson_schedule_shim::get_tstart, &poisson_schedule_shim::set_tstart,
-            "The delivery time of the first event in the sequence (in ms).")
-        .def_property("freq", &poisson_schedule_shim::get_freq, &poisson_schedule_shim::set_freq,
-            "The expected frequency (in Hz).")
-        .def_readwrite("seed", &poisson_schedule_shim::seed,
-            "The seed for the random number generator.")
-        .def("__str__", &schedule_poisson_string)
-        .def("__repr__",&schedule_poisson_string);
 
-// Event generator
     pybind11::class_<event_generator_shim> event_generator(m, "event_generator");
 
     event_generator
diff --git a/python/event_generator.hpp b/python/event_generator.hpp
index ed41c10e..d58d8d4d 100644
--- a/python/event_generator.hpp
+++ b/python/event_generator.hpp
@@ -3,8 +3,6 @@
 #include <arbor/common_types.hpp>
 #include <arbor/schedule.hpp>
 
-#include <pybind11/pybind11.h>
-
 namespace pyarb {
 
 struct event_generator_shim {
@@ -12,8 +10,8 @@ struct event_generator_shim {
     double weight;
     arb::schedule time_sched;
 
-    event_generator_shim(arb::cell_member_type cell, double event_weight, arb::schedule sched):
-        target(cell),
+    event_generator_shim(arb::cell_member_type gid, double event_weight, arb::schedule sched):
+        target(gid),
         weight(event_weight),
         time_sched(std::move(sched))
     {}
diff --git a/python/pyarb.cpp b/python/pyarb.cpp
index c964caa4..965d6406 100644
--- a/python/pyarb.cpp
+++ b/python/pyarb.cpp
@@ -10,10 +10,12 @@ void register_config(pybind11::module& m);
 void register_contexts(pybind11::module& m);
 void register_event_generators(pybind11::module& m);
 void register_identifiers(pybind11::module& m);
+void register_recipe(pybind11::module& m);
+void register_schedules(pybind11::module& m);
+
 #ifdef ARB_MPI_ENABLED
 void register_mpi(pybind11::module& m);
 #endif
-void register_recipe(pybind11::module& m);
 }
 
 PYBIND11_MODULE(arbor, m) {
@@ -28,4 +30,5 @@ PYBIND11_MODULE(arbor, m) {
     pyarb::register_mpi(m);
     #endif
     pyarb::register_recipe(m);
+    pyarb::register_schedules(m);
 }
diff --git a/python/recipe.cpp b/python/recipe.cpp
index 93d1e2bf..8bb0980e 100644
--- a/python/recipe.cpp
+++ b/python/recipe.cpp
@@ -19,7 +19,6 @@
 
 namespace pyarb {
 
-// ========================================= Unwrap =========================================
 // The py::recipe::cell_decription returns a pybind11::object, that is
 // unwrapped and copied into a arb::util::unique_any.
 
@@ -58,10 +57,9 @@ arb::util::unique_any py_recipe_shim::get_cell_description(arb::cell_gid_type gi
 // The py::recipe::global_properties returns a pybind11::object, that is
 // unwrapped and copied into a arb::util::any.
 arb::util::any py_recipe_shim::get_global_properties(arb::cell_kind kind) const {
-    using pybind11::isinstance;
     using pybind11::cast;
 
-    // Aquire the GIL because it must be held when calling isinstance and cast.
+    // Aquire the GIL because it must be held when calling cast.
     auto guard = pybind11::gil_scoped_acquire();
 
     // Get the python object pyarb::global_properties from the python front end
@@ -73,10 +71,9 @@ arb::util::any py_recipe_shim::get_global_properties(arb::cell_kind kind) const
 
     else return arb::util::any{};
 
-    throw pyarb_error(
-                        "recipe.global_properties returned \""
-                        + std::string(pybind11::str(o))
-                        + "\" which does not describe a known Arbor global property description");
+    throw pyarb_error( "recipe.global_properties returned \""
+                       + std::string(pybind11::str(o))
+                       + "\" which does not describe a known Arbor global property description");
 
 }
 
diff --git a/python/schedule.cpp b/python/schedule.cpp
new file mode 100644
index 00000000..2b81a148
--- /dev/null
+++ b/python/schedule.cpp
@@ -0,0 +1,236 @@
+#include <arbor/schedule.hpp>
+#include <arbor/common_types.hpp>
+#include <arbor/util/optional.hpp>
+
+#include <pybind11/pybind11.h>
+
+#include "conversion.hpp"
+#include "schedule.hpp"
+
+namespace pyarb {
+
+//
+// regular_schedule shim
+//
+
+regular_schedule_shim::regular_schedule_shim(
+        pybind11::object t0,
+        time_type deltat,
+        pybind11::object t1)
+{
+    set_tstart(t0);
+    set_tstop(t1);
+    set_dt(deltat);
+}
+
+void regular_schedule_shim::set_tstart(pybind11::object t) {
+    tstart = py2optional<time_type>(
+            t, "tstart must a non-negative number, or None", is_nonneg());
+};
+
+void regular_schedule_shim::set_tstop(pybind11::object t) {
+    tstop = py2optional<time_type>(
+            t, "tstop must a non-negative number, or None", is_nonneg());
+};
+
+void regular_schedule_shim::set_dt(arb::time_type delta_t) {
+    pyarb::assert_throw(is_nonneg()(delta_t), "dt must be a non-negative number");
+    dt = delta_t;
+};
+
+regular_schedule_shim::opt_time_type regular_schedule_shim::get_tstart() const {
+    return tstart;
+}
+
+regular_schedule_shim::opt_time_type regular_schedule_shim::get_dt() const {
+    return dt;
+}
+
+regular_schedule_shim::opt_time_type regular_schedule_shim::get_tstop() const {
+    return tstop;
+}
+
+arb::schedule regular_schedule_shim::schedule() const {
+    return arb::regular_schedule(
+            tstart.value_or(arb::terminal_time),
+            dt,
+            tstop.value_or(arb::terminal_time));
+}
+
+//
+// explicit_schedule shim
+//
+
+//struct explicit_schedule_shim {
+explicit_schedule_shim::explicit_schedule_shim(std::vector<arb::time_type> t) {
+    set_times(t);
+}
+
+// getter and setter (in order to assert when being set)
+void explicit_schedule_shim::set_times(std::vector<arb::time_type> t) {
+    times = std::move(t);
+
+    // Sort the times in ascending order if necessary
+    if (!std::is_sorted(times.begin(), times.end())) {
+        std::sort(times.begin(), times.end());
+    }
+
+    // Assert that there are no negative times
+    if (times.size()) {
+        pyarb::assert_throw(is_nonneg()(times[0]),
+                "explicit time schedule can not contain negative values");
+    }
+};
+
+std::vector<arb::time_type> explicit_schedule_shim::get_times() const {
+    return times;
+}
+
+arb::schedule explicit_schedule_shim::schedule() const {
+    return arb::explicit_schedule(times);
+}
+
+//
+// poisson_schedule shim
+//
+
+poisson_schedule_shim::poisson_schedule_shim(
+        arb::time_type ts,
+        arb::time_type f,
+        rng_type::result_type s)
+{
+    set_tstart(ts);
+    set_freq(f);
+    seed = s;
+}
+
+void poisson_schedule_shim::set_tstart(arb::time_type t) {
+    pyarb::assert_throw(is_nonneg()(t), "tstart must be a non-negative number");
+    tstart = t;
+};
+
+void poisson_schedule_shim::set_freq(arb::time_type f) {
+    pyarb::assert_throw(is_nonneg()(f), "frequency must be a non-negative number");
+    freq = f;
+};
+
+arb::time_type poisson_schedule_shim::get_tstart() const {
+    return tstart;
+}
+
+arb::time_type poisson_schedule_shim::get_freq() const {
+    return freq;
+}
+
+arb::schedule poisson_schedule_shim::schedule() const {
+    // convert frequency to kHz.
+    return arb::poisson_schedule(tstart, freq/1000., rng_type(seed));
+}
+
+void register_schedules(pybind11::module& m) {
+    using namespace pybind11::literals;
+    using time_type = arb::time_type;
+
+    // Regular schedule
+    pybind11::class_<regular_schedule_shim> regular_schedule(m, "regular_schedule",
+        "Describes a regular schedule with multiples of dt within the interval [tstart, tstop).");
+
+    regular_schedule
+        .def(pybind11::init<pybind11::object, time_type, pybind11::object>(),
+            "tstart"_a = pybind11::none(), "dt"_a = 0., "tstop"_a = pybind11::none(),
+            "Construct a regular schedule with arguments:\n"
+            "  tstart: The delivery time of the first event in the sequence (in ms, default None).\n"
+            "  dt:     The interval between time points (in ms, default 0).\n"
+            "  tstop:  No events delivered after this time (in ms, default None).")
+        .def_property("tstart", &regular_schedule_shim::get_tstart, &regular_schedule_shim::set_tstart,
+            "The delivery time of the first event in the sequence (in ms).")
+        .def_property("tstop", &regular_schedule_shim::get_tstop, &regular_schedule_shim::set_tstop,
+            "No events delivered after this time (in ms).")
+        .def_property("dt", &regular_schedule_shim::get_dt, &regular_schedule_shim::set_dt,
+            "The interval between time points (in ms).")
+        ; //.def("__str__", &schedule_regular_string)
+        //.def("__repr__",&schedule_regular_string);
+
+    // Explicit schedule
+    pybind11::class_<explicit_schedule_shim> explicit_schedule(m, "explicit_schedule",
+        "Describes an explicit schedule at a predetermined (sorted) sequence of times.");
+
+    explicit_schedule
+        .def(pybind11::init<>(),
+            "Construct an empty explicit schedule.\n")
+        .def(pybind11::init<std::vector<time_type>>(),
+            "times"_a,
+            "Construct an explicit schedule with argument:\n"
+            "  times: A list of times (in ms, default []).")
+        .def_property("times", &explicit_schedule_shim::get_times, &explicit_schedule_shim::set_times,
+            "A list of times (in ms).")
+        ; //.def("__str__", &schedule_explicit_string)
+        //.def("__repr__",&schedule_explicit_string);
+
+    // Poisson schedule
+    pybind11::class_<poisson_schedule_shim> poisson_schedule(m, "poisson_schedule",
+        "Describes a schedule according to a Poisson process.");
+
+    poisson_schedule
+        .def(pybind11::init<time_type, time_type, std::mt19937_64::result_type>(),
+            "tstart"_a = 0., "freq"_a = 10., "seed"_a = 0,
+            "Construct a Poisson schedule with arguments:\n"
+            "  tstart: The delivery time of the first event in the sequence (in ms, default 0 ms).\n"
+            "  freq:   The expected frequency (in Hz, default 10 Hz).\n"
+            "  seed:   The seed for the random number generator (default 0).")
+        .def_property("tstart", &poisson_schedule_shim::get_tstart, &poisson_schedule_shim::set_tstart,
+            "The delivery time of the first event in the sequence (in ms).")
+        .def_property("freq", &poisson_schedule_shim::get_freq, &poisson_schedule_shim::set_freq,
+            "The expected frequency (in Hz).")
+        .def_readwrite("seed", &poisson_schedule_shim::seed,
+            "The seed for the random number generator.")
+        ; //.def("__str__", &schedule_poisson_string)
+        //.def("__repr__",&schedule_poisson_string);
+}
+
+
+}
+/*
+// Helper template for printing C++ optional types in Python.
+// Prints either the value, or None if optional value is not set.
+template <typename T>
+std::string to_string(const arb::util::optional<T>& o, std::string unit) {
+    if (!o) return "None";
+
+    std::stringstream s;
+    s << *o << " " << unit;
+    return s.str();
+}
+
+std::string schedule_regular_string(const regular_schedule_shim& r) {
+    std::stringstream s;
+    s << "<regular_schedule: "
+      << "tstart " << to_string(r.tstart, "ms") << ", "
+      << "dt " << r.dt << " ms, "
+      << "tstop " << to_string(r.tstop, "ms") << ">";
+    return s.str();
+};
+
+std::string schedule_explicit_string(const explicit_schedule_shim& e) {
+    std::stringstream s;
+    s << "<explicit_schedule: times [";
+    bool first = true;
+    for (auto t: e.times) {
+        if (!first) {
+            s << " ";
+        }
+        s << t;
+        first = false;
+    }
+    s << "] ms>";
+    return s.str();
+};
+
+std::string schedule_poisson_string(const poisson_schedule_shim& p) {
+    std::stringstream s;
+    s << "<poisson_schedule: tstart " << p.tstart << " ms"
+      << ", freq " << p.freq << " Hz"
+      << ", seed " << p.seed << ">";
+    return s.str();
+};
+*/
diff --git a/python/schedule.hpp b/python/schedule.hpp
new file mode 100644
index 00000000..9f43ccc1
--- /dev/null
+++ b/python/schedule.hpp
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <arbor/schedule.hpp>
+#include <arbor/common_types.hpp>
+#include <arbor/util/optional.hpp>
+
+#include <pybind11/pybind11.h>
+
+namespace pyarb {
+
+// A Python shim that holds the information that describes an
+// arb::regular_schedule. This is wrapped in pybind11, and users constructing
+// a regular_schedule in python are manipulating this type. This is converted to
+// an arb::regular_schedule when a C++ recipe is created from a Python recipe.
+struct regular_schedule_shim {
+    using time_type = arb::time_type;
+    using opt_time_type = arb::util::optional<time_type>;
+
+    opt_time_type tstart = {};
+    opt_time_type tstop = {};
+    time_type dt = 0;
+
+    regular_schedule_shim() = default;
+
+    regular_schedule_shim(pybind11::object t0, time_type deltat, pybind11::object t1);
+
+    // getter and setter (in order to assert when being set)
+    void set_tstart(pybind11::object t);
+    void set_tstop(pybind11::object t);
+    void set_dt(time_type delta_t);
+
+    opt_time_type get_tstart() const;
+    opt_time_type get_dt()     const;
+    opt_time_type get_tstop()  const;
+
+    arb::schedule schedule() const;
+};
+
+// A Python shim for arb::explicit_schedule.
+// This is wrapped in pybind11, and users constructing an explicit_schedule in
+// Python are manipulating this type. This is converted to an
+// arb::explicit_schedule when a C++ recipe is created from a Python recipe.
+struct explicit_schedule_shim {
+    std::vector<arb::time_type> times;
+
+    explicit_schedule_shim() = default;
+    explicit_schedule_shim(std::vector<arb::time_type> t);
+
+    // getter and setter (in order to assert when being set)
+    void set_times(std::vector<arb::time_type> t);
+    std::vector<arb::time_type> get_times() const;
+
+    arb::schedule schedule() const;
+};
+
+// A Python shim for arb::poisson_schedule.
+// This is wrapped in pybind11, and users constructing a poisson_schedule in
+// Python are manipulating this type. This is converted to an
+// arb::poisson_schedule when a C++ recipe is created from a Python recipe.
+struct poisson_schedule_shim {
+    using rng_type = std::mt19937_64;
+
+    arb::time_type tstart = arb::terminal_time;
+    arb::time_type freq = 10.;  // Hz
+    rng_type::result_type seed = 0;
+
+    poisson_schedule_shim() = default;
+    poisson_schedule_shim(arb::time_type ts, arb::time_type f, rng_type::result_type s);
+
+    void set_tstart(arb::time_type t);
+    void set_freq(arb::time_type f);
+
+    arb::time_type get_tstart() const;
+    arb::time_type get_freq() const;
+
+    arb::schedule schedule() const;
+};
+
+}
-- 
GitLab