.. 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: