brian-team/brian2 — pull request #110 by mstimberg (2013-09-09 15:16:23+00:00)
Title: Finalize functions

Ok, this branch is not quite ready to merge yet (mostly because it is lacking a lot of documentation) but before I do the final polishing, I'd be happy to get some feedback (this includes you @achilleas-k :) ).

Here's how things work:
A `Function` object describes a function in general terms: What are the input and output units, which Python function implements it and which sympy function (if any) is equivalent. That the object already contains a reference to the Python function is a bit of a break of the description vs. implementation distinction but we need it so that for example `from numpy import exp` does not trigger warnings about conflicting definitions of the `exp` function.

Each `Function` object has a dictionary `implementation`, mapping the name of a language (e.g. `cpp`) or the a `CodeObjectClass` (e.g. `WeaveCodeObject`) to a `FunctionImplementation` object. This object is a simple container for (all optional) attributes: name, code and namespace. Specifying a name means that the name is translated into the given name (e.g. the C implementation of `arcsin` translates the name to `asin`), the code is handled as part of the language (for Python, the "code" corresponds to a Python function, for C++ to a dictionary of strings) and values in the namespace are added to the namespace when a `CodeObject` is created.

Adding support for function in languages is therefore quite straightforward, see https://github.com/brian-team/brian2/blob/finalize_functions/brian2/codegen/languages/cpp_lang.py#L237 for the C++ specifications, for example. Function implementations are first checked for the `CodeObject` class and a general language implementation is used as the fallback. This is currently only used for the `randn` function which uses numpy's randn function when using weave (https://github.com/brian-team/brian2/blob/finalize_functions/brian2/codegen/runtime/weave_rt/weave_rt.py#L85) and a numpy-independent pure C++ function for non-weave C++ implementations (e.g. the future C++ standalone).

If a user wants to use a non-default function in their code, they can specify it themselves by either:
* simply writing a function in the usual Python way and using the `@check_units` decorator: The function will only be available for the numpy runtime, though.
* Wrapping a Python function in a `Function` object and adding implementations for other targets
* use the `@make_function` wrapper and supply the code for other targets in the decorator

Now, this is all working so far but there are some points that are still worth discussing:

* The biggest issue is the handling of user-defined functions and the `check_units` decorator: If a user defines a function, they expect it to work as a normal function including unit handling. On the other hand, when running the code this function will not receive any units and it also shouldn't return any. Furthermore, it shouldn't internally use units since that slows things down (it could also lead to unit errors but we can switch the checking off). From the implementation side the cleanest approach would be the following, I think: The check_units decorator makes the function check the units of its arguments *and then strips them away*. It also attaches the given "result unit" to the result. This would mean that for use outside of a run, units would work in the usual way but during a run, no units would be involved. A disadvantage is that it can't use units internally and the user would have to take care of writing `float(ms)` etc. Any ideas/thoughts?

Small issues:

* In the implementations dictionary, I'm using a string for the language but a class object for the `CodeObject` class -- should we add a class attribute with the name to `CodeObject`? And we should then probably turn the `runtime_targets` dictionary into a list or set and just check the attributes of its content.
* The whole `CodeObject`/`Language`/translate system is a bit convoluted and passes a lot of arguments around instead of storing them in an object -- should we refactor this a bit?

Oh, BTW: The branch also contains a `TimedArray` implementation. Since we want it to work for numpy and C++ it's less flexible than the Brian1 version: You can only use a fixed dt (not an arbitrary array of times) and only a 1d array (i.e. you cannot return different values for different neurons). We could handle these cases by specific implementations later, I guess. Probably the cleanest would be to have `TimedArray1D`, `TimedArray2D` etc. and a `timed_array` function that choses the right class depending on the attributes or something like that?