Skip to content
Snippets Groups Projects
Unverified Commit 28e86cb2 authored by akuesters's avatar akuesters Committed by GitHub
Browse files

Merge pull request #774 from bcumming/python-strings

clean up stringification in python wrappers
parents dfd312e3 632505b3
No related branches found
No related tags found
No related merge requests found
......@@ -24,7 +24,6 @@ add_library(pyarb MODULE
pyarb.cpp
recipe.cpp
schedule.cpp
strings.cpp
)
target_link_libraries(pyarb PRIVATE arbor pybind11::module)
......
......@@ -11,7 +11,7 @@
#include "context.hpp"
#include "conversion.hpp"
#include "error.hpp"
#include "strings.hpp"
#include "strprintf.hpp"
#ifdef ARB_MPI_ENABLED
#include "mpi.hpp"
......@@ -19,6 +19,18 @@
namespace pyarb {
std::ostream& operator<<(std::ostream& o, const context_shim& ctx) {
auto& c = ctx.context;
const bool gpu = arb::has_gpu(c);
const bool mpi = arb::has_mpi(c);
return
o << "<context: threads " << arb::num_threads(c)
<< ", gpu " << (gpu? "yes": "no")
<< ", mpi " << (mpi? "yes": "no")
<< " ranks " << arb::num_ranks(c)
<< ">";
}
// A Python shim that holds the information that describes an arb::proc_allocation.
struct proc_allocation_shim {
arb::util::optional<int> gpu_id = {};
......@@ -51,23 +63,9 @@ struct proc_allocation_shim {
}
};
// 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) {
if (!o) return "None";
std::stringstream s;
s << *o;
return s.str();
}
std::string proc_alloc_string(const proc_allocation_shim& a) {
std::stringstream s;
s << "<hardware resource allocation: threads " << a.num_threads
<< ", gpu id " << to_string(a.gpu_id);
s << ">";
return s.str();
std::ostream& operator<<(std::ostream& o, const proc_allocation_shim& alloc) {
return o << "<hardware resource allocation: threads " << alloc.num_threads
<< ", gpu id " << alloc.gpu_id << ">";
}
void register_contexts(pybind11::module& m) {
......@@ -90,8 +88,8 @@ void register_contexts(pybind11::module& m) {
"Corresponds to the integer index used to identify GPUs in CUDA API calls.")
.def_property_readonly("has_gpu", &proc_allocation_shim::has_gpu,
"Whether a GPU is being used (True/False).")
.def("__str__", &proc_alloc_string)
.def("__repr__", &proc_alloc_string);
.def("__str__", util::to_string<proc_allocation_shim>)
.def("__repr__", util::to_string<proc_allocation_shim>);
// context
pybind11::class_<context_shim> context(m, "context", "An opaque handle for the hardware resources used in a simulation.");
......@@ -166,8 +164,8 @@ void register_contexts(pybind11::module& m) {
"The number of distributed domains (equivalent to the number of MPI ranks).")
.def_property_readonly("rank", [](const context_shim& ctx){return arb::rank(ctx.context);},
"The numeric id of the local domain (equivalent to MPI rank).")
.def("__str__", [](const context_shim& c){return context_string(c.context);})
.def("__repr__", [](const context_shim& c){return context_string(c.context);});
.def("__str__", util::to_string<context_shim>)
.def("__repr__", util::to_string<context_shim>);
}
} // namespace pyarb
......@@ -5,6 +5,7 @@
#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
......
#include <ostream>
#include <string>
#include <arbor/common_types.hpp>
#include <pybind11/pybind11.h>
#include "strings.hpp"
#include "strprintf.hpp"
namespace pyarb {
using util::pprintf;
void register_identifiers(pybind11::module& m) {
using namespace pybind11::literals;
......@@ -36,8 +39,8 @@ void register_identifiers(pybind11::module& m) {
"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);
.def("__str__", [](arb::cell_member_type m) {return pprintf("<cell member: gid {}, index {}>", m.gid, m.index);})
.def("__repr__",[](arb::cell_member_type m) {return pprintf("<cell member: gid {}, index {}>", m.gid, m.index);});
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.")
......
#include <sstream>
#include <string>
#include <pybind11/pybind11.h>
#include <arbor/version.hpp>
#include <pybind11/pybind11.h>
#include "strprintf.hpp"
#ifdef ARB_MPI_ENABLED
#include <mpi.h>
......@@ -84,13 +86,13 @@ int mpi_is_finalized() {
// Define the stringifier for mpi_comm_shim here, to minimise the ifdefication
// elsewhere in this wrapper code.
std::string mpi_comm_string(const mpi_comm_shim& c) {
std::stringstream s;
s << "<mpi_comm: ";
if (c.comm==MPI_COMM_WORLD) s << "MPI_COMM_WORLD>";
else s << c.comm << ">";
return s.str();
std::ostream& operator<<(std::ostream& o, const mpi_comm_shim& c) {
if (c.comm==MPI_COMM_WORLD) {
return o << "<mpi communicator: MPI_COMM_WORLD>";
}
else {
return o << "<mpi communicator: " << c.comm << ">";
}
}
void register_mpi(pybind11::module& m) {
......@@ -100,8 +102,8 @@ void register_mpi(pybind11::module& m) {
mpi_comm
.def(pybind11::init<>())
.def(pybind11::init([](pybind11::object o){return mpi_comm_shim(o);}))
.def("__str__", &mpi_comm_string)
.def("__repr__", &mpi_comm_string);
.def("__str__", util::to_string<mpi_comm_shim>)
.def("__repr__", util::to_string<mpi_comm_shim>);
m.def("mpi_init", &mpi_init, "Initialize MPI with MPI_THREAD_SINGLE, as required by Arbor.");
m.def("mpi_finalize", &mpi_finalize, "Finalize MPI (calls MPI_Finalize)");
......
......@@ -15,6 +15,7 @@
#include "error.hpp"
#include "event_generator.hpp"
#include "strprintf.hpp"
#include "recipe.hpp"
namespace pyarb {
......@@ -111,20 +112,14 @@ std::vector<arb::event_generator> py_recipe_shim::event_generators(arb::cell_gid
// 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 con_to_string(const arb::cell_connection& c) {
return util::pprintf("<connection: ({},{}) -> ({},{}), delay {}, weight {}>",
c.source.gid, c.source.index, c.dest.gid, c.dest.index, c.delay, c.weight);
}
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();
std::string gj_to_string(const arb::gap_junction_connection& gc) {
return util::pprintf("<gap junction: ({},{}) <-> ({},{}), conductance {}>",
gc.local.gid, gc.local.index, gc.peer.gid, gc.peer.index, gc.ggap);
}
void register_recipe(pybind11::module& m) {
......@@ -157,8 +152,8 @@ void register_recipe(pybind11::module& m) {
"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);
.def("__str__", &con_to_string)
.def("__repr__", &con_to_string);
// Gap Junction Connections
pybind11::class_<arb::gap_junction_connection> gap_junction_connection(m, "gap_junction_connection",
......@@ -182,8 +177,8 @@ void register_recipe(pybind11::module& m) {
"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);
.def("__str__", &gj_to_string)
.def("__repr__", &gj_to_string);
// Recipes
pybind11::class_<py_recipe,
......
......@@ -6,9 +6,28 @@
#include "conversion.hpp"
#include "schedule.hpp"
#include "strprintf.hpp"
namespace pyarb {
std::ostream& operator<<(std::ostream& o, const regular_schedule_shim& x) {
return o << "<regular_schedule: tstart "
<< x.tstart << " ms, dt "
<< x.dt << " ms, tstop "
<< x.tstop << " ms>";
}
std::ostream& operator<<(std::ostream& o, const explicit_schedule_shim& e) {
o << "<explicit_schedule: times [";
return util::csv(o, e.times) << "] ms>";
};
std::ostream& operator<<(std::ostream& o, const poisson_schedule_shim& p) {
return o << "<poisson_schedule: tstart " << p.tstart << " ms"
<< ", freq " << p.freq << " Hz"
<< ", seed " << p.seed << ">";
};
//
// regular_schedule shim
//
......@@ -148,8 +167,8 @@ void register_schedules(pybind11::module& m) {
"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);
.def("__str__", util::to_string<regular_schedule_shim>)
.def("__repr__", util::to_string<regular_schedule_shim>);
// Explicit schedule
pybind11::class_<explicit_schedule_shim> explicit_schedule(m, "explicit_schedule",
......@@ -164,8 +183,8 @@ void register_schedules(pybind11::module& m) {
" 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);
.def("__str__", util::to_string<explicit_schedule_shim>)
.def("__repr__", util::to_string<explicit_schedule_shim>);
// Poisson schedule
pybind11::class_<poisson_schedule_shim> poisson_schedule(m, "poisson_schedule",
......@@ -184,53 +203,9 @@ void register_schedules(pybind11::module& m) {
"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);
.def("__str__", util::to_string<poisson_schedule_shim>)
.def("__repr__", util::to_string<poisson_schedule_shim>);
}
}
/*
// 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();
};
*/
#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);
const bool mpi = arb::has_mpi(c);
s << "<context: threads " << arb::num_threads(c)
<< ", gpu " << (gpu? "yes": "None")
<< ", distributed " << (mpi? "MPI": "local")
<< " ranks " << arb::num_ranks(c)
<< ">";
return s.str();
}
} // namespace pyarb
#pragma once
#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&);
} // namespace pyarb
#pragma once
// printf-like routines that return std::string.
#include <cstdio>
#include <memory>
#include <string>
#include <sstream>
#include <system_error>
#include <utility>
#include <vector>
#include <arbor/util/optional.hpp>
namespace pyarb {
namespace util {
// Use ADL to_string or std::to_string, falling back to ostream formatting:
namespace impl_to_string {
using std::to_string;
template <typename T, typename = void>
struct select {
static std::string str(const T& value) {
std::ostringstream o;
o << value;
return o.str();
}
};
// Can be eplaced with std::void_t in c++17.
template <typename ...Args>
using void_t = void;
template <typename T>
struct select<T, void_t<decltype(to_string(std::declval<T>()))>> {
static std::string str(const T& v) {
return to_string(v);
}
};
}
template <typename T>
std::string to_string(const T& value) {
return impl_to_string::select<T>::str(value);
}
// Use snprintf to format a string, with special handling for standard
// smart pointer types and strings.
namespace impl {
template <typename X>
X sprintf_arg_translate(const X& x) { return x; }
inline const char* sprintf_arg_translate(const std::string& x) { return x.c_str(); }
template <typename T, typename Deleter>
T* sprintf_arg_translate(const std::unique_ptr<T, Deleter>& x) { return x.get(); }
template <typename T>
T* sprintf_arg_translate(const std::shared_ptr<T>& x) { return x.get(); }
}
template <typename... Args>
std::string strprintf(const char* fmt, Args&&... args) {
thread_local static std::vector<char> buffer(1024);
for (;;) {
int n = std::snprintf(buffer.data(), buffer.size(), fmt, impl::sprintf_arg_translate(std::forward<Args>(args))...);
if (n<0) {
throw std::system_error(errno, std::generic_category());
}
else if ((unsigned)n<buffer.size()) {
return std::string(buffer.data(), n);
}
buffer.resize(2*n);
}
}
template <typename... Args>
std::string strprintf(const std::string& fmt, Args&&... args) {
return strprintf(fmt.c_str(), std::forward<Args>(args)...);
}
// Substitute instances of '{}' in the format string with the following parameters,
// using default std::ostream formatting.
namespace impl {
inline void pprintf_(std::ostringstream& o, const char* s) {
o << s;
}
template <typename T, typename... Tail>
void pprintf_(std::ostringstream& o, const char* s, T&& value, Tail&&... tail) {
const char* t = s;
while (*t && !(t[0]=='{' && t[1]=='}')) {
++t;
}
o.write(s, t-s);
if (*t) {
o << std::forward<T>(value);
pprintf_(o, t+2, std::forward<Tail>(tail)...);
}
}
}
template <typename... Args>
std::string pprintf(const char *s, Args&&... args) {
std::ostringstream o;
impl::pprintf_(o, s, std::forward<Args>(args)...);
return o.str();
}
namespace impl {
template <typename Seq>
struct sepval {
const Seq& seq_;
const char* sep_;
sepval(const Seq& seq, const char* sep): seq_(seq), sep_(sep) {}
friend std::ostream& operator<<(std::ostream& o, const sepval& s) {
bool first = true;
for (auto& x: s.seq_) {
if (!first) o << s.sep_;
first = false;
o << x;
}
return o;
}
};
template <typename Seq>
struct sepval_lim {
const Seq& seq_;
const char* sep_;
unsigned count_;
sepval_lim(const Seq& seq, const char* sep, unsigned count): seq_(seq), sep_(sep), count_(count) {}
friend std::ostream& operator<<(std::ostream& o, const sepval_lim& s) {
bool first = true;
unsigned n = s.count_;
for (auto& x: s.seq_) {
if (!first) {
o << s.sep_;
first = false;
}
if (!n) {
return o << "...";
}
--n;
o << x;
}
return o;
}
};
}
template <typename Seq>
std::ostream& sepval(std::ostream& o, const char* sep, const Seq& seq) {
return o << impl::sepval<Seq>(seq, sep);
}
template <typename Seq>
std::ostream& sepval(std::ostream& o, const char* sep, const Seq& seq, unsigned n) {
return o << impl::sepval_lim<Seq>(seq, sep, n);
}
template <typename Seq>
std::ostream& csv(std::ostream& o, const Seq& seq) {
return o << impl::sepval<Seq>(seq, ", ");
}
template <typename Seq>
std::ostream& csv(std::ostream& o, const Seq& seq, unsigned n) {
return o << impl::sepval_lim<Seq>(seq, ", ", n);
}
} // namespace util
template <typename T>
std::ostream& operator<<(std::ostream& o, const arb::util::optional<T>& x) {
return o << (x? util::to_string(*x): "None");
}
} // namespace pyarb
......@@ -44,7 +44,7 @@ class Contexts_arbmpi(unittest.TestCase):
comm = arb.mpi_comm()
# test that by default communicator is MPI_COMM_WORLD
self.assertEqual(str(comm), '<mpi_comm: MPI_COMM_WORLD>')
self.assertEqual(str(comm), '<mpi communicator: MPI_COMM_WORLD>')
def test_context_arbmpi(self):
comm = arb.mpi_comm()
......
......@@ -37,7 +37,7 @@ class Contexts_mpi4py(unittest.TestCase):
comm = arb.mpi_comm(mpi.COMM_WORLD)
# test that set communicator is MPI_COMM_WORLD
self.assertEqual(str(comm), '<mpi_comm: MPI_COMM_WORLD>')
self.assertEqual(str(comm), '<mpi communicator: MPI_COMM_WORLD>')
def test_context_mpi4py(self):
comm = arb.mpi_comm(mpi.COMM_WORLD)
......
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