-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathModule.cpp
228 lines (196 loc) · 10.5 KB
/
Module.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#include "Module.h"
#include "Timeline.h"
#include "Model.h"
#include "MonteCarlo.h"
#include "NPArray.h"
#include "DataFrame.h"
#include "Log.h"
#include "Error.h"
#include "NewOrder.h"
using namespace py::literals;
namespace {
// not visible to (rest of) C++ - use the function declared in Log.h
void log_obj(const py::object& msg)
{
py::print(no::env::logPrefix[no::env::Context::PY], msg);
}
}
// initially set to invalid values (so that if model is not properly initialised its immediately apparent)
std::atomic_int no::env::rank = -1;
std::atomic_int no::env::size = -1;
std::atomic_bool no::env::verbose = false;
std::atomic_bool no::env::checked = true;
std::atomic_int64_t no::env::uniqueIndex = -1;
// these types are not trivially copyable so can't be atomic
std::string no::env::logPrefix[2];
void init_env(py::object mpi)
{
int r = 0;
int s = 1;
py::object comm = py::none();
try
{
py::module mpi = py::module::import("mpi4py.MPI");
comm = mpi.attr("COMM_WORLD");
r = comm.attr("Get_rank")().cast<int>();
s = comm.attr("Get_size")().cast<int>();
}
catch(const py::error_already_set& pyerror)
{
// if something other than module not found has occurred, fail
if (!pyerror.matches(PyExc_ModuleNotFoundError)) throw;
no::warn("neworder installed in serial mode. If necessary, enable MPI with: pip install neworder[parallel]");
}
mpi.attr("COMM") = comm;
mpi.attr("RANK") = r;
mpi.attr("SIZE") = s;
no::env::rank.store(r, std::memory_order_relaxed);
no::env::size.store(s, std::memory_order_relaxed);
no::env::uniqueIndex.store(static_cast<int64_t>(no::env::rank), std::memory_order_relaxed);
// cache log message context for efficiency
no::env::logPrefix[no::env::Context::CPP] = "[no %%/%%]"s % r % s;
no::env::logPrefix[no::env::Context::PY] = "[py %%/%%]"s % r % s;
}
// python-visible log function defined above
PYBIND11_MODULE(_neworder_core, m)
{
// py::options options;
// options.disable_function_signatures();
#include "Module_docstr.cpp"
m.doc() = module_docstr;
// time-related module
auto time = m.def_submodule("time", time_docstr)
.def("isnever", no::time::isnever, time_isnever_docstr, "t"_a) // scalar
.def("isnever", no::time::isnever_a, time_isnever_a_docstr, "t"_a); // array
time.attr("DISTANT_PAST") = no::time::distant_past();
time.attr("FAR_FUTURE") = no::time::far_future();
time.attr("NEVER") = no::time::never();
// register abstract base class
py::class_<no::Timeline, no::PyTimeline>(m, "Timeline")
.def(py::init<>())
.def_property_readonly("time", &no::Timeline::time, timeline_time_docstr)
.def_property_readonly("start", &no::Timeline::start, timeline_start_docstr)
.def_property_readonly("end", &no::Timeline::end, timeline_end_docstr)
.def_property_readonly("index", &no::Timeline::index, timeline_index_docstr)
.def_property_readonly("nsteps", &no::Timeline::nsteps, timeline_nsteps_docstr)
.def_property_readonly("dt", &no::Timeline::dt, timeline_dt_docstr)
.def_property_readonly("at_end", &no::Timeline::at_end, timeline_at_end_docstr)
.def("__repr__", &no::Timeline::repr, timeline_repr_docstr)
;
py::class_<no::NoTimeline, no::Timeline>(m, "NoTimeline", notimeline_docstr)
.def(py::init<>(), notimeline_init_docstr);
py::class_<no::LinearTimeline, no::Timeline>(m, "LinearTimeline", lineartimeline_docstr)
.def(py::init<double, double, size_t>(), lineartimeline_init_docstr, "start"_a, "end"_a, "nsteps"_a)
.def(py::init<double, double>(), lineartimeline_init_open_docstr, "start"_a, "step"_a);
py::class_<no::NumericTimeline, no::Timeline>(m, "NumericTimeline", numerictimeline_docstr)
.def(py::init<const std::vector<double>&>(), numerictimeline_init_docstr, "times"_a);
py::class_<no::CalendarTimeline, no::Timeline>(m, "CalendarTimeline", calendartimeline_docstr)
.def(py::init<std::chrono::system_clock::time_point, std::chrono::system_clock::time_point, size_t, char>(),
calendartimeline_init_docstr, "start"_a, "end"_a, "step"_a, "unit"_a)
.def(py::init<std::chrono::system_clock::time_point, size_t, char>(),
calendartimeline_init_open_docstr, "start"_a, "step"_a, "unit"_a);
// MC
py::class_<no::MonteCarlo>(m, "MonteCarlo", mc_docstr)
// constructor is NOT exposed to python, can only be created within a model
.def_static("deterministic_identical_stream", &no::MonteCarlo::deterministic_identical_stream, mc_deterministic_identical_stream_docstr)
.def_static("deterministic_independent_stream", &no::MonteCarlo::deterministic_independent_stream, mc_deterministic_independent_stream_docstr)
.def_static("nondeterministic_stream", &no::MonteCarlo::nondeterministic_stream, mc_nondeterministic_stream_docstr)
.def("init_bitgen", &no::MonteCarlo::init_bitgen, "internal helper function used by as_np")
.def("seed", &no::MonteCarlo::seed, mc_seed_docstr)
.def("reset", &no::MonteCarlo::reset, mc_reset_docstr)
.def("state", &no::MonteCarlo::state, mc_state_docstr)
.def("raw", &no::MonteCarlo::raw, mc_raw_docstr)
.def("ustream", &no::MonteCarlo::ustream, mc_ustream_docstr, "n"_a)
.def("sample", &no::MonteCarlo::sample, mc_sample_docstr, "n"_a, "cat_weights"_a)
// explicitly give function type for overloads
.def("hazard", py::overload_cast<double, py::ssize_t>(&no::MonteCarlo::hazard),
mc_hazard_docstr,
"p"_a, "n"_a)
.def("hazard", py::overload_cast<const py::array_t<double>&>(&no::MonteCarlo::hazard),
mc_hazard_a_docstr,
"p"_a)
.def("stopping", py::overload_cast<double, py::ssize_t>(&no::MonteCarlo::stopping),
mc_stopping_docstr,
"lambda_"_a, "n"_a)
.def("stopping", py::overload_cast<const py::array_t<double>&>(&no::MonteCarlo::stopping),
mc_stopping_a_docstr,
"lambda_"_a)
.def("counts", &no::MonteCarlo::counts,
mc_counts_docstr,
"lambda_"_a, "dt"_a)
.def("arrivals", &no::MonteCarlo::arrivals,
mc_arrivals_docstr,
"lambda_"_a , "dt"_a, "n"_a, "mingap"_a)
.def("first_arrival", &no::MonteCarlo::first_arrival,
mc_first_arrival_docstr,
"lambda_"_a, "dt"_a, "n"_a, "minval"_a)
.def("first_arrival", [](no::MonteCarlo& self, const py::array_t<double>& lambda_t, double dt, size_t n) {
return self.first_arrival(lambda_t, dt, n, 0.0);
},
mc_first_arrival3_docstr,
"lambda_"_a, "dt"_a, "n"_a)
.def("next_arrival", &no::MonteCarlo::next_arrival,
mc_next_arrival_docstr,
"startingpoints"_a, "lambda_"_a, "dt"_a, "relative"_a, "minsep"_a)
.def("next_arrival", [](no::MonteCarlo& self, const py::array_t<double>& startingpoints, const py::array_t<double>& lambda_t, double dt, bool relative) {
return self.next_arrival(startingpoints, lambda_t, dt, relative, 0.0);
},
mc_next_arrival4_docstr,
"startingpoints"_a, "lambda_"_a, "dt"_a, "relative"_a)
.def("next_arrival", [](no::MonteCarlo& self, const py::array_t<double>& startingpoints, const py::array_t<double>& lambda_t, double dt) {
return self.next_arrival(startingpoints, lambda_t, dt, false, 0.0);
},
mc_next_arrival3_docstr,
"startingpoints"_a, "lambda_"_a, "dt"_a)
.def("__repr__", &no::MonteCarlo::repr, mc_repr_docstr);
// Microsimulation (or ABM) model class
auto model = py::class_<no::Model, no::PyModel>(m, "Model", model_docstr)
.def(py::init<no::Timeline&, const py::function&>(), model_init_docstr,"timeline"_a,
"seeder"_a = py::cpp_function(no::MonteCarlo::deterministic_independent_stream))
// properties are readonly only in the sense you can't assign to them; you CAN call their mutable methods
.def_property_readonly("timeline", &no::Model::timeline, model_timeline_docstr)
.def_property_readonly("mc", &no::Model::mc, model_mc_docstr)
.def_property_readonly("run_state", &no::Model::runState, model_runstate_docstr)
.def("modify", &no::Model::modify, model_modify_docstr)
.def("step", &no::Model::step, model_step_docstr)
.def("check", &no::Model::check, model_check_docstr)
.def("finalise", &no::Model::finalise, model_finalise_docstr)
.def("halt", &no::Model::halt, model_halt_docstr);
// NB the all-important run function is not exposed to python, it can only be executed via the `neworder.run` function
py::enum_<no::Model::RunState>(model, "RunState")
.value("NOT_STARTED", no::Model::NOT_STARTED)
.value("RUNNING", no::Model::RUNNING)
.value("HALTED", no::Model::HALTED)
.value("COMPLETED", no::Model::COMPLETED)
.export_values();
// statistical utils
m.def_submodule("stats", stats_docstr)
.def("logistic", no::logistic,
stats_logistic_docstr,
"x"_a, "x0"_a, "k"_a)
.def("logistic", [](const py::array_t<double>& a, double k) { return no::logistic(a, 0.0, k); },
stats_logistic_docstr_2,
"x"_a, "k"_a)
.def("logistic", [](const py::array_t<double>& a) { return no::logistic(a, 0.0, 1.0); },
stats_logistic_docstr_1,
"x"_a)
.def("logit", no::logit,
stats_logit_docstr,
"x"_a);
// dataframe manipulation
m.def_submodule("df", df_docstr)
.def("unique_index", no::df::unique_index, df_unique_index_docstr, "n"_a)
.def("transition", no::df::transition, df_transition_docstr, "model"_a, "categories"_a, "transition_matrix"_a, "df"_a, "colname"_a)
.def("testfunc", no::df::testfunc, df_testfunc_docstr, "model"_a, "df"_a, "colname"_a);
//.def("linked_change", no::df::linked_change, py::return_value_policy::take_ownership);
// model control plus utility/diagnostics
m.def("log", log_obj, log_docstr, "obj"_a)
.def("run", no::Model::run, run_docstr, "model"_a)
.def("verbose", [](bool v = true) { no::env::verbose.store(v, std::memory_order_relaxed); }, verbose_docstr, "verbose"_a = true)
.def("checked", [](bool c = true) { no::env::checked.store(c, std::memory_order_relaxed); }, checked_docstr, "checked"_a = true);
// MPI submodule
auto mpi = m.def_submodule("mpi", mpi_docstr);
init_env(mpi);
// Map custom C++ exceptions to python ones
py::register_exception_translator(no::exception_translator);
}