Skip to content
Snippets Groups Projects
Commit a6ddd515 authored by akuesters's avatar akuesters Committed by Benjamin Cumming
Browse files

Py feature recipe wo probes (#768)

Fixes #760

Wraps arbor recipe (without probes, i.e. num_probes, probe_info, get_probe) including 

- cell_connection, 
- gap_junction_connection, 
- recipe with
  - num_cells
  - cell_description
  - cell_kind
  - num_sources
  - num_targets
  - num_gap_junctions_sites
  - event_generators
  - connections_on
  - gap_junctions_on
  - global_properties
- enum cell_kind in `identifiers.cpp`
parent ab7b440b
No related branches found
No related tags found
No related merge requests found
......@@ -22,6 +22,7 @@ add_library(pyarb MODULE
identifiers.cpp
mpi.cpp
pyarb.cpp
recipe.cpp
strings.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(),
......
......@@ -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;
......
#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
......@@ -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
......@@ -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) {
......
......@@ -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);
}
#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
#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
......@@ -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)
......
......@@ -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')
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment