diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 4b37a93911f13ee40254bd5816c6743a257bf0d4..8065522a88ac7078518f1527c35289854626009d 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(pyarb MODULE identifiers.cpp mpi.cpp pyarb.cpp + recipe.cpp strings.cpp ) diff --git a/python/context.cpp b/python/context.cpp index 4e7dde4f49c3e3b9d2f107176f799946039fb958..04cb3ab0404434d0c1a11d1de7d7b8bdd703b5cc 100644 --- a/python/context.cpp +++ b/python/context.cpp @@ -38,11 +38,11 @@ struct proc_allocation_shim { // 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_int); }; void set_num_threads(int threads) { - pyarb::assert_throw([](int n) { return n>0; }(threads), "threads must be a positive integer."); + pyarb::assert_throw([](int n) { return n>0; }(threads), "threads must be a positive integer"); num_threads = threads; }; @@ -153,8 +153,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_int); 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/event_generator.cpp b/python/event_generator.cpp index f31bbe15b0591f8b75e55e80291776a472d20680..9344a3fd1a9b8df90e2d2bf0eb3a5ec58c660181 100644 --- a/python/event_generator.cpp +++ b/python/event_generator.cpp @@ -3,7 +3,6 @@ #include <string> #include <arbor/common_types.hpp> -#include <arbor/event_generator.hpp> #include <arbor/schedule.hpp> #include <pybind11/pybind11.h> @@ -12,6 +11,7 @@ #include "conversion.hpp" #include "error.hpp" +#include "event_generator.hpp" namespace pyarb { @@ -41,13 +41,13 @@ struct regular_schedule_shim { // 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); + 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); + 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."); + pyarb::assert_throw(is_nonneg(delta_t), "dt must be a non-negative number"); dt = delta_t; }; @@ -90,7 +90,7 @@ struct explicit_schedule_shim { // 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."); + "explicit time schedule can not contain negative values"); } }; @@ -123,12 +123,12 @@ struct poisson_schedule_shim { } void set_tstart(time_type t) { - pyarb::assert_throw(is_nonneg(t), "tstart must be a non-negative number."); + 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."); + pyarb::assert_throw(is_nonneg(f), "frequency must be a non-negative number"); freq = f; }; @@ -141,6 +141,15 @@ struct poisson_schedule_shim { } }; +template <typename Sched> +event_generator_shim make_event_generator( + arb::cell_member_type target, + double weight, + const Sched& sched) +{ + 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> @@ -184,27 +193,6 @@ std::string schedule_poisson_string(const poisson_schedule_shim& p) { return s.str(); }; -struct event_generator_shim { - arb::cell_member_type target; - double weight; - arb::schedule time_sched; - - event_generator_shim(arb::cell_member_type cell, double event_weight, arb::schedule sched): - target(cell), - weight(event_weight), - time_sched(std::move(sched)) - {} -}; - -template <typename Sched> -event_generator_shim make_event_generator( - arb::cell_member_type target, - double weight, - const Sched& sched) -{ - return event_generator_shim(target, weight, sched.schedule()); -} - void register_event_generators(pybind11::module& m) { using namespace pybind11::literals; using time_type = arb::time_type; diff --git a/python/event_generator.hpp b/python/event_generator.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ed41c10ee4a349817180c48288ec934ea166fc10 --- /dev/null +++ b/python/event_generator.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include <arbor/common_types.hpp> +#include <arbor/schedule.hpp> + +#include <pybind11/pybind11.h> + +namespace pyarb { + +struct event_generator_shim { + arb::cell_member_type target; + double weight; + arb::schedule time_sched; + + event_generator_shim(arb::cell_member_type cell, double event_weight, arb::schedule sched): + target(cell), + weight(event_weight), + time_sched(std::move(sched)) + {} +}; + +} // namespace pyarb diff --git a/python/identifiers.cpp b/python/identifiers.cpp index cb6159a2c750fa15f8e1d91f19b733c6a99f3217..3aae5a61c8790d798a825c97e804e1139dc23f96 100644 --- a/python/identifiers.cpp +++ b/python/identifiers.cpp @@ -38,6 +38,17 @@ void register_identifiers(pybind11::module& m) { "Cell-local index of the item.") .def("__str__", &cell_member_string) .def("__repr__", &cell_member_string); + + pybind11::enum_<arb::cell_kind>(m, "cell_kind", + "Enumeration used to identify the cell kind, used by the model to group equal kinds in the same cell group.") + .value("benchmark", arb::cell_kind::benchmark, + "Proxy cell used for benchmarking.") + .value("cable", arb::cell_kind::cable, + "A cell with morphology described by branching 1D cable segments.") + .value("lif", arb::cell_kind::lif, + "Leaky-integrate and fire neuron.") + .value("spike_source", arb::cell_kind::spike_source, + "Proxy cell that generates spikes from a spike sequence provided by the user."); } } // namespace pyarb diff --git a/python/mpi.cpp b/python/mpi.cpp index 16a2b36260336b5060ebb0dbd43a5977ca9cc651..15f1b5df9d582ed756f155b3dc0f0d8aa06a2762 100644 --- a/python/mpi.cpp +++ b/python/mpi.cpp @@ -32,7 +32,7 @@ MPI_Comm convert_to_mpi_comm(pybind11::object o) { return *PyMPIComm_Get(o.ptr()); } #endif - throw arb::mpi_error(MPI_ERR_OTHER, "Unable to convert to an MPI Communicatior."); + throw arb::mpi_error(MPI_ERR_OTHER, "Unable to convert to an MPI Communicatior"); } mpi_comm_shim::mpi_comm_shim(pybind11::object o) { diff --git a/python/pyarb.cpp b/python/pyarb.cpp index e085141ea0157b0fd157a1d2033b1ac22daac8cc..c964caa4ea346627dff6e804dc974eefb0834f90 100644 --- a/python/pyarb.cpp +++ b/python/pyarb.cpp @@ -13,6 +13,7 @@ void register_identifiers(pybind11::module& m); #ifdef ARB_MPI_ENABLED void register_mpi(pybind11::module& m); #endif +void register_recipe(pybind11::module& m); } PYBIND11_MODULE(arbor, m) { @@ -26,4 +27,5 @@ PYBIND11_MODULE(arbor, m) { #ifdef ARB_MPI_ENABLED pyarb::register_mpi(m); #endif + pyarb::register_recipe(m); } diff --git a/python/recipe.cpp b/python/recipe.cpp new file mode 100644 index 0000000000000000000000000000000000000000..93d1e2bfb00ccbf1b8ef7594e503b24d0943f6e9 --- /dev/null +++ b/python/recipe.cpp @@ -0,0 +1,232 @@ +#include <sstream> +#include <string> +#include <vector> + +#include <pybind11/pybind11.h> +#include <pybind11/pytypes.h> +#include <pybind11/stl.h> + +#include <arbor/benchmark_cell.hpp> +#include <arbor/cable_cell.hpp> +#include <arbor/event_generator.hpp> +#include <arbor/lif_cell.hpp> +#include <arbor/recipe.hpp> +#include <arbor/spike_source_cell.hpp> + +#include "error.hpp" +#include "event_generator.hpp" +#include "recipe.hpp" + +namespace pyarb { + +// ========================================= Unwrap ========================================= +// The py::recipe::cell_decription returns a pybind11::object, that is +// unwrapped and copied into a arb::util::unique_any. + +arb::util::unique_any py_recipe_shim::get_cell_description(arb::cell_gid_type gid) const { + using pybind11::isinstance; + using pybind11::cast; + + // Aquire the GIL because it must be held when calling isinstance and cast. + auto guard = pybind11::gil_scoped_acquire(); + + // Get the python object pyarb::cell_description from the python front end + pybind11::object o = impl_->cell_description(gid); + + if (isinstance<arb::cable_cell>(o)) { + return arb::util::unique_any(cast<arb::cable_cell>(o)); + } + + else if (isinstance<arb::lif_cell>(o)) { + return arb::util::unique_any(cast<arb::lif_cell>(o)); + } + + else if (isinstance<arb::spike_source_cell>(o)) { + return arb::util::unique_any(cast<arb::spike_source_cell>(o)); + } + + else if (isinstance<arb::benchmark_cell>(o)) { + return arb::util::unique_any(cast<arb::benchmark_cell>(o)); + } + + throw pyarb_error( + "recipe.cell_description returned \"" + + std::string(pybind11::str(o)) + + "\" which does not describe a known Arbor cell type"); +} + +// 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. + auto guard = pybind11::gil_scoped_acquire(); + + // Get the python object pyarb::global_properties from the python front end + pybind11::object o = impl_->global_properties(kind); + + if (kind == arb::cell_kind::cable) { + return arb::util::any(cast<arb::cable_cell_global_properties>(o)); + } + + 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"); + +} + +std::vector<arb::event_generator> py_recipe_shim::event_generators(arb::cell_gid_type gid) const { + using namespace std::string_literals; + using pybind11::isinstance; + using pybind11::cast; + + // Aquire the GIL because it must be held when calling isinstance and cast. + auto guard = pybind11::gil_scoped_acquire(); + + // Get the python list of pyarb::event_generator_shim from the python front end. + auto pygens = impl_->event_generators(gid); + + std::vector<arb::event_generator> gens; + gens.reserve(pygens.size()); + + for (auto& g: pygens) { + // check that a valid Python event_generator was passed. + if (!isinstance<pyarb::event_generator_shim>(g)) { + std::stringstream s; + s << "recipe supplied an invalid event generator for gid " + << gid << ": " << pybind11::str(g); + throw pyarb_error(s.str()); + } + // get a reference to the python event_generator + auto& p = cast<const pyarb::event_generator_shim&>(g); + + // convert the event_generator to an arb::event_generator + gens.push_back(arb::schedule_generator({gid, p.target.index}, p.weight, std::move(p.time_sched))); + } + + return gens; +} + +// TODO: implement py_recipe_shim::probe_info + +std::string connection_string(const arb::cell_connection& c) { + std::stringstream s; + s << "<connection: (" << c.source.gid << "," << c.source.index << ")" + << " -> (" << c.dest.gid << "," << c.dest.index << ")" + << " , delay " << c.delay << ", weight " << c.weight << ">"; + return s.str(); +} + +std::string gap_junction_connection_string(const arb::gap_junction_connection& gc) { + std::stringstream s; + s << "<connection: (" << gc.local.gid << "," << gc.local.index << ")" + << " -> (" << gc.peer.gid << "," << gc.peer.index << ")" + << " , conductance " << gc.ggap << ">"; + return s.str(); +} + +void register_recipe(pybind11::module& m) { + using namespace pybind11::literals; + + // Connections + pybind11::class_<arb::cell_connection> cell_connection(m, "cell_connection", + "Describes a connection between two cells:\n" + "a pre-synaptic source and a post-synaptic destination."); + cell_connection + .def(pybind11::init<>( + [](){return arb::cell_connection({0u,0u}, {0u,0u}, 0.f, 0.f);}), + "Construct a connection with default arguments:\n" + " source: gid 0, index 0.\n" + " destination: gid 0, index 0.\n" + " weight: 0.\n" + " delay: 0 ms.\n") + .def(pybind11::init<arb::cell_member_type, arb::cell_member_type, float, float>(), + "source"_a, "destination"_a, "weight"_a = 0.f, "delay"_a = 0.f, + "Construct a connection with arguments:\n" + " source: The source end point of the connection.\n" + " destination: The destination end point of the connection.\n" + " weight: The weight delivered to the target synapse (dimensionless with interpretation specific to synapse type of target, default 0.).\n" + " delay: The delay of the connection (unit: ms, default 0.).\n") + .def_readwrite("source", &arb::cell_connection::source, + "The source of the connection.") + .def_readwrite("destination", &arb::cell_connection::dest, + "The destination of the connection.") + .def_readwrite("weight", &arb::cell_connection::weight, + "The weight of the connection (unit: Sâ‹…cmâ»Â²).") + .def_readwrite("delay", &arb::cell_connection::delay, + "The delay time of the connection (unit: ms).") + .def("__str__", &connection_string) + .def("__repr__", &connection_string); + + // Gap Junction Connections + pybind11::class_<arb::gap_junction_connection> gap_junction_connection(m, "gap_junction_connection", + "Describes a gap junction between two gap junction sites."); + gap_junction_connection + .def(pybind11::init<>( + [](){return arb::gap_junction_connection({0u,0u}, {0u,0u}, 0.f);}), + "Construct a gap junction connection with default arguments:\n" + " local: gid 0, index 0.\n" + " peer: gid 0, index 0.\n" + " ggap: 0 μS.\n") + .def(pybind11::init<arb::cell_member_type, arb::cell_member_type, double>(), + "local"_a, "peer"_a, "ggap"_a = 0.f, + "Construct a gap junction connection with arguments:\n" + " local: One half of the gap junction connection.\n" + " peer: Other half of the gap junction connection.\n" + " ggap: Gap junction conductance (unit: μS, default 0.).\n") + .def_readwrite("local", &arb::gap_junction_connection::local, + "One half of the gap junction connection.") + .def_readwrite("peer", &arb::gap_junction_connection::peer, + "Other half of the gap junction connection.") + .def_readwrite("ggap", &arb::gap_junction_connection::ggap, + "Gap junction conductance (unit: μS).") + .def("__str__", &gap_junction_connection_string) + .def("__repr__", &gap_junction_connection_string); + + // Recipes + pybind11::class_<py_recipe, + py_recipe_trampoline, + std::shared_ptr<py_recipe>> + recipe(m, "recipe", pybind11::dynamic_attr(), + "A description of a model, describing the cells and the network via a cell-centric interface."); + recipe + .def(pybind11::init<>()) + .def("num_cells", &py_recipe::num_cells, "The number of cells in the model.") + .def("cell_description", &py_recipe::cell_description, pybind11::return_value_policy::copy, + "gid"_a, + "High level description of the cell with global identifier gid.") + .def("cell_kind", &py_recipe::cell_kind, + "gid"_a, + "The kind of cell with global identifier gid.") + .def("num_sources", &py_recipe::num_sources, + "gid"_a, + "The number of spike sources on gid (default 0).") + .def("num_targets", &py_recipe::num_targets, + "gid"_a, + "The number of post-synaptic sites on gid (default 0).") + // TODO: py_recipe::num_probes + .def("num_gap_junction_sites", &py_recipe::num_gap_junction_sites, + "gid"_a, + "The number of gap junction sites on gid (default 0).") + .def("event_generators", &py_recipe::event_generators, + "gid"_a, + "A list of all the event generators that are attached to gid (default []).") + .def("connections_on", &py_recipe::connections_on, + "gid"_a, + "A list of all the incoming connections to gid (default []).") + .def("gap_junctions_on", &py_recipe::gap_junctions_on, + "gid"_a, + "A list of the gap junctions connected to gid (default []).") + // TODO: py_recipe::get_probe + .def("global_properties", &py_recipe::global_properties, pybind11::return_value_policy::copy, + "cell_kind"_a, + "Global property type specific to a given cell kind.") + .def("__str__", [](const py_recipe&){return "<pyarb.recipe>";}) + .def("__repr__", [](const py_recipe&){return "<pyarb.recipe>";}); +} +} // namespace pyarb diff --git a/python/recipe.hpp b/python/recipe.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4e83c276d37eedb497be973871c0b1d25cb3c168 --- /dev/null +++ b/python/recipe.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include <vector> + +#include <pybind11/pybind11.h> +#include <pybind11/pytypes.h> +#include <pybind11/stl.h> + +#include <arbor/event_generator.hpp> +#include <arbor/recipe.hpp> + +namespace pyarb { + +// pyarb::recipe is the recipe interface used by Python. +// Calls that return generic types return pybind11::object, to avoid +// having to wrap some C++ types used by the C++ interface (specifically +// util::unique_any, util::any, std::unique_ptr, etc.) +// For example, requests for cell description return pybind11::object, instead +// of util::unique_any used by the C++ recipe interface. +// The py_recipe_shim unwraps the python objects, and forwards them +// to the C++ back end. + +class py_recipe { +public: + py_recipe() = default; + virtual ~py_recipe() {} + + virtual arb::cell_size_type num_cells() const = 0; + + virtual pybind11::object cell_description(arb::cell_gid_type gid) const = 0; + virtual arb::cell_kind cell_kind(arb::cell_gid_type gid) const = 0; + + virtual arb::cell_size_type num_sources(arb::cell_gid_type) const { return 0; } + virtual arb::cell_size_type num_targets(arb::cell_gid_type) const { return 0; } + + //TODO: virtual arb::cell_size_type num_probes(arb::cell_gid_type) const { return 0; } + + virtual arb::cell_size_type num_gap_junction_sites(arb::cell_gid_type gid) const { + return gap_junctions_on(gid).size(); + } + + virtual std::vector<pybind11::object> event_generators(arb::cell_gid_type gid) const { + auto guard = pybind11::gil_scoped_acquire(); + return {}; + } + + virtual std::vector<arb::cell_connection> connections_on(arb::cell_gid_type gid) const { return {}; } + virtual std::vector<arb::gap_junction_connection> gap_junctions_on(arb::cell_gid_type) const { return {}; } + + //TODO: virtual pybind11::object get_probe (arb::cell_member_type id) const {...} + + virtual pybind11::object global_properties(arb::cell_kind kind) const = 0; +}; + +class py_recipe_trampoline: public py_recipe { +public: + arb::cell_size_type num_cells() const override { + PYBIND11_OVERLOAD_PURE(arb::cell_size_type, py_recipe, num_cells); + } + + pybind11::object cell_description(arb::cell_gid_type gid) const override { + PYBIND11_OVERLOAD_PURE(pybind11::object, py_recipe, cell_description, gid); + } + + arb::cell_kind cell_kind(arb::cell_gid_type gid) const override { + PYBIND11_OVERLOAD_PURE(arb::cell_kind, py_recipe, cell_kind, gid); + } + + arb::cell_size_type num_sources(arb::cell_gid_type gid) const override { + PYBIND11_OVERLOAD(arb::cell_size_type, py_recipe, num_sources, gid); + } + + arb::cell_size_type num_targets(arb::cell_gid_type gid) const override { + PYBIND11_OVERLOAD(arb::cell_size_type, py_recipe, num_targets, gid); + } + + //TODO: arb::cell_size_type num_probes(arb::cell_gid_type) + + arb::cell_size_type num_gap_junction_sites(arb::cell_gid_type gid) const override { + PYBIND11_OVERLOAD(arb::cell_size_type, py_recipe, num_gap_junction_sites, gid); + } + + std::vector<pybind11::object> event_generators(arb::cell_gid_type gid) const override { + PYBIND11_OVERLOAD(std::vector<pybind11::object>, py_recipe, event_generators, gid); + } + + std::vector<arb::cell_connection> connections_on(arb::cell_gid_type gid) const override { + PYBIND11_OVERLOAD(std::vector<arb::cell_connection>, py_recipe, connections_on, gid); + } + + std::vector<arb::gap_junction_connection> gap_junctions_on(arb::cell_gid_type gid) const override { + PYBIND11_OVERLOAD(std::vector<arb::gap_junction_connection>, py_recipe, gap_junctions_on, gid); + } + + //TODO: pybind11::object get_probe(arb::cell_member_type id) + + pybind11::object global_properties(arb::cell_kind kind) const override { + PYBIND11_OVERLOAD_PURE(pybind11::object, py_recipe, global_properties, kind); + } +}; + +// A recipe shim that holds a pyarb::recipe implementation. +// Unwraps/translates python-side output from pyarb::recipe and forwards +// to arb::recipe. +// For example, unwrap cell descriptions stored in PyObject, and rewrap +// in util::unique_any. + +class py_recipe_shim: public arb::recipe { + // pointer to the python recipe implementation + std::shared_ptr<py_recipe> impl_; + +public: + using recipe::recipe; + + py_recipe_shim(std::shared_ptr<py_recipe> r): impl_(std::move(r)) {} + + arb::cell_size_type num_cells() const override { + return impl_->num_cells(); + } + + // The pyarb::recipe::cell_decription returns a pybind11::object, that is + // unwrapped and copied into a util::unique_any. + arb::util::unique_any get_cell_description(arb::cell_gid_type gid) const override; + + arb::cell_kind get_cell_kind(arb::cell_gid_type gid) const override { + return impl_->cell_kind(gid); + } + + arb::cell_size_type num_sources(arb::cell_gid_type gid) const override { + return impl_->num_sources(gid); + } + + arb::cell_size_type num_targets(arb::cell_gid_type gid) const override { + return impl_->num_targets(gid); + } + +/* //TODO: arb::cell_size_type num_probes(arb::cell_gid_type gid) const override { + return impl_->num_probes(gid); + } +*/ + arb::cell_size_type num_gap_junction_sites(arb::cell_gid_type gid) const override { + return impl_->num_gap_junction_sites(gid); + } + + std::vector<arb::event_generator> event_generators(arb::cell_gid_type gid) const override; + + std::vector<arb::cell_connection> connections_on(arb::cell_gid_type gid) const override { + return impl_->connections_on(gid); + } + + std::vector<arb::gap_junction_connection> gap_junctions_on(arb::cell_gid_type gid) const override { + return impl_->gap_junctions_on(gid); + } + + //TODO: arb::probe_info get_probe(arb::cell_member_type id) const override + + // The pyarb::recipe::global_properties returns a pybind11::object, that is + // unwrapped and copied into a util::any. + arb::util::any get_global_properties(arb::cell_kind kind) const override; + +}; + +} // namespace pyarb diff --git a/python/test/unit/test_contexts.py b/python/test/unit/test_contexts.py index e007207f680b2fdf56e122a5442d128f7b374b0b..a445bb153936b02a887c462995f5087356bcd9a6 100644 --- a/python/test/unit/test_contexts.py +++ b/python/test/unit/test_contexts.py @@ -42,18 +42,18 @@ class Contexts(unittest.TestCase): def test_exceptions_allocation(self): with self.assertRaisesRegex(RuntimeError, - "gpu id must be None, or a non-negative integer."): + "gpu_id must be None, or a non-negative integer"): arb.proc_allocation(gpu_id = 1.) with self.assertRaisesRegex(RuntimeError, - "gpu id must be None, or a non-negative integer."): + "gpu_id must be None, or a non-negative integer"): arb.proc_allocation(gpu_id = -1) with self.assertRaisesRegex(RuntimeError, - "gpu id must be None, or a non-negative integer."): + "gpu_id must be None, or a non-negative integer"): arb.proc_allocation(gpu_id = 'gpu_id') with self.assertRaises(TypeError): arb.proc_allocation(threads = 1.) with self.assertRaisesRegex(RuntimeError, - "threads must be a positive integer."): + "threads must be a positive integer"): arb.proc_allocation(threads = 0) with self.assertRaises(TypeError): arb.proc_allocation(threads = None) diff --git a/python/test/unit/test_event_generators.py b/python/test/unit/test_event_generators.py index 5febad17e93584c897eca8c3e47a4962795fea7e..b64119c8bcd8670cb6ea43a3fe9ab685a8e621a2 100644 --- a/python/test/unit/test_event_generators.py +++ b/python/test/unit/test_event_generators.py @@ -51,17 +51,17 @@ class RegularSchedule(unittest.TestCase): def test_exceptions_regular_schedule(self): with self.assertRaisesRegex(RuntimeError, - "tstart must a non-negative number, or None."): + "tstart must a non-negative number, or None"): arb.regular_schedule(tstart = -1.) with self.assertRaisesRegex(RuntimeError, - "dt must be a non-negative number."): + "dt must be a non-negative number"): arb.regular_schedule(dt = -0.1) with self.assertRaises(TypeError): arb.regular_schedule(dt = None) with self.assertRaises(TypeError): arb.regular_schedule(dt = 'dt') with self.assertRaisesRegex(RuntimeError, - "tstop must a non-negative number, or None."): + "tstop must a non-negative number, or None"): arb.regular_schedule(tstop = 'tstop') class ExplicitSchedule(unittest.TestCase): @@ -86,7 +86,7 @@ class ExplicitSchedule(unittest.TestCase): def test_exceptions_explicit_schedule(self): with self.assertRaisesRegex(RuntimeError, - "explicit time schedule can not contain negative values."): + "explicit time schedule can not contain negative values"): arb.explicit_schedule([-1]) with self.assertRaises(TypeError): arb.explicit_schedule(['times']) @@ -128,14 +128,14 @@ class PoissonSchedule(unittest.TestCase): def test_exceptions_poisson_schedule(self): with self.assertRaisesRegex(RuntimeError, - "tstart must be a non-negative number."): + "tstart must be a non-negative number"): arb.poisson_schedule(tstart = -10.) with self.assertRaises(TypeError): arb.poisson_schedule(tstart = None) with self.assertRaises(TypeError): arb.poisson_schedule(tstart = 'tstart') with self.assertRaisesRegex(RuntimeError, - "frequency must be a non-negative number."): + "frequency must be a non-negative number"): arb.poisson_schedule(freq = -100.) with self.assertRaises(TypeError): arb.poisson_schedule(freq = 'freq')