NEURON
nrnpy_p2h.cpp
Go to the documentation of this file.
1 #include <../../nrnconf.h>
2 
3 #include <cstdio>
4 #include <optional>
5 
6 #include <InterViews/resource.h>
7 #include <nrnoc2iv.h>
8 #include <classreg.h>
9 #include "neuron/unique_cstr.hpp"
10 #include "nrnpython.h"
11 #include "hoccontext.h"
12 #include "nrnpy.h"
13 #include "nrnpy_utils.h"
14 #include "oc_ansi.h"
15 
16 #include "parse.hpp"
17 
18 #include <nanobind/nanobind.h>
19 
20 namespace nb = nanobind;
21 
23 static nb::object nrnpy_pyCallObject(nb::callable, nb::object);
26 
27 struct Py2Nrn final {
28  ~Py2Nrn() {
29  nanobind::gil_scoped_acquire lock{};
30  Py_XDECREF(po_);
31  }
32  int type_{}; // 0 toplevel
34 };
35 
36 static void call_python_with_section(Object* pyact, Section* sec) {
37  nb::callable po = nb::borrow<nb::callable>(((Py2Nrn*) pyact->u.this_pointer)->po_);
38  nanobind::gil_scoped_acquire lock{};
39 
40  nb::tuple args = nb::make_tuple(reinterpret_cast<PyObject*>(newpysechelp(sec)));
41  nb::object r = nrnpy_pyCallObject(po, args);
42  if (!r.is_valid()) {
43  auto mes = nrnpyerr_str();
44  if (mes.is_valid()) {
45  Fprintf(stderr, "%s\n", mes.c_str());
46  hoc_execerror("Call of Python Callable failed", nullptr);
47  }
48  if (PyErr_Occurred()) {
49  PyErr_Print();
50  }
51  }
52 }
53 
54 static void* opaque_obj2pyobj(Object* ho) {
55  assert(ho && ho->ctemplate->sym == nrnpy_pyobj_sym_);
56  PyObject* po = ((Py2Nrn*) ho->u.this_pointer)->po_;
57  assert(po);
58  return po;
59 }
60 
62  if (ho->ctemplate->sym == nrnpy_pyobj_sym_) {
63  return ((Py2Nrn*) ho->u.this_pointer)->po_ == po;
64  }
65  return 0;
66 }
67 
68 // contain same Python object
69 static int pysame(Object* o1, Object* o2) {
70  if (o2->ctemplate->sym == nrnpy_pyobj_sym_) {
71  return nrnpy_ho_eq_po(o1, ((Py2Nrn*) o2->u.this_pointer)->po_);
72  }
73  return 0;
74 }
75 
76 // Returns a borrowed reference.
78  PyObject* po = ((Py2Nrn*) ho->u.this_pointer)->po_;
79  if (!po) {
80  if (!main_module) {
81  main_module = PyImport_AddModule("__main__");
82  main_namespace = PyModule_GetDict(main_module);
83  Py_INCREF(main_module);
84  Py_INCREF(main_namespace);
85  }
86  po = main_module;
87  }
88  return po;
89 }
90 
92  Py2Nrn* pn = new Py2Nrn();
93  pn->po_ = po;
94  Py_INCREF(po);
95  pn->type_ = 1;
96  Object* on = hoc_new_object(nrnpy_pyobj_sym_, (void*) pn);
97  hoc_obj_ref(on);
98  return on;
99 }
100 
101 static nb::object nrnpy_pyCallObject(nb::callable callable, nb::object args) {
102  // When hoc calls a PythonObject method, then in case python
103  // calls something back in hoc, the hoc interpreter must be
104  // at the top level
105  auto interp = HocTopContextManager();
106  nb::tuple tup(args);
107  nb::object p = nb::steal(PyObject_CallObject(callable.ptr(), tup.ptr()));
108 #if 0
109 printf("PyObject_CallObject callable\n");
110 PyObject_Print(callable, stdout, 0);
111 printf("\nargs\n");
112 PyObject_Print(args, stdout, 0);
113 printf("\nreturn %p\n", p);
114 #endif
115  // It would be nice to handle the error here, ending with a hoc_execerror
116  // for any Exception (note, that does not include SystemExit). However
117  // since many, but not all, of the callers need to clean up and
118  // release the GIL, errors get handled by the caller or higher up.
119  // The almost generic idiom is:
120  /**
121  if (!p) {
122  auto mes = nrnpyerr_str();
123  if (mes.is_valid()) {
124  Fprintf(stderr, "%s\n", mes.c_str());
125  free(mes);
126  hoc_execerror("Call of Python Callable failed", NULL);
127  }
128  if (PyErr_Occurred()) {
129  PyErr_Print(); // Python process will exit with the error code specified by the
130  SystemExit instance.
131  }
132  }
133  **/
134  return p;
135 }
136 
137 static void py2n_component(Object* ob, Symbol* sym, int nindex, int isfunc) {
138 #if 0
139  if (isfunc) {
140  printf("py2n_component %s.%s(%d)\n", hoc_object_name(ob), sym->name, nindex);
141  }else{
142  printf("py2n_component %s.%s[%d]\n", hoc_object_name(ob), sym->name, nindex);
143  }
144 #endif
145  int i;
146  Py2Nrn* pn = (Py2Nrn*) ob->u.this_pointer;
147  auto head = nb::borrow(pn->po_);
148  nb::object tail;
149  nanobind::gil_scoped_acquire lock{};
150  if (pn->type_ == 0) { // top level
151  if (!main_module) {
152  main_module = PyImport_AddModule("__main__");
153  main_namespace = PyModule_GetDict(main_module);
154  Py_INCREF(main_module);
155  Py_INCREF(main_namespace);
156  }
157  tail = nb::steal(PyRun_String(sym->name, Py_eval_input, main_namespace, main_namespace));
158  } else {
159  if (strcmp(sym->name, "_") == 0) {
160  tail = head;
161  } else {
162  tail = head.attr(sym->name);
163  }
164  }
165  if (!tail) {
166  PyErr_Print();
167  hoc_execerror("No attribute:", sym->name);
168  }
169  Object* on;
170  nb::object result;
171  if (isfunc) {
172  nb::list args{};
173  for (i = 0; i < nindex; ++i) {
174  nb::object arg = nb::steal(nrnpy_hoc_pop("isfunc py2n_component"));
175  if (!arg) {
176  auto err = Py2NRNString::get_pyerr();
177  hoc_execerr_ext("arg %d error: %s", i, err.c_str());
178  }
179  args.append(arg);
180  }
181  args.reverse();
182  // printf("PyObject_CallObject %s %p\n", sym->name, tail);
183  result = nrnpy_pyCallObject(nb::borrow<nb::callable>(tail), args);
184  // PyObject_Print(result, stdout, 0);
185  // printf(" result of call\n");
186  if (!result) {
187  auto mes = nrnpyerr_str();
188  if (mes.is_valid()) {
189  Fprintf(stderr, "%s\n", mes.c_str());
190  hoc_execerror("PyObject method call failed:", sym->name);
191  }
192  if (PyErr_Occurred()) {
193  PyErr_Print();
194  }
195  return;
196  }
197  } else if (nindex) {
198  nb::object arg;
199  int n = hoc_pop_ndim();
200  if (n > 1) {
202  "%d dimensional python objects "
203  "can't be accessed from hoc with var._[i1][i2]... syntax. "
204  "Must use var._[i1]._[i2]... hoc syntax.",
205  n);
206  }
207  if (hoc_stack_type() == NUMBER) {
208  arg = nb::int_((long) hoc_xpop());
209  } else {
210  // I don't think it is syntactically possible
211  // for this to be a VAR. It is possible for it to
212  // be an Object but the GetItem below will raise
213  // TypeError: list indices must be integers or slices, not hoc.HocObject
214  arg = nb::steal(nrnpy_hoc_pop("nindex py2n_component"));
215  }
216  result = tail[arg];
217  if (!result) {
218  PyErr_Print();
219  hoc_execerror("Python get item failed:", hoc_object_name(ob));
220  }
221  } else {
222  result = tail;
223  }
224  // printf("py2n_component %s %d %s result refcount=%d\n", hoc_object_name(ob),
225  // ob->refcount, sym->name, result->ob_refcnt);
226  // if numeric, string, or python HocObject return those
227  if (nrnpy_numbercheck(result.ptr())) {
228  hoc_pop_defer();
229  double d = static_cast<double>(nb::float_(result));
230  hoc_pushx(d);
231  } else if (is_python_string(result.ptr())) {
232  char** ts = hoc_temp_charptr();
233  // TODO double check that this doesn't leak.
234  *ts = Py2NRNString::as_ascii(result.ptr()).release();
235  hoc_pop_defer();
236  hoc_pushstr(ts);
237  } else {
238  // PyObject_Print(result, stdout, 0);
239  on = nrnpy_po2ho(result.ptr());
240  hoc_pop_defer();
241  hoc_push_object(on);
242  if (on) {
243  on->refcount--;
244  }
245  }
246 }
247 
248 static void hpoasgn(Object* o, int type) {
249  int err = 0;
250  int nindex;
251  Symbol* sym;
252  nb::object poright;
253  if (type == NUMBER) {
254  poright = nb::steal(PyFloat_FromDouble(hoc_xpop()));
255  } else if (type == STRING) {
256  poright = nb::steal(Py_BuildValue("s", *hoc_strpop()));
257  } else if (type == OBJECTVAR || type == OBJECTTMP) {
258  Object** po2 = hoc_objpop();
259  poright = nb::steal(nrnpy_ho2po(*po2));
260  hoc_tobj_unref(po2);
261  } else {
262  hoc_execerror("Cannot assign that type to PythonObject", (char*) 0);
263  }
264  auto stack_value = hoc_pop_object();
265  assert(o == stack_value.get());
266  auto poleft = nb::borrow(nrnpy_hoc2pyobject(o));
267  sym = hoc_spop();
268  nindex = hoc_ipop();
269  // printf("hpoasgn %s %s %d\n", hoc_object_name(o), sym->name, nindex);
270  if (nindex == 0) {
271  err = PyObject_SetAttrString(poleft.ptr(), sym->name, poright.ptr());
272  } else if (nindex == 1) {
273  int ndim = hoc_pop_ndim();
274  assert(ndim == 1);
275  auto key = nb::steal(PyLong_FromDouble(hoc_xpop()));
276  nb::object a;
277  if (strcmp(sym->name, "_") == 0) {
278  a = nb::borrow(poleft);
279  } else {
280  a = nb::steal(PyObject_GetAttrString(poleft.ptr(), sym->name));
281  }
282  if (a) {
283  err = PyObject_SetItem(a.ptr(), key.ptr(), poright.ptr());
284  } else {
285  err = -1;
286  }
287  } else {
289  "%d dimensional python objects "
290  "can't be accessed from hoc with var._[i1][i2]... syntax. "
291  "Must use var._[i1]._[i2]... hoc syntax.",
292  nindex);
293  }
294  if (err) {
295  PyErr_Print();
296  hoc_execerror("Assignment to PythonObject failed", NULL);
297  }
298 }
299 
300 static nb::object hoccommand_exec_help1(nb::object po) {
301  if (nb::tuple::check_(po)) {
302  nb::object args = po[1];
303  if (!nb::tuple::check_(args)) {
304  args = nb::make_tuple(args);
305  }
306  return nrnpy_pyCallObject(po[0], args);
307  } else {
308  return nrnpy_pyCallObject(nb::borrow<nb::callable>(po), nb::tuple());
309  }
310 }
311 
312 static nb::object hoccommand_exec_help(Object* ho) {
313  PyObject* po = ((Py2Nrn*) ho->u.this_pointer)->po_;
314  // printf("%s\n", hoc_object_name(ho));
315  return hoccommand_exec_help1(nb::borrow(po));
316 }
317 
318 static double praxis_efun(Object* ho, Object* v) {
319  nanobind::gil_scoped_acquire lock{};
320 
321  auto pc = nb::steal(nrnpy_ho2po(ho));
322  auto pv = nb::steal(nrnpy_ho2po(v));
323  auto po = nb::steal(Py_BuildValue("(OO)", pc.ptr(), pv.ptr()));
324  nb::object r = hoccommand_exec_help1(po);
325  if (!r.is_valid()) {
326  auto mes = nrnpyerr_str();
327  if (mes.is_valid()) {
328  Fprintf(stderr, "%s\n", mes.c_str());
329  hoc_execerror("Call of Python Callable failed in praxis_efun", NULL);
330  }
331  if (PyErr_Occurred()) {
332  PyErr_Print();
333  }
334  return 1e9; // SystemExit?
335  }
336  return static_cast<double>(nb::float_(r));
337 }
338 
339 static int hoccommand_exec(Object* ho) {
340  nanobind::gil_scoped_acquire lock{};
341 
342  nb::object r = hoccommand_exec_help(ho);
343  if (!r.is_valid()) {
344  auto mes = nrnpyerr_str();
345  if (mes.is_valid()) {
346  std::string tmp = "Python Callback failed [hoccommand_exec]:\n";
347  tmp += mes.c_str();
348  hoc_execerror(tmp.c_str(), nullptr);
349  }
350  if (PyErr_Occurred()) {
351  PyErr_Print();
352  }
353  }
354  return r.is_valid();
355 }
356 
357 static int hoccommand_exec_strret(Object* ho, char* buf, int size) {
358  nanobind::gil_scoped_acquire lock{};
359 
360  nb::object r = hoccommand_exec_help(ho);
361  if (r.is_valid()) {
362  nb::str pn(r);
363  auto str = Py2NRNString::as_ascii(pn.ptr());
364  strncpy(buf, str.c_str(), size);
365  buf[size - 1] = '\0';
366  } else {
367  auto mes = nrnpyerr_str();
368  if (mes.is_valid()) {
369  Fprintf(stderr, "%s\n", mes.c_str());
370  hoc_execerror("Python Callback failed", 0);
371  }
372  if (PyErr_Occurred()) {
373  PyErr_Print();
374  }
375  }
376  return r.is_valid();
377 }
378 
379 static void grphcmdtool(Object* ho, int type, double x, double y, int key) {
380  nb::callable po = nb::borrow<nb::callable>(((Py2Nrn*) ho->u.this_pointer)->po_);
381  nanobind::gil_scoped_acquire lock{};
382 
383  nb::tuple args = nb::make_tuple(type, x, y, key);
384  nb::object r = nrnpy_pyCallObject(po, args);
385  if (!r.is_valid()) {
386  auto mes = nrnpyerr_str();
387  if (mes.is_valid()) {
388  Fprintf(stderr, "%s\n", mes.c_str());
389  hoc_execerror("Python Callback failed", 0);
390  }
391  if (PyErr_Occurred()) {
392  PyErr_Print();
393  }
394  }
395 }
396 
398  auto po = nb::borrow(((Py2Nrn*) ho->u.this_pointer)->po_);
399  nanobind::gil_scoped_acquire lock{};
400 
401  auto args = nb::steal(PyTuple_New((Py_ssize_t) narg));
402  if (!args) {
403  hoc_execerror("PyTuple_New failed", 0);
404  }
405  for (int i = 0; i < narg; ++i) {
406  // not used with datahandle args.
407  auto item = nb::steal(nrnpy_hoc_pop("callable_with_args"));
408  if (!item) {
409  hoc_execerror("nrnpy_hoc_pop failed", 0);
410  }
411  if (PyTuple_SetItem(args.ptr(), (Py_ssize_t) (narg - i - 1), item.release().ptr()) != 0) {
412  hoc_execerror("PyTuple_SetItem failed", 0);
413  }
414  }
415 
416  nb::tuple r = nb::make_tuple(po, args);
417 
418  Object* hr = nrnpy_po2ho(r.release().ptr());
419 
420  return hr;
421 }
422 
423 static double func_call(Object* ho, int narg, int* err) {
424  nb::callable po = nb::borrow<nb::callable>(((Py2Nrn*) ho->u.this_pointer)->po_);
425  nanobind::gil_scoped_acquire lock{};
426 
427  nb::list args{};
428  for (int i = 0; i < narg; ++i) {
429  nb::object item = nb::steal(nrnpy_hoc_pop("func_call"));
430  if (!item) {
431  hoc_execerror("nrnpy_hoc_pop failed", 0);
432  }
433  args.append(item);
434  }
435  args.reverse();
436 
437  nb::object r = nrnpy_pyCallObject(po, args);
438  double rval = 0.0;
439  if (!r.is_valid()) {
440  if (!err || *err) {
441  auto mes = nrnpyerr_str();
442  if (mes.is_valid()) {
443  Fprintf(stderr, "%s\n", mes.c_str());
444  }
445  if (PyErr_Occurred()) {
446  PyErr_Print();
447  }
448  } else {
449  PyErr_Clear();
450  }
451  if (!err || *err) {
452  hoc_execerror("func_call failed", NULL);
453  }
454  if (err) {
455  *err = 1;
456  }
457  } else {
458  if (nrnpy_numbercheck(r.ptr())) {
459  rval = static_cast<double>(nb::float_(r));
460  }
461  if (err) {
462  *err = 0;
463  } // success
464  }
465  return rval;
466 }
467 
468 static double guigetval(Object* ho) {
469  nb::gil_scoped_acquire lock{};
470  nb::tuple po(((Py2Nrn*) ho->u.this_pointer)->po_);
471  if (nb::sequence::check_(po[0]) || nb::mapping::check_(po[0])) {
472  return nb::cast<double>(po[0][po[1]]);
473  } else {
474  return nb::cast<double>(po[0].attr(po[1]));
475  }
476 }
477 
478 static void guisetval(Object* ho, double x) {
479  nb::gil_scoped_acquire lock{};
480  nb::tuple po(((Py2Nrn*) ho->u.this_pointer)->po_);
481  if (nb::sequence::check_(po[0]) || nb::mapping::check_(po[0])) {
482  po[0][po[1]] = x;
483  } else {
484  po[0].attr(po[1]) = x;
485  }
486 }
487 
488 static int guigetstr(Object* ho, char** cpp) {
489  nb::gil_scoped_acquire lock{};
490  nb::tuple po(((Py2Nrn*) ho->u.this_pointer)->po_);
491  auto name = nb::cast<std::string>(po[0].attr(po[1]));
492  if (*cpp && name == *cpp) {
493  return 0;
494  }
495  if (*cpp) {
496  delete[] * cpp;
497  }
498  *cpp = new char[name.size() + 1];
499  strcpy(*cpp, name.c_str());
500  return 1;
501 }
502 
503 static nb::callable loads;
504 static nb::callable dumps;
505 
506 static void setpickle() {
507  if (dumps) {
508  return;
509  }
510  nb::module_ pickle = nb::module_::import_("pickle");
511  dumps = pickle.attr("dumps");
512  loads = pickle.attr("loads");
513  if (!dumps || !loads) {
514  hoc_execerror("Neither Python cPickle nor pickle are available", 0);
515  }
516  // We intentionally leak these, because if we don't
517  // we observe SEGFAULTS during application shutdown.
518  // Likely because "~nb::callable" runs after the Python
519  // library cleaned up.
520  dumps.inc_ref();
521  loads.inc_ref();
522 }
523 
524 // note that *size includes the null terminating character if it exists
525 static std::vector<char> pickle(PyObject* p) {
526  auto r = nb::borrow<nb::bytes>(dumps(nb::borrow(p)));
527  if (!r && PyErr_Occurred()) {
528  PyErr_Print();
529  }
530  assert(r);
531  return std::vector<char>(r.c_str(), r.c_str() + r.size());
532 }
533 
534 static std::vector<char> po2pickle(Object* ho) {
535  setpickle();
536  if (ho && ho->ctemplate->sym == nrnpy_pyobj_sym_) {
537  return pickle(nrnpy_hoc2pyobject(ho));
538  } else {
539  return {};
540  }
541 }
542 
543 static nb::object unpickle(const char* s, std::size_t len) {
544  return loads(nb::bytes(s, len));
545 }
546 
547 static nb::object unpickle(const std::vector<char>& s) {
548  return unpickle(s.data(), s.size());
549 }
550 
551 static Object* pickle2po(const std::vector<char>& s) {
552  setpickle();
553  nb::object po = unpickle(s);
554  Object* ho = nrnpy_pyobject_in_obj(po.ptr());
555  return ho;
556 }
557 
558 /** Full python traceback error message returned as string.
559  * Caller should free the return value if not NULL
560  **/
562  if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_Exception)) {
563  PyObject *ptype, *pvalue, *ptraceback;
564  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
565  PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
566 
567  auto type = nb::steal(ptype);
568  auto value = nb::steal(pvalue);
569  auto traceback = nb::steal(ptraceback);
570 
571  // try for full backtrace
572  nb::str py_str;
573  neuron::unique_cstr cmes;
574 
575  // Since traceback.format_exception returns list of strings, wrap
576  // in neuron.format_exception that returns a string.
577  if (!traceback) {
578  traceback = nb::none();
579  }
580  nb::module_ pyth_module = nb::module_::import_("neuron");
581  if (pyth_module) {
582  nb::callable pyth_func = pyth_module.attr("format_exception");
583  if (pyth_func) {
584  py_str = nb::str(pyth_func(type, value, traceback));
585  }
586  }
587  if (py_str) {
588  cmes = neuron::unique_cstr(strdup(py_str.c_str()));
589  }
590 
591  if (!py_str) {
592  PyErr_Print();
593  Fprintf(stderr, "nrnpyerr_str failed\n");
594  }
595 
596  return cmes;
597  }
598  return {};
599 }
600 
601 std::vector<char> call_picklef(const std::vector<char>& fname, int narg) {
602  // fname is a pickled callable, narg is the number of args on the
603  // hoc stack with types double, char*, hoc Vector, and PythonObject
604  // callable return must be pickleable.
605  setpickle();
606  nb::bytes ps(fname.data(), fname.size());
607 
608  auto callable = nb::borrow<nb::callable>(loads(ps));
609  assert(callable);
610 
611  nb::list args{};
612  for (int i = 0; i < narg; ++i) {
613  nb::object arg = nb::steal(nrnpy_hoc_pop("call_picklef"));
614  args.append(arg);
615  }
616  nb::object result = callable(*args);
617  if (!result) {
618  auto mes = nrnpyerr_str();
619  if (mes.is_valid()) {
620  Fprintf(stderr, fmt::format("{}\n", mes.c_str()).c_str());
621  hoc_execerror("PyObject method call failed:", NULL);
622  }
623  if (PyErr_Occurred()) {
624  PyErr_Print();
625  }
626  }
627  return pickle(result.ptr());
628 }
629 
630 #include "nrnmpi.h"
631 
632 static std::vector<int> mk_displ(int* cnts) {
633  std::vector<int> displ(nrnmpi_numprocs + 1);
634  displ[0] = 0;
635  for (int i = 0; i < nrnmpi_numprocs; ++i) {
636  displ[i + 1] = displ[i] + cnts[i];
637  }
638  return displ;
639 }
640 
641 static nb::list char2pylist(const std::vector<char>& buf,
642  const std::vector<int>& cnt,
643  const std::vector<int>& displ) {
644  nb::list plist{};
645  for (int i = 0; i < cnt.size(); ++i) {
646  if (cnt[i] == 0) {
647  plist.append(nb::none());
648  } else {
649  plist.append(unpickle(buf.data() + displ[i], cnt[i]));
650  }
651  }
652  return plist;
653 }
654 
655 #if NRNMPI
656 // Returns a new reference.
657 static PyObject* py_allgather(PyObject* psrc) {
658  int np = nrnmpi_numprocs;
659  auto sbuf = pickle(psrc);
660  // what are the counts from each rank
661  std::vector<int> rcnt(np);
662  rcnt[nrnmpi_myid] = static_cast<int>(sbuf.size());
663  nrnmpi_int_allgather_inplace(rcnt.data(), 1);
664  auto rdispl = mk_displ(rcnt.data());
665  std::vector<char> rbuf(rdispl[np]);
666 
667  nrnmpi_char_allgatherv(sbuf.data(), rbuf.data(), rcnt.data(), rdispl.data());
668 
669  return char2pylist(rbuf, rcnt, rdispl).release().ptr();
670 }
671 
672 // Returns a new reference.
673 static PyObject* py_gather(PyObject* psrc, int root) {
674  int np = nrnmpi_numprocs;
675  auto sbuf = pickle(psrc);
676  // what are the counts from each rank
677  int scnt = static_cast<int>(sbuf.size());
678  std::vector<int> rcnt;
679  if (root == nrnmpi_myid) {
680  rcnt.resize(np);
681  }
682  nrnmpi_int_gather(&scnt, rcnt.data(), 1, root);
683  std::vector<int> rdispl;
684  std::vector<char> rbuf;
685  if (root == nrnmpi_myid) {
686  rdispl = mk_displ(rcnt.data());
687  rbuf.resize(rdispl[np]);
688  }
689 
690  nrnmpi_char_gatherv(sbuf.data(), scnt, rbuf.data(), rcnt.data(), rdispl.data(), root);
691 
692  nb::object pdest = nb::none();
693  if (root == nrnmpi_myid) {
694  pdest = char2pylist(rbuf, rcnt, rdispl);
695  }
696  return pdest.release().ptr();
697 }
698 
699 // Returns a new reference.
700 static PyObject* py_broadcast(PyObject* psrc, int root) {
701  // Note: root returns reffed psrc.
702  std::vector<char> buf{};
703  int cnt = 0;
704  if (root == nrnmpi_myid) {
705  buf = pickle(psrc);
706  cnt = static_cast<int>(buf.size());
707  }
709  if (root != nrnmpi_myid) {
710  buf.resize(cnt);
711  }
712  nrnmpi_char_broadcast(buf.data(), cnt, root);
713  nb::object pdest;
714  if (root != nrnmpi_myid) {
715  pdest = unpickle(buf);
716  } else {
717  pdest = nb::borrow(psrc);
718  }
719  return pdest.release().ptr();
720 }
721 #endif
722 
723 // type 1-alltoall, 2-allgather, 3-gather, 4-broadcast, 5-scatter
724 // size for 3, 4, 5 refer to rootrank.
725 static Object* py_alltoall_type(int size, int type) {
726  int np = nrnmpi_numprocs; // of subworld communicator
727  nb::object psrc;
728 
729  if (type == 1 || type == 5) { // alltoall, scatter
730  Object* o = *hoc_objgetarg(1);
731  if (type == 1 || nrnmpi_myid == size) { // if scatter only root must be a list
732  psrc = nb::borrow(nrnpy_hoc2pyobject(o));
733  if (!PyList_Check(psrc.ptr())) {
734  hoc_execerror("Argument must be a Python list", 0);
735  }
736  if (PyList_Size(psrc.ptr()) != np) {
737  if (type == 1) {
738  hoc_execerror("py_alltoall list size must be nhost", 0);
739  } else {
740  hoc_execerror("py_scatter list size must be nhost", 0);
741  }
742  }
743  }
744  if (np == 1) {
745  if (type == 1) {
746  return o;
747  } else { // return psrc[0]
748  auto pdest = nb::borrow(PyList_GetItem(psrc.ptr(), 0));
749  Object* ho = nrnpy_po2ho(pdest.ptr());
750  if (ho) {
751  --ho->refcount;
752  }
753  return ho;
754  }
755  }
756  } else {
757  // Get the raw PyObject* arg. So things like None, int, bool are preserved.
758  psrc = nb::borrow(hocobj_call_arg(0));
759 
760  if (np == 1) {
761  nb::object pdest;
762  if (type == 4) { // broadcast is just the PyObject
763  pdest = psrc;
764  } else { // allgather and gather must wrap psrc in list
765  pdest = nb::steal(PyList_New(1));
766  PyList_SetItem(pdest.ptr(), 0, psrc.release().ptr());
767  }
768  Object* ho = nrnpy_po2ho(pdest.ptr());
769  if (ho) {
770  --ho->refcount;
771  }
772  return ho;
773  }
774  }
775 
776 #if NRNMPI
777  setpickle();
778  int root;
779  nb::object pdest;
780 
781  if (type == 2) {
782  pdest = nb::steal(py_allgather(psrc.ptr()));
783  } else if (type != 1 && type != 5) {
784  root = size;
785  if (root < 0 || root >= np) {
786  hoc_execerror("root rank must be >= 0 and < nhost", 0);
787  }
788  if (type == 3) {
789  pdest = nb::steal(py_gather(psrc.ptr(), root));
790  } else if (type == 4) {
791  pdest = nb::steal(py_broadcast(psrc.ptr(), root));
792  }
793  } else {
794  if (type == 5) { // scatter
795  root = size;
796  size = 0; // calculate dest size (cannot be -1 so cannot return it)
797  }
798 
799  std::vector<char> s{};
800  std::vector<int> scnt{};
801 
802  // setup source buffer for transfer s, scnt, sdispl
803  // for alltoall, each rank handled identically
804  // for scatter, root handled as list all, other ranks handled as None
805  if (type == 1 || nrnmpi_myid == root) { // psrc is list of nhost items
806  scnt.reserve(np);
807  for (const nb::handle& p: nb::list(psrc)) {
808  if (p.is_none()) {
809  scnt.push_back(0);
810  continue;
811  }
812  const std::vector<char> b = pickle(p.ptr());
813  if (size >= 0) {
814  s.insert(std::end(s), std::begin(b), std::end(b));
815  }
816  scnt.push_back(static_cast<int>(b.size()));
817  }
818 
819  // scatter equivalent to alltoall NONE list for not root ranks.
820  } else if (type == 5 && nrnmpi_myid != root) {
821  // nothing to setup, s, scnt, sdispl already NULL
822  }
823 
824  // destinations need to know receive counts. Then transfer data.
825  if (type == 1) { // alltoall
826 
827  // what are destination counts
828  std::vector<int> ones(np, 1);
829  auto sdispl = mk_displ(ones.data());
830  std::vector<int> rcnt(np);
832  scnt.data(), ones.data(), sdispl.data(), rcnt.data(), ones.data(), sdispl.data());
833 
834  // exchange
835  sdispl = mk_displ(scnt.data());
836  auto rdispl = mk_displ(rcnt.data());
837  if (size < 0) {
838  pdest = nb::make_tuple(sdispl[np], rdispl[np]);
839  } else {
840  std::vector<char> r(rdispl[np] + 1); // force > 0 for all None case
841  nrnmpi_char_alltoallv(
842  s.data(), scnt.data(), sdispl.data(), r.data(), rcnt.data(), rdispl.data());
843 
844  pdest = char2pylist(r, rcnt, rdispl);
845  }
846 
847  } else { // scatter
848 
849  // destination counts
850  int rcnt = -1;
851  nrnmpi_int_scatter(scnt.data(), &rcnt, 1, root);
852  std::vector<char> r(rcnt + 1); // rcnt can be 0
853  std::vector<int> sdispl;
854 
855  // exchange
856  if (nrnmpi_myid == root) {
857  sdispl = mk_displ(scnt.data());
858  }
859  nrnmpi_char_scatterv(s.data(), scnt.data(), sdispl.data(), r.data(), rcnt, root);
860 
861  if (rcnt) {
862  pdest = unpickle(r);
863  } else {
864  pdest = nb::none();
865  }
866  }
867  }
868 
869  Object* ho = nrnpy_po2ho(pdest.ptr());
870 
871  // The problem here is when `pdest` is a HOC object. In that case `ho` has `refcount == 2` but
872  // must be returned with refcount 0. The `ho` is contained in the `pdest` (which is why the HOC
873  // refcount is 2) and will be destroyed along with the `pdest` if its reference count is 0.
874  //
875  // Therefore, we must first DECREF `pdest` and then decrement `ho->refcount`. If we do
876  // it the other way around the `Py_DECREF` causes the Python object to be deallocated,
877  // as part of that deallocation method, the contained HOC object is dereferenced, and because
878  // it'll have a reference count of 0 it's also be deallocated.
879  pdest.dec_ref();
880  pdest.release();
881 
882  if (ho) {
883  --ho->refcount;
884  }
885  return ho;
886 #else
887  assert(0);
888  return NULL;
889 #endif
890 }
891 
893  return PyEval_SaveThread();
894 }
895 static void restore_thread(PyThreadState* g) {
896  PyEval_RestoreThread(g);
897 }
898 
901 
902 static void* p_cons(Object*) {
903  return new Py2Nrn{};
904 }
905 
906 static void p_destruct(void* v) {
907  delete static_cast<Py2Nrn*>(v);
908 }
909 
910 /**
911  * @brief Populate NEURON state with information from a specific Python.
912  * @param ptrs Logically a return value; avoidi
913  */
915  assert(ptrs);
916  class2oc("PythonObject", p_cons, p_destruct, nullptr, nullptr, nullptr);
917  nrnpy_pyobj_sym_ = hoc_lookup("PythonObject");
920  ptrs->call_func = func_call;
921  ptrs->call_picklef = call_picklef;
923  ptrs->cmdtool = grphcmdtool;
924  ptrs->guigetstr = guigetstr;
925  ptrs->guigetval = guigetval;
926  ptrs->guisetval = guisetval;
929  ptrs->ho2po = nrnpy_ho2po;
930  ptrs->hpoasgn = hpoasgn;
933  ptrs->pickle2po = pickle2po;
934  ptrs->po2ho = nrnpy_po2ho;
935  ptrs->po2pickle = po2pickle;
936  ptrs->praxis_efun = praxis_efun;
937  ptrs->pysame = pysame;
940  ptrs->save_thread = save_thread;
941  // call a function in nrnpython.cpp to register the functions defined there
943  // call a function in nrnpy_hoc.cpp to register the functions defined there
945 }
static void nrnmpi_int_alltoallv(const int *s, const int *scnt, const int *sdispl, int *r, int *rcnt, int *rdispl)
#define STRING
Definition: bbslsrv.cpp:9
static neuron::unique_cstr as_ascii(PyObject *python_string)
Definition: nrnpy_utils.cpp:17
static neuron::unique_cstr get_pyerr()
Definition: nrnpy_utils.cpp:53
A RAII wrapper for C-style strings.
Definition: unique_cstr.hpp:18
char * release()
Releases ownership of the string.
Definition: unique_cstr.hpp:44
void class2oc(const char *, ctor_f *cons, dtor_f *destruct, Member_func *, Member_ret_obj_func *, Member_ret_str_func *)
Definition: hoc_oop.cpp:1631
#define cnt
Definition: tqueue.hpp:44
#define key
Definition: tqueue.hpp:45
#define v
Definition: md1redef.h:11
#define sec
Definition: md1redef.h:20
#define i
Definition: md1redef.h:19
static double interp(double frac, double x1, double x2)
Definition: functabl.cpp:67
char buf[512]
Definition: init.cpp:13
double hoc_xpop()
Definition: code.cpp:903
void hoc_pushstr(char **d)
Definition: code.cpp:800
void hoc_execerr_ext(const char *fmt,...)
printf style specification of hoc_execerror message.
Definition: fileio.cpp:828
Object * hoc_new_object(Symbol *symtemp, void *v)
Definition: hoc_oop.cpp:450
int hoc_ipop()
Definition: code.cpp:967
char ** hoc_temp_charptr(void)
Definition: code.cpp:717
void hoc_pop_defer()
Definition: code.cpp:318
int hoc_pop_ndim()
Definition: code.cpp:933
void hoc_obj_ref(Object *obj)
Definition: hoc_oop.cpp:1844
char * hoc_object_name(Object *ob)
Definition: hoc_oop.cpp:73
Symbol * hoc_spop()
Definition: code.cpp:928
Symbol * hoc_lookup(const char *)
Definition: symbol.cpp:59
void hoc_push_object(Object *d)
Definition: code.cpp:793
TmpObject hoc_pop_object()
Definition: code.cpp:957
#define assert(ex)
Definition: hocassrt.h:24
#define OBJECTTMP
Definition: hocdec.h:88
Object ** hoc_objgetarg(int)
Definition: code.cpp:1614
static int narg()
Definition: ivocvect.cpp:121
void hoc_pushx(double)
Definition: code.cpp:779
printf
Definition: extdef.h:5
const char * name
Definition: init.cpp:16
static int np
Definition: mpispike.cpp:25
void hoc_execerror(const char *s1, const char *s2)
Definition: nrnoc_aux.cpp:39
handle_interface< non_owning_identifier< storage > > handle
Non-owning handle to a Mechanism instance.
int root
Definition: cellorder.cpp:622
#define NRN_EXPORT
Definition: nrn_export.hpp:6
int const size_t const size_t n
Definition: nrngsl.h:10
size_t p
s
Definition: multisend.cpp:521
short type
Definition: cabvars.h:10
Symbol * nrnpy_pyobj_sym_
Definition: hoc_oop.cpp:25
_ts PyThreadState
Definition: nrnpy.h:14
_object PyObject
Definition: nrnpy.h:12
int nrnpy_numbercheck(PyObject *po)
Definition: nrnpy_hoc.cpp:542
char ** hoc_strpop()
Definition: code.cpp:962
Object ** hoc_objpop()
Pop pointer to object pointer and return top elem from stack.
Definition: code.cpp:943
int hoc_stack_type()
Get the type of the top entry.
Definition: code.cpp:310
void hoc_tobj_unref(Object **)
Definition: code.cpp:160
PyObject * hocobj_call_arg(int i)
Definition: nrnpy_hoc.cpp:778
Object * nrnpy_po2ho(PyObject *po)
Definition: nrnpy_hoc.cpp:592
PyObject * nrnpy_ho2po(Object *o)
Definition: nrnpy_hoc.cpp:566
PyObject * nrnpy_hoc_pop(const char *mes)
Definition: nrnpy_hoc.cpp:620
NPySecObj * newpysechelp(Section *sec)
Definition: nrnpy_nrn.cpp:1043
std::vector< char > call_picklef(const std::vector< char > &fname, int narg)
Definition: nrnpy_p2h.cpp:601
static int guigetstr(Object *ho, char **cpp)
Definition: nrnpy_p2h.cpp:488
static double func_call(Object *ho, int narg, int *err)
Definition: nrnpy_p2h.cpp:423
static std::vector< char > pickle(PyObject *p)
Definition: nrnpy_p2h.cpp:525
static double guigetval(Object *ho)
Definition: nrnpy_p2h.cpp:468
static neuron::unique_cstr nrnpyerr_str()
Full python traceback error message returned as string.
Definition: nrnpy_p2h.cpp:561
static Object * pickle2po(const std::vector< char > &s)
Definition: nrnpy_p2h.cpp:551
static nb::object nrnpy_pyCallObject(nb::callable, nb::object)
Definition: nrnpy_p2h.cpp:101
void nrnpython_reg_real_nrnpy_hoc_cpp(neuron::python::impl_ptrs *ptrs)
Definition: nrnpy_hoc.cpp:3491
static void grphcmdtool(Object *ho, int type, double x, double y, int key)
Definition: nrnpy_p2h.cpp:379
static void py2n_component(Object *ob, Symbol *sym, int nindex, int isfunc)
Definition: nrnpy_p2h.cpp:137
static int hoccommand_exec(Object *ho)
Definition: nrnpy_p2h.cpp:339
static nb::object hoccommand_exec_help1(nb::object po)
Definition: nrnpy_p2h.cpp:300
NRN_EXPORT void nrnpython_reg_real(neuron::python::impl_ptrs *ptrs)
Populate NEURON state with information from a specific Python.
Definition: nrnpy_p2h.cpp:914
Object * nrnpy_pyobject_in_obj(PyObject *po)
Definition: nrnpy_p2h.cpp:91
static std::vector< char > po2pickle(Object *ho)
Definition: nrnpy_p2h.cpp:534
static double praxis_efun(Object *ho, Object *v)
Definition: nrnpy_p2h.cpp:318
static void restore_thread(PyThreadState *g)
Definition: nrnpy_p2h.cpp:895
static void p_destruct(void *v)
Definition: nrnpy_p2h.cpp:906
void nrnpython_reg_real_nrnpython_cpp(neuron::python::impl_ptrs *ptrs)
Definition: nrnpython.cpp:415
static int pysame(Object *o1, Object *o2)
Definition: nrnpy_p2h.cpp:69
static PyThreadState * save_thread()
Definition: nrnpy_p2h.cpp:892
static std::vector< int > mk_displ(int *cnts)
Definition: nrnpy_p2h.cpp:632
static nb::object unpickle(const char *s, std::size_t len)
Definition: nrnpy_p2h.cpp:543
static PyObject * main_module
Definition: nrnpy_p2h.cpp:24
static void * opaque_obj2pyobj(Object *ho)
Definition: nrnpy_p2h.cpp:54
PyObject * nrnpy_hoc2pyobject(Object *ho)
Definition: nrnpy_p2h.cpp:77
static void setpickle()
Definition: nrnpy_p2h.cpp:506
static nb::callable loads
Definition: nrnpy_p2h.cpp:503
static nb::callable dumps
Definition: nrnpy_p2h.cpp:504
static PyObject * main_namespace
Definition: nrnpy_p2h.cpp:25
static Object * py_alltoall_type(int size, int type)
Definition: nrnpy_p2h.cpp:725
static void guisetval(Object *ho, double x)
Definition: nrnpy_p2h.cpp:478
static Object * callable_with_args(Object *ho, int narg)
Definition: nrnpy_p2h.cpp:397
static void call_python_with_section(Object *pyact, Section *sec)
Definition: nrnpy_p2h.cpp:36
static nb::list char2pylist(const std::vector< char > &buf, const std::vector< int > &cnt, const std::vector< int > &displ)
Definition: nrnpy_p2h.cpp:641
int nrnpy_ho_eq_po(Object *ho, PyObject *po)
Definition: nrnpy_p2h.cpp:61
static void * p_cons(Object *)
Definition: nrnpy_p2h.cpp:902
static int hoccommand_exec_strret(Object *ho, char *buf, int size)
Definition: nrnpy_p2h.cpp:357
static void hpoasgn(Object *o, int type)
Definition: nrnpy_p2h.cpp:248
static nb::object hoccommand_exec_help(Object *ho)
Definition: nrnpy_p2h.cpp:312
bool is_python_string(PyObject *python_string)
Definition: nrnpy_utils.h:9
#define lock
int nrnmpi_myid
HOC interpreter function declarations (included by hocdec.h)
static Object ** py_gather(void *)
Definition: ocbbs.cpp:394
static void nrnmpi_char_broadcast(char *, int, int)
Definition: ocbbs.cpp:43
static Object ** py_broadcast(void *)
Definition: ocbbs.cpp:398
static void nrnmpi_int_broadcast(int *, int, int)
Definition: ocbbs.cpp:42
static Object ** py_allgather(void *)
Definition: ocbbs.cpp:390
static uint32_t value
Definition: scoprand.cpp:25
#define pc
Definition: section.h:37
#define NULL
Definition: spdefs.h:105
Definition: hocdec.h:173
void * this_pointer
Definition: hocdec.h:178
int refcount
Definition: hocdec.h:174
cTemplate * ctemplate
Definition: hocdec.h:180
union Object::@47 u
PyObject * po_
Definition: nrnpy_p2h.cpp:33
~Py2Nrn()
Definition: nrnpy_p2h.cpp:28
int type_
Definition: nrnpy_p2h.cpp:32
Definition: model.h:47
char * name
Definition: model.h:61
Symbol * sym
Definition: hocdec.h:147
Collection of pointers to functions with python-version-specific implementations.
Definition: nrnpy.h:25
void(* cmdtool)(Object *, int type, double x, double y, int kd)
Definition: nrnpy.h:30
int(* guigetstr)(Object *, char **)
Definition: nrnpy.h:31
std::vector< char >(* call_picklef)(const std::vector< char > &, int narg)
Definition: nrnpy.h:28
Object *(* callable_with_args)(Object *, int narg)
Definition: nrnpy.h:26
PyThreadState *(* save_thread)()
Definition: nrnpy.h:54
void(* hpoasgn)(Object *, int)
Definition: nrnpy.h:41
std::vector< char >(* po2pickle)(Object *)
Definition: nrnpy.h:49
void(* restore_thread)(PyThreadState *)
Definition: nrnpy.h:53
int(* hoccommand_exec_strret)(Object *, char *, int)
Definition: nrnpy.h:38
void *(* opaque_obj2pyobj)(Object *)
Definition: nrnpy.h:46
double(* guigetval)(Object *)
Definition: nrnpy.h:32
Object *(* po2ho)(PyObject *)
Definition: nrnpy.h:48
PyObject *(* ho2po)(Object *)
Definition: nrnpy.h:40
void(* py2n_component)(Object *, Symbol *, int, int)
Definition: nrnpy.h:52
double(* call_func)(Object *, int, int *)
Definition: nrnpy.h:27
void(* guisetval)(Object *, double)
Definition: nrnpy.h:36
int(* pysame)(Object *o1, Object *o2)
Definition: nrnpy.h:51
Object *(* mpi_alltoall_type)(int, int)
Definition: nrnpy.h:44
double(* praxis_efun)(Object *pycallable, Object *hvec)
Definition: nrnpy.h:50
int(* hoccommand_exec)(Object *)
Definition: nrnpy.h:37
Object *(* pickle2po)(const std::vector< char > &)
Definition: nrnpy.h:47
void(* call_python_with_section)(Object *, Section *)
Definition: nrnpy.h:29
int Fprintf(FILE *stream, const char *fmt, Args... args)
Definition: logger.hpp:8