|
| 1 | + |
| 2 | +## Transferring dynamically allocated data between NEURON and CoreNEURON |
| 3 | + |
| 4 | + |
| 5 | +User-allocated data can be managed in NMODL using the `POINTER` type. It allows the |
| 6 | +programmer to reference data that has been allocated in HOC or in VERBATIM blocks. This |
| 7 | +allows for more advanced data-structures that are not natively supported in NMODL. |
| 8 | + |
| 9 | +Since NEURON itself has no knowledge of the layout and size of this data it cannot |
| 10 | +transfer `POINTER` data automatically to CoreNEURON. Furtheremore, in many cases there |
| 11 | +is no need to transfer the data between the two instances. In some cases, however, the |
| 12 | +programmer would like to transfer certain user-defined data into CoreNEURON. The most |
| 13 | +prominent example are random123 RNG stream parameters used in synapse mechanisms. To |
| 14 | +support this use-case the `BBCOREPOINTER` type was introduced. Variables that are declared as |
| 15 | +`BBCOREPOINTER` behave exactly the same as `POINTER` but are additionally taken into account |
| 16 | +when NEURON is serializing mechanism data (for file writing or direct-memory transfer). |
| 17 | +For NEURON to be able to write (and indeed CoreNEURON to be able to read) `BBCOREPOINTER` |
| 18 | +data, the programmer has to additionally provide two C functions that are called as part |
| 19 | +of the serialization/deserialization. |
| 20 | + |
| 21 | +``` |
| 22 | +static void bbcore_write(double* x, int* d, int* d_offset, int* x_offset, _threadargsproto_); |
| 23 | +
|
| 24 | +static void bbcore_read(double* x, int* d, int* d_offset, int* x_offset, _threadargsproto_); |
| 25 | +``` |
| 26 | + |
| 27 | +The implementation of `bbcore_write` and `bbcore_read` determines the serialization and |
| 28 | +deserialization of the per-instance mechanism data referenced through the various |
| 29 | +`BBCOREPOINTER`s. |
| 30 | + |
| 31 | +NEURON will call `bbcore_write` twice per mechanism instance. In a first sweep, the call is used to |
| 32 | +determine the required memory to be allocated on the serialization arrays. In the second sweep the |
| 33 | +call is used to fill in the data per mechanism instance. |
| 34 | + |
| 35 | +The functions take following arguments |
| 36 | + |
| 37 | +* `x`: A `double` type array that will be allocated by NEURON to fill with real-valued data. In the |
| 38 | + first call, `x` is NULL as it has not been allocated yet. |
| 39 | +* `d`: An `int` type array that will be allocated by NEURON to fill with integer-valued data. In the |
| 40 | + first call, `d` is NULL as it has not been allocated yet. |
| 41 | +* `x_offset`: The offset in `x` at which the mechanism instance should write its real-valued |
| 42 | + `BBCOREPOINTER` data. In the first call this is an output argument that is expected to be updated |
| 43 | + by the per-instance size to be allocated. |
| 44 | +* `d_offset`: The offset in `x` at which the mechanism instance should write its integer-valued |
| 45 | + `BBCOREPOINTER` data. In the first call this is an output argument that is expected to be updated |
| 46 | + by the per-instance size to be allocated. |
| 47 | +* `_threadargsproto_`: a macro placeholder for NEURON/CoreNEURON data-structure parameters. They |
| 48 | + are typically only used through generated defines and not by the programmer. The macro is defined |
| 49 | + as follows: |
| 50 | + |
| 51 | +``` |
| 52 | +#define _threadargsproto_ \ |
| 53 | + int _iml, int _cntml_padded, double *_p, Datum *_ppvar, ThreadDatum *_thread, NrnThread *_nt, \ |
| 54 | + double _v |
| 55 | +``` |
| 56 | + |
| 57 | +Putting all of this together, the following is a minimal MOD using BBCOREPOINTER: |
| 58 | + |
| 59 | +``` |
| 60 | +TITLE A BBCOREPOINTER Example |
| 61 | +
|
| 62 | +NEURON { |
| 63 | + BBCOREPOINTER my_data |
| 64 | +} |
| 65 | +
|
| 66 | +ASSIGNED { |
| 67 | + my_data |
| 68 | +} |
| 69 | +
|
| 70 | +: Do something interesting with my_data ... |
| 71 | +
|
| 72 | +VERBATIM |
| 73 | +static void bbcore_write(double* x, int* d, int* x_offset, int* d_offset, _threadargsproto_) { |
| 74 | + if (x) { |
| 75 | + double* x_i = x + *x_offset; |
| 76 | + x_i[0] = _p_my_data[0]; |
| 77 | + x_i[1] = _p_my_data[1]; |
| 78 | + } |
| 79 | + *x_offset += 2; // reserve 2 doubles on serialization buffer x |
| 80 | +} |
| 81 | +
|
| 82 | +static void bbcore_read(double* x, int* d, int* x_offset, int* d_offset, _threadargsproto_) { |
| 83 | + assert(!_p_my_data); |
| 84 | + double* x_i = x + *x_offset; |
| 85 | + // my_data needs to be allocated somehow |
| 86 | + _p_my_data = (double*)malloc(sizeof(double)*2); |
| 87 | + _p_my_data[0] = x_i[0]; |
| 88 | + _p_my_data[1] = x_i[1]; |
| 89 | + *x_offset += 2; |
| 90 | +} |
| 91 | +ENDVERBATIM |
| 92 | +``` |
| 93 | + |
0 commit comments