This repository has been archived by the owner on Feb 2, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 22
/
developer-codegen.txt
648 lines (509 loc) · 17.8 KB
/
developer-codegen.txt
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
.. currentmodule:: brian.experimental.codegen2
.. index::
pair: code; generation
Code generation
^^^^^^^^^^^^^^^
.. warning::
This section is a work in progress, documenting the most recent code generation
framework, which is in the package ``brian.experimental.codegen2``.
Overview
========
To generate code, we start with a basic statement or set of statements we want
to evaluate for all neurons, or for all synapses, and then apply various
transformations to generate code that will do this. We start from a
structured, language-invariant representation of the set of basic statements.
We then 'resolve' the unknown symbols in it. This is done recursively, the
resolution of each symbol can add vectorised statements or loops to the
current representation, and add data to a namespace that will be associated to
the final code. Symbols will be things like a :class:`~brian.NeuronGroup` state
variable, or a synaptic weight value. The output of this process is a new,
more complicated structured representation, including things like loops if
necessary. Next, we convert this structured representation into a code
string. Finally, this code string is JIT-compiled into an executable object.
Using numerical integration generation
--------------------------------------
You can use Brian's equations format to generate C/C++ code for a numerical
integration step, for example::
eqs = '''
dv/dt = (ge+gi-(v+49*mV))/(20*ms) : volt
dge/dt = -ge/(5*ms) : volt
dgi/dt = -gi/(10*ms) : volt
'''
code, vars, params = make_c_integrator(eqs, method=euler, dt=0.1*ms)
print code
has output::
double _temp_v = 50.0*ge + 50.0*gi - 50.0*v - 2.45;
double _temp_ge = -200.0*ge;
double _temp_gi = -100.0*gi;
v += _temp_v*0.0001;
ge += _temp_ge*0.0001;
gi += _temp_gi*0.0001;
See the documentation for the function :func:`make_c_integrator`.
Using the code generation package
---------------------------------
The basic way to use the code generation module is as follows:
1. Create a :class:`Block` of :class:`Statement` objects which you want to
execute. You can use :func:`statements_from_codestring` to do this.
2. Create a dictionary of :class:`Symbol` objects corresponding to the symbols
in the block above.
3. Call :meth:`CodeItem.generate` with the specified language and symbols, to
give you a :class:`Code` object.
4. Optionally, insert additional data into the namespace of the :class:`Code`
object.
5. Use the :class:`Code` object via ``code(name1=val1, name2=val2)`` where the
``name=val`` are to be inserted into the namespace before the code is called.
This process is very clearly illustrated in the source code for
:class:`CodeGenStateUpdater`.
Structure of the package
------------------------
The following are the main elements of the code generation package:
:class:`Code`
This is the output of the code generation package, a compilable/compiled
code fragment, along with a namespace in which it is executed.
:class:`Language`
Used to specify which language the output should have.
:class:`CodeItem`
Before code is converted into a specific language, it is stored in a
language-invariant format consisting of :class:`CodeItem` objects, which
can in turn contain other :class:`CodeItem` objects. The main derived
classes from this are :class:`Block` and :class:`Statement`. The first can
contain a series of statements, or it can be a for loop, an if block, etc.
A :class:`Statement` can be a :class:`MathematicalStatement` or
:class:`CodeStatement`. The former is for things like ``x=y*z`` and the
latter for things like ``x = arr[index]``.
:class:`Symbol`, :func:`resolve`
A :class:`CodeItem` with unresolved dependencies needs to be resolved by
the function :func:`resolve`. Each unresolved depdendency should correspond
to a :class:`Symbol` which knows how to modify a :class:`CodeItem` in order
to resolve itself. For example, a :class:`NeuronGroupStateVariableSymbol`
will insert the :class:`~brian.NeuronGroup` state variable value into the
namespace, create a new array symbol like ``__arr_V`` for symbol ``V``,
and resolve itself either by doing nothing (in Python, as the variable is
already vectorised), or by introducing a loop (in C++), or by setting the
index variable as the kernel thread (for GPU). For more details, see
the section on resolution below.
:func:`make_integration_step`, :func:`euler`, :func:`rk2`, :func:`exp_euler`
Numerical integration schemes, each integration scheme (such as
:func:`euler`) converts a set of differential equations into a sequence of
:class:`MathematicalStatement` objects comprising an integration step.
:class:`CodeGenStateUpdater`, :class:`CodeGenThreshold`, :class:`CodeGenReset`, :class:`CodeGenConnection`
Brian objects using code generation.
Resolution process
==================
Example
-------
We start with a worked example. Consider the statement::
V = V+1
Here ``V`` is a :class:`NeuronGroup` state variable. We wish to transform this
into code that can be executed. In the case of Python, the output would look
like::
_neuron_index = slice(None)
V = _arr_V[_neuron_index]
_arr_V[_neuron_index] = V+1
The symbol ``_arr_V`` would be added directly to the namespace.
In the case of C++ it would look like::
for(int _neuron_index=0; _neuron_index<_len__arr_V; _neuron_index++)
{
double &V = _arr_V[_neuron_index];
V = V+1;
}
Here the symbols ``_arr_V`` and `_len__arr_V`` would be added to the namespace.
The reason for these complicated names is to do with making the code as
generic as possible, not introducing namespace clashes (symbols starting with
``_`` are reserved), etc.
The way the process works is we start with the statement ``V=V+1`` and a
:class:`Symbol` object with name ``V``, specifically a
:class:`NeuronGroupStateVariableSymbol`. The statement ``V=V+1`` depends on
``V`` with both a :class:`Read` and :class:`Write` dependency. We therefore
have to 'resolve' the symbol ``V``. To do this we call the method
:meth:`~ArraySymbol.resolve` on ``V``.
In the case of Python, this gives us::
V = _arr_V[_neuron_index]
_arr_V[_neuron_index] = V+1
It adds ``_arr_V`` to the namespace, and creates a dependency on
``_neuron_index``. The reason that ``V=V+1`` is translated to
``_arr_V[_neuron_index] = V+1`` is that on the left hand side we have a write
variable, and on the right hand side we have a read variable. In Python, when
vectorising, we have no choice but to give the underlying array with its slice
when writing to an array. However, at this point the code generation framework
doesn't know what ``_neuron_index`` will be, so it could be, for example, an
array of indices. In this case, suppose we did ``V*V`` it would be more
efficient to compute ``V=_arr_V[_neuron_index]`` and then compute ``V*V`` than
to compute ``_arr_V[_neuron_index]*_arr_V[_neuron_index]``, and in the case
where ``_neuron_index=slice(None)`` it is no slower, so we always do this.
In the case of C++, the first resolution step gives us::
double &V = _arr_V[_neuron_index];
V = V+1;
For the second resolution step, we need to resolve ``_neuron_index``, which
is a symbol of type :class:`SliceIndex`, telling us that ``_neuron_index``
varies over all neurons. Note that we could also have ``_neuron_index`` being
an :class:`ArrayIndex`, for examples ``spikes``, and then this could be used
for a reset operation (we would iterate only over those indices of neurons
which had spiked). Here though, we iterate over all neurons. In Python, calling
the :meth:`~ArrayIndex.resolve` method of ``_neuron_index`` gives us::
_neuron_index = slice(None)
V = _arr_V[_neuron_index]
_arr_V[_neuron_index] = V+1
and in C++::
for(int _neuron_index=0; _neuron_index<_len__arr_V; _neuron_index++)
{
double &V = _arr_V[_neuron_index];
V = V+1;
}
In both cases, the ``_neuron_index`` symbol is resolved and the process is
complete.
Note that we have actually mixed two stages here, the stage of generating a
structured representation of the code using :class:`CodeItem` objects, and the
stage of generating code strings using :meth:`CodeItem.convert_to`. In fact,
the converting of, for example, ``V`` to ``_arr_V[_neuron_index]`` only happens
at the second stage.
:func:`resolve`
---------------
The first stage, acting on the structured representation of nested
:class:`CodeItem` objects is resolved using the function :func:`resolve`. This
calls :meth:`Symbol.resolve` for each of the symbols in turn. The resolution
order is determined by an optimal efficiency algorithm, see the reference
documentation for :func:`resolve` for the full algorithm description.
:meth:`Symbol.resolve` can do an arbitrary transformation of the input
:class:`CodeItem`, but typically it will either do something like::
load()
item
save()
Or something like::
for name in array:
item
See the reference documentation for :meth:`Symbol.resolve`, and the
documentation for the most important symbols:
* :class:`SliceIndex`
* :class:`ArraySymbol`
* :class:`ArrayIndex`
* :class:`NeuronGroupStateVariableSymbol`
:meth:`~CodeItem.convert_to`
----------------------------
This step is relatively straightforward, each :class:`CodeItem` object has its
``convert_to`` method called iteratively. The important one is in
:class:`MathematicalStatement`, where the left hand side usage is replaced by
:meth:`Symbol.write` and the right hand side usage is replaced by
:meth:`Symbol.read`. In addition, at this stage the syntax of mathematical
statements is corrected, e.g. Python's ``x**y`` is replaced by C++'s
``pow(x,y)`` using ``sympy``.
Code generation in Brian
========================
The four objects used for code generation in Brian are:
:class:`CodeGenStateUpdater`
Used for numerical integration, see above and reference documentation.
:class:`CodeGenThreshold`
Used for computing a threshold function.
:class:`CodeGenReset`
Used for computing post-spike reset.
:class:`CodeGenConnection`
Used for synaptic propagation.
Numerical integration
---------------------
An integration scheme is generated from an :class:`Equations` object using the
:func:`make_integration_step` function. See reference documentation for that
function for details.
This is carried out by :class:`CodeGenStateUpdater`, which can be used as a
Brian :class:`brian.StateUpdater` object in :class:`brian.NeuronGroup`.
As an example, for Euler integration, the differential equations::
dx/dt = expr
are separated by :class:`Equations` into variable ``x`` with expression
``expr``. This then becomes::
_temp_x := expr
x += _temp_x*dt
This can then be resolved by the code generation mechanisms described already.
Synaptic propagation
--------------------
TODO: synaptic propagation, including docstrings and code comments
NOTE: GPU functionality not included for synaptic propagation yet.
GPU
===
.. warning::
GPU code is highly transitional, many details may change in the future.
GPU code is handled by five classes:
:class:`GPULanguage` (derived from :class:`CLanguage`)
Identifies the language as CUDA, and stores a singleton
:class:`GPUManager` object which is used to manage the GPU.
:class:`GPUCode` (derived from :class:`Code`)
Returned from the code generation process, but mostly just acts as a proxy
to :class:`GPUManager`.
:class:`GPUKernel`
Handles the final stage of taking a partially generated kernel (without the
vectorisation over threads) and computing the final kernel (using
vectorisation over threads). Also adds data to the
:class:`GPUSymbolMemoryManager`.
:class:`GPUManager`
Manages the GPU generally. Stores a set of kernels (:class:`GPUKernel`) and
manages memory via :class:`GPUSymbolMemoryManager`. Handles joining the
memory management code and kernel code into a single source file, and
compiling it.
:class:`GPUSymbolMemoryManager`
Handles allocation of GPU memory for symbols.
For more details, see the reference documentation for the classes in the order
above.
Note that :class:`CodeGenConnection` is the only code generation version of a
Brian class which is not GPU enabled at present.
Extending code generation
=========================
To extend code generation, you will probably need to add new :class:`Symbol`
classes. Read the documentation for this class to start, and the documentation
for the most important symbols:
* :class:`SliceIndex`
* :class:`ArraySymbol`
* :class:`ArrayIndex`
* :class:`NeuronGroupStateVariableSymbol`
See also :class:`CodeItem`, particularly the process described in
:meth:`CodeItem.generate`.
Inheritance diagrams
====================
The overall structure of the classes in the code generation package are
included below, for reference.
Languages
---------
.. inheritance-diagram::
brian.experimental.codegen2.languages
GPULanguage
:parts: 2
Code objects
------------
.. inheritance-diagram::
brian.experimental.codegen2.codeobject
GPUCode
:parts: 2
Code items
----------
.. inheritance-diagram::
brian.experimental.codegen2.codeitems
brian.experimental.codegen2.statements
brian.experimental.codegen2.blocks
:parts: 2
Equations
---------
.. inheritance-diagram::
brian.experimental.codegen2.equations
brian.experimental.codegen2.expressions
:parts: 2
Symbols
-------
.. inheritance-diagram::
brian.experimental.codegen2.symbols
:parts: 2
Resolution and code output
--------------------------
.. inheritance-diagram::
brian.experimental.codegen2.dependencies
brian.experimental.codegen2.formatting
:parts: 2
Integration
-----------
.. inheritance-diagram::
brian.experimental.codegen2.integration
:parts: 2
GPU
---
.. inheritance-diagram::
brian.experimental.codegen2.gpu.management
:parts: 2
Brian objects
-------------
Connection
~~~~~~~~~~
.. inheritance-diagram::
brian.experimental.codegen2.connection
:parts: 3
Reset
~~~~~
.. inheritance-diagram::
brian.experimental.codegen2.reset
:parts: 3
State updater
~~~~~~~~~~~~~
.. inheritance-diagram::
brian.experimental.codegen2.stateupdater
:parts: 3
Threshold
~~~~~~~~~
.. inheritance-diagram::
brian.experimental.codegen2.threshold
:parts: 3
Reference
=========
blocks
------
.. autoclass:: Block
:members:
:undoc-members:
.. autoclass:: ControlBlock
:members:
:undoc-members:
.. autoclass:: ForBlock
:members:
:undoc-members:
.. autoclass:: PythonForBlock
:members:
:undoc-members:
.. autoclass:: CForBlock
:members:
:undoc-members:
.. autoclass:: IfBlock
:members:
:undoc-members:
.. autoclass:: PythonIfBlock
:members:
:undoc-members:
.. autoclass:: CIfBlock
:members:
:undoc-members:
codeitems
---------
.. autoclass:: CodeItem
:members:
:undoc-members:
codeobject
----------
.. autoclass:: Code
:members:
:undoc-members:
.. autoclass:: PythonCode
:members:
:undoc-members:
.. autoclass:: CCode
:members:
:undoc-members:
connection
----------
.. autoclass:: CodeGenConnection
:members:
:undoc-members:
.. autoclass:: DenseMatrixSymbols
:members:
:undoc-members:
.. autoclass:: SparseMatrixSymbols
:members:
:undoc-members:
dependencies
------------
.. autoclass:: Dependency
:members:
:undoc-members:
.. autoclass:: Read
:members:
:undoc-members:
.. autoclass:: Write
:members:
:undoc-members:
.. autofunction:: get_read_or_write_dependencies
equations
---------
.. autofunction:: freeze_with_equations
.. autofunction:: frozen_equations
expressions
-----------
.. autoclass:: Expression
:members:
:undoc-members:
formatting
----------
.. autofunction:: word_substitute
.. autofunction:: flattened_docstring
.. autofunction:: indent_string
.. autofunction:: get_identifiers
.. autofunction:: strip_empty_lines
gpu
---
.. warning::
GPU code is highly transitional, many details may change in the future.
.. autoclass:: GPUKernel
:members:
:undoc-members:
.. autoclass:: GPUManager
:members:
:undoc-members:
.. autoclass:: GPUSymbolMemoryManager
:members:
:undoc-members:
.. autoclass:: GPUCode
:members:
:undoc-members:
.. autoclass:: GPULanguage
:members:
:undoc-members:
integration
-----------
.. autoclass:: EquationsContainer
:members:
:undoc-members:
.. autofunction:: make_integration_step
.. autofunction:: euler
.. autofunction:: rk2
.. autofunction:: exp_euler
languages
---------
.. autoclass:: Language
:members:
:undoc-members:
.. autoclass:: PythonLanguage
:members:
:undoc-members:
.. autoclass:: CLanguage
:members:
:undoc-members:
makeintegrator
--------------
.. autofunction:: make_c_integrator
reset
-----
.. autoclass:: CodeGenReset
:members:
:undoc-members:
resolution
----------
.. autofunction:: resolve
statements
----------
.. autoclass:: Statement
:members:
:undoc-members:
.. autoclass:: CodeStatement
:members:
:undoc-members:
.. autoclass:: CDefineFromArray
:members:
:undoc-members:
.. autoclass:: MathematicalStatement
:members:
:undoc-members:
.. autofunction:: statements_from_codestring
.. autofunction:: c_data_type
stateupdater
------------
.. autoclass:: CodeGenStateUpdater
:members:
:undoc-members:
symbols
-------
.. autoclass:: Symbol
:members:
:undoc-members:
.. autoclass:: RuntimeSymbol
:members:
:undoc-members:
.. autoclass:: ArraySymbol
:members:
:undoc-members:
.. autoclass:: NeuronGroupStateVariableSymbol
:members:
:undoc-members:
.. autoclass:: SliceIndex
:members:
:undoc-members:
.. autoclass:: ArrayIndex
:members:
:undoc-members:
.. autofunction:: language_invariant_symbol_method
.. autofunction:: get_neuron_group_symbols
threshold
---------
.. autoclass:: CodeGenThreshold
:members:
:undoc-members: