diff --git a/.travis.yml b/.travis.yml index 252dcbf5fdf40d7dd1d290ffe0735af593a62141..9f9d3effab76d1da68a2cfc5e0100016c4949ab4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -156,6 +156,7 @@ install: python$PY get-pip.py pip --version fi + - if [[ ( "$WITH_PYTHON" == "true" ) && ( "$TRAVIS_OS_NAME" == "osx" ) ]]; then pip$PY install numpy; fi - | if [[ "$WITH_DISTRIBUTED" == "mpi" ]]; then if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 7e5b02f3e4e8d14d6864761022b920bc32706faa..4b37a93911f13ee40254bd5816c6743a257bf0d4 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -18,7 +18,8 @@ add_subdirectory(pybind11) add_library(pyarb MODULE config.cpp context.cpp - exception.cpp + event_generator.cpp + identifiers.cpp mpi.cpp pyarb.cpp strings.cpp diff --git a/python/config.cpp b/python/config.cpp index 5b1aa2eb19dfcba36a1413fdaf36ab411dfb63b6..29636c8d525012e89f8eedb70e0ebf5a4d52f5cc 100644 --- a/python/config.cpp +++ b/python/config.cpp @@ -1,8 +1,8 @@ -#include <arbor/version.hpp> - -#include <sstream> #include <iomanip> #include <ios> +#include <sstream> + +#include <arbor/version.hpp> #include <pybind11/pybind11.h> #include <pybind11/stl.h> diff --git a/python/context.cpp b/python/context.cpp index b3ddb8c8ba63f9003b3b4b00f26078a2b2163988..61492e59baf49484ac0269961a5e2cfcd35d9e75 100644 --- a/python/context.cpp +++ b/python/context.cpp @@ -1,16 +1,15 @@ #include <iostream> - #include <sstream> #include <string> #include <arbor/context.hpp> #include <arbor/version.hpp> +#include <pybind11/pybind11.h> + #include "context.hpp" #include "strings.hpp" -#include <pybind11/pybind11.h> - #ifdef ARB_MPI_ENABLED #include "mpi.hpp" #endif diff --git a/python/conversion.hpp b/python/conversion.hpp new file mode 100644 index 0000000000000000000000000000000000000000..811eb74eaf70c70fe73d305e622a17e758bdc9a9 --- /dev/null +++ b/python/conversion.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include <pybind11/pybind11.h> +#include <pybind11/pytypes.h> + +#include "error.hpp" + +namespace pyarb { + +// 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. +template <typename T, typename F> +arb::util::optional<T> py2optional(pybind11::object o, const char* msg, F&& pred) { + bool ok = true; + T value; + + if (!o.is_none()) { + try { + value = o.cast<T>(); + ok = pred(value); + } + catch (...) { + ok = false; + } + } + + if (!ok) { + throw pyarb_error(msg); + } + + return o.is_none()? arb::util::nullopt: arb::util::optional<T>(std::move(value)); +} + +template <typename T> +arb::util::optional<T> py2optional(pybind11::object o, const char* msg) { + T value; + + if (!o.is_none()) { + try { + value = o.cast<T>(); + } + catch (...) { + throw pyarb_error(msg); + } + } + + return o.is_none()? arb::util::nullopt: arb::util::optional<T>(std::move(value)); +} + +} diff --git a/python/error.hpp b/python/error.hpp new file mode 100644 index 0000000000000000000000000000000000000000..fbcd5ba7216094cdc08aacae17ac5cd25396deab --- /dev/null +++ b/python/error.hpp @@ -0,0 +1,32 @@ +#pragma once + +#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 + +struct pyarb_error: std::runtime_error { + pyarb_error(const std::string& what_msg): + std::runtime_error(what_msg) {} +}; + +static +void assert_throw(bool pred, const char* msg) { + if (!pred) throw pyarb_error(msg); +} + +} // namespace pyarb diff --git a/python/event_generator.cpp b/python/event_generator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f31bbe15b0591f8b75e55e80291776a472d20680 --- /dev/null +++ b/python/event_generator.cpp @@ -0,0 +1,304 @@ +#include <stdexcept> +#include <sstream> +#include <string> + +#include <arbor/common_types.hpp> +#include <arbor/event_generator.hpp> +#include <arbor/schedule.hpp> + +#include <pybind11/pybind11.h> +#include <pybind11/pytypes.h> +#include <pybind11/stl.h> + +#include "conversion.hpp" +#include "error.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)); + } +}; + +// 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(); +}; + +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; + + // 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", ®ular_schedule_shim::get_tstart, ®ular_schedule_shim::set_tstart, + "The delivery time of the first event in the sequence (in ms).") + .def_property("tstop", ®ular_schedule_shim::get_tstop, ®ular_schedule_shim::set_tstop, + "No events delivered after this time (in ms).") + .def_property("dt", ®ular_schedule_shim::get_dt, ®ular_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 + .def(pybind11::init<>( + [](arb::cell_member_type target, double weight, const regular_schedule_shim& sched){ + return make_event_generator(target, weight, sched);}), + "target"_a, "weight"_a, "sched"_a, + "Construct an event generator with arguments:\n" + " target: The target synapse (gid, local_id).\n" + " weight: The weight of events to deliver.\n" + " sched: A regular schedule of the events.") + .def(pybind11::init<>( + [](arb::cell_member_type target, double weight, const explicit_schedule_shim& sched){ + return make_event_generator(target, weight, sched);}), + "target"_a, "weight"_a, "sched"_a, + "Construct an event generator with arguments:\n" + " target: The target synapse (gid, local_id).\n" + " weight: The weight of events to deliver.\n" + " sched: An explicit schedule of the events.") + .def(pybind11::init<>( + [](arb::cell_member_type target, double weight, const poisson_schedule_shim& sched){ + return make_event_generator(target, weight, sched);}), + "target"_a, "weight"_a, "sched"_a, + "Construct an event generator with arguments:\n" + " target: The target synapse (gid, local_id).\n" + " weight: The weight of events to deliver.\n" + " sched: A poisson schedule of the events.") + .def_readwrite("target", &event_generator_shim::target, + "The target synapse (gid, local_id).") + .def_readwrite("weight", &event_generator_shim::weight, + "The weight of events to deliver.") + .def("__str__", [](const event_generator_shim&){return "<arbor.event_generator>";}) + .def("__repr__", [](const event_generator_shim&){return "<arbor.event_generator>";}); +} + +} // namespace pyarb diff --git a/python/exception.cpp b/python/exception.cpp deleted file mode 100644 index 3be0e787a3ee78294aedd7e90242e500f413c8c0..0000000000000000000000000000000000000000 --- a/python/exception.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include <string> - -#include "exception.hpp" - -namespace pyarb { - -python_error::python_error(const std::string& message): - arbor_exception("arbor python wrapper error: " + message + "\n") -{} - -} // namespace pyarb diff --git a/python/exception.hpp b/python/exception.hpp deleted file mode 100644 index 5f352154b8597ecbb4e8e8a245a76891ee4928da..0000000000000000000000000000000000000000 --- a/python/exception.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include <string> - -#include <arbor/arbexcept.hpp> - -namespace pyarb { - -using arb::arbor_exception; - -// Python wrapper errors - -struct python_error: arbor_exception { - explicit python_error(const std::string& message); -}; - -} // namespace pyarb diff --git a/python/identifiers.cpp b/python/identifiers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cb6159a2c750fa15f8e1d91f19b733c6a99f3217 --- /dev/null +++ b/python/identifiers.cpp @@ -0,0 +1,43 @@ +#include <string> + +#include <arbor/common_types.hpp> + +#include <pybind11/pybind11.h> + +#include "strings.hpp" + +namespace pyarb { + +void register_identifiers(pybind11::module& m) { + using namespace pybind11::literals; + + pybind11::class_<arb::cell_member_type> cell_member(m, "cell_member", + "For global identification of a cell-local item.\n\n" + "Items of cell_member must:\n" + "(1) be associated with a unique cell, identified by the member gid;\n" + "(2) identify an item within a cell-local collection by the member index.\n"); + + cell_member + .def(pybind11::init<>(), + "Construct a cell member with default values gid = 0 and index = 0.") + .def(pybind11::init( + [](arb::cell_gid_type gid, arb::cell_lid_type idx) { + arb::cell_member_type m; + m.gid = gid; + m.index = idx; + return m; + }), + "gid"_a, + "index"_a, + "Construct a cell member with arguments:/n" + " gid: The global identifier of the cell.\n" + " index: The cell-local index of the item.\n") + .def_readwrite("gid", &arb::cell_member_type::gid, + "The global identifier of the cell.") + .def_readwrite("index", &arb::cell_member_type::index, + "Cell-local index of the item.") + .def("__str__", &cell_member_string) + .def("__repr__", &cell_member_string); +} + +} // namespace pyarb diff --git a/python/mpi.cpp b/python/mpi.cpp index b6c14c35d5832a5eebb0b0a5577629303927a9bc..0fd0dbadde66b9544288f211a6b5131f8227d9a5 100644 --- a/python/mpi.cpp +++ b/python/mpi.cpp @@ -6,10 +6,10 @@ #include <pybind11/pybind11.h> #ifdef ARB_MPI_ENABLED -#include <arbor/communication/mpi_error.hpp> - #include <mpi.h> +#include <arbor/communication/mpi_error.hpp> + #include "mpi.hpp" #ifdef ARB_WITH_MPI4PY diff --git a/python/pyarb.cpp b/python/pyarb.cpp index 1b9d11b5f337e4195e38303450be4a29e927cb0e..e085141ea0157b0fd157a1d2033b1ac22daac8cc 100644 --- a/python/pyarb.cpp +++ b/python/pyarb.cpp @@ -8,6 +8,8 @@ namespace pyarb { 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); #ifdef ARB_MPI_ENABLED void register_mpi(pybind11::module& m); #endif @@ -19,6 +21,8 @@ PYBIND11_MODULE(arbor, m) { pyarb::register_config(m); pyarb::register_contexts(m); + pyarb::register_event_generators(m); + pyarb::register_identifiers(m); #ifdef ARB_MPI_ENABLED pyarb::register_mpi(m); #endif diff --git a/python/strings.cpp b/python/strings.cpp index cf0e2c100ab277d09072489d0525f189a713745e..1788766602d6b511f31ed2f08f529759eb7c78da 100644 --- a/python/strings.cpp +++ b/python/strings.cpp @@ -1,12 +1,20 @@ -#include <string> #include <sstream> +#include <string> +#include <arbor/common_types.hpp> #include <arbor/context.hpp> #include "strings.hpp" namespace pyarb { +std::string cell_member_string(const arb::cell_member_type& m) { + std::stringstream s; + s << "<cell_member: gid " << m.gid + << ", index " << m.index << ">"; + return s.str(); +} + std::string context_string(const arb::context& c) { std::stringstream s; const bool gpu = arb::has_gpu(c); diff --git a/python/strings.hpp b/python/strings.hpp index c0e9bc042bd1e22c1fbb0aae8b5bf5e56c27ff59..dba70801706d456f680758edb90b31383fb54b1d 100644 --- a/python/strings.hpp +++ b/python/strings.hpp @@ -1,15 +1,14 @@ #pragma once -/* - * Utilities for generating string representations of types. - */ - #include <string> +#include <arbor/common_types.hpp> #include <arbor/context.hpp> +// Utilities for generating string representations of types. namespace pyarb { +std::string cell_member_string(const arb::cell_member_type&); std::string context_string(const arb::context&); std::string proc_allocation_string(const arb::proc_allocation&); diff --git a/python/test/unit/runner.py b/python/test/unit/runner.py index 56cd8a67c99ee4b12bc3625a2e27b137417db420..1ea70555e0505cfbbbab34e692fb6eae42176890 100644 --- a/python/test/unit/runner.py +++ b/python/test/unit/runner.py @@ -11,14 +11,20 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../. try: import options import test_contexts + import test_identifiers + import test_event_generators # add more if needed except ModuleNotFoundError: from test import options from test.unit import test_contexts + from test.unit import test_identifiers + from test.unit import test_event_generators # add more if needed test_modules = [\ - test_contexts\ + test_contexts,\ + test_identifiers,\ + test_event_generators\ ] # add more if needed def suite(): diff --git a/python/test/unit/test_contexts.py b/python/test/unit/test_contexts.py index 5e2389e55236c2b437a661dfa235fadef2077268..6ec70a62acd6ed2a446e63e47db6e2de4388bd4b 100644 --- a/python/test/unit/test_contexts.py +++ b/python/test/unit/test_contexts.py @@ -20,7 +20,7 @@ all tests for non-distributed arb.context """ class Contexts(unittest.TestCase): - def test_default(self): + def test_default_context(self): ctx = arb.context() def test_resources(self): diff --git a/python/test/unit/test_event_generators.py b/python/test/unit/test_event_generators.py new file mode 100644 index 0000000000000000000000000000000000000000..9c7ef47200c0e3e62a3270c59340eb7e1eef1258 --- /dev/null +++ b/python/test/unit/test_event_generators.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# +# test_event_generators.py + +import unittest +import numpy as np + +import arbor as arb + +# to be able to run .py file from child directory +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) + +try: + import options +except ModuleNotFoundError: + from test import options + +""" +all tests for event generators (regular, explicit, poisson) +""" + +class RegularSchedule(unittest.TestCase): + def test_none_contor_regular_schedule(self): + rs = arb.regular_schedule(tstart=None, tstop=None) + + def test_tstart_dt_tstop_contor_regular_schedule(self): + rs = arb.regular_schedule(10., 1., 20.) + self.assertEqual(rs.tstart, 10.) + self.assertEqual(rs.dt, 1.) + self.assertEqual(rs.tstop, 20.) + + def test_set_tstart_dt_tstop_regular_schedule(self): + rs = arb.regular_schedule() + rs.tstart = 17. + rs.dt = 0.5 + rs.tstop = 42. + self.assertEqual(rs.tstart, 17.) + self.assertAlmostEqual(rs.dt, 0.5) + self.assertEqual(rs.tstop, 42.) + + def test_event_generator_regular_schedule(self): + cm = arb.cell_member() + cm.gid = 42 + cm.index = 3 + rs = arb.regular_schedule(2.0, 1., 100.) + rg = arb.event_generator(cm, 3.14, rs) + self.assertEqual(rg.target.gid, 42) + self.assertEqual(rg.target.index, 3) + self.assertAlmostEqual(rg.weight, 3.14) + + def test_exceptions_regular_schedule(self): + with self.assertRaisesRegex(RuntimeError, + "tstart must a non-negative number, or None."): + arb.regular_schedule(tstart=-1.) + with self.assertRaisesRegex(RuntimeError, + "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."): + arb.regular_schedule(tstop='tstop') + +class ExplicitSchedule(unittest.TestCase): + def test_times_contor_explicit_schedule(self): + es = arb.explicit_schedule([1, 2, 3, 4.5]) + self.assertEqual(es.times, [1, 2, 3, 4.5]) + + def test_set_times_explicit_schedule(self): + es = arb.explicit_schedule() + es.times = [42, 43, 44, 55.5, 100] + self.assertEqual(es.times, [42, 43, 44, 55.5, 100]) + + def test_event_generator_explicit_schedule(self): + cm = arb.cell_member() + cm.gid = 0 + cm.index = 42 + es = arb.explicit_schedule([0,1,2,3,4.4]) + eg = arb.event_generator(cm, -0.01, es) + self.assertEqual(eg.target.gid, 0) + self.assertEqual(eg.target.index, 42) + self.assertAlmostEqual(eg.weight, -0.01) + + def test_exceptions_explicit_schedule(self): + with self.assertRaisesRegex(RuntimeError, + "explicit time schedule can not contain negative values."): + arb.explicit_schedule([-1]) + with self.assertRaises(TypeError): + arb.explicit_schedule(['times']) + with self.assertRaises(TypeError): + arb.explicit_schedule([None]) + with self.assertRaises(TypeError): + arb.explicit_schedule([[1,2,3]]) + +class PoissonSchedule(unittest.TestCase): + def test_freq_seed_contor_poisson_schedule(self): + ps = arb.poisson_schedule(freq = 5., seed = 42) + self.assertEqual(ps.freq, 5.) + self.assertEqual(ps.seed, 42) + + def test_tstart_freq_seed_contor_poisson_schedule(self): + ps = arb.poisson_schedule(10., 100., 1000) + self.assertEqual(ps.tstart, 10.) + self.assertEqual(ps.freq, 100.) + self.assertEqual(ps.seed, 1000) + + def test_set_tstart_freq_seed_poisson_schedule(self): + ps = arb.poisson_schedule() + ps.tstart = 4.5 + ps.freq = 5.5 + ps.seed = 83 + self.assertAlmostEqual(ps.tstart, 4.5) + self.assertAlmostEqual(ps.freq, 5.5) + self.assertEqual(ps.seed, 83) + + def test_event_generator_poisson_schedule(self): + cm = arb.cell_member() + cm.gid = 4 + cm.index = 2 + ps = arb.poisson_schedule(0., 10., 0) + pg = arb.event_generator(cm, 42., ps) + self.assertEqual(pg.target.gid, 4) + self.assertEqual(pg.target.index, 2) + self.assertEqual(pg.weight, 42.) + + def test_exceptions_poisson_schedule(self): + with self.assertRaisesRegex(RuntimeError, + "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."): + arb.poisson_schedule(freq=-100.) + with self.assertRaises(TypeError): + arb.poisson_schedule(freq="freq") + with self.assertRaises(TypeError): + arb.poisson_schedule(seed=-1) + with self.assertRaises(TypeError): + arb.poisson_schedule(seed=10.) + with self.assertRaises(TypeError): + arb.poisson_schedule(seed="seed") + with self.assertRaises(TypeError): + arb.poisson_schedule(seed=None) + +def suite(): + # specify class and test functions in tuple (here: all tests starting with 'test' from classes RegularSchedule, ExplicitSchedule and PoissonSchedule + suite = unittest.TestSuite() + suite.addTests(unittest.makeSuite(RegularSchedule, ('test'))) + suite.addTests(unittest.makeSuite(ExplicitSchedule, ('test'))) + suite.addTests(unittest.makeSuite(PoissonSchedule, ('test'))) + return suite + +def run(): + v = options.parse_arguments().verbosity + runner = unittest.TextTestRunner(verbosity = v) + runner.run(suite()) + +if __name__ == "__main__": + run() diff --git a/python/test/unit/test_identifiers.py b/python/test/unit/test_identifiers.py new file mode 100644 index 0000000000000000000000000000000000000000..4bd41794fae19834b080aaca60d4b114a5ac0990 --- /dev/null +++ b/python/test/unit/test_identifiers.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# +# test_identifiers.py + +import unittest + +import arbor as arb + +# to be able to run .py file from child directory +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) + +try: + import options +except ModuleNotFoundError: + from test import options + +""" +all tests for identifiers, indexes, kinds +""" + +class CellMembers(unittest.TestCase): + def test_default_cell_member(self): + cm = arb.cell_member() + self.assertEqual(cm.gid, 0) + self.assertEqual(cm.index, 0) + + def test_gid_index_contor_cell_member(self): + cm = arb.cell_member(17,42) + self.assertEqual(cm.gid, 17) + self.assertEqual(cm.index, 42) + + def test_set_git_index_cell_member(self): + cm = arb.cell_member() + cm.gid = 13 + cm.index = 23 + self.assertEqual(cm.gid,13) + self.assertEqual(cm.index, 23) + +def suite(): + # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts + suite = unittest.makeSuite(CellMembers, ('test')) + return suite + +def run(): + v = options.parse_arguments().verbosity + runner = unittest.TextTestRunner(verbosity = v) + runner.run(suite()) + +if __name__ == "__main__": + run()