diff --git a/brian2/codegen/__init__.py b/brian2/codegen/__init__.py index 3c3606ece..8a9bd2f30 100644 --- a/brian2/codegen/__init__.py +++ b/brian2/codegen/__init__.py @@ -2,3 +2,5 @@ from .functions import * from .statements import * from .translation import * +from .runtime import * +from ._prefs import * diff --git a/brian2/codegen/_prefs.py b/brian2/codegen/_prefs.py new file mode 100644 index 000000000..d0f37c0d8 --- /dev/null +++ b/brian2/codegen/_prefs.py @@ -0,0 +1,23 @@ +from brian2.core.preferences import brian_prefs, BrianPreference + +# Preferences +brian_prefs.register_preferences( + 'codegen', + 'Code generation preferences', + target = BrianPreference( + default='numpy', + docs=''' + Default target for code generation. + + Can be a string, in which case it should be one of: + + * `'numpy'` by default because this works on all platforms, but may not + be maximally efficient. + * `'weave`' uses ``scipy.weave`` to generate and compile C++ code, + should work anywhere where ``gcc`` is installed and available at the + command line. + + Or it can be a ``CodeObject`` class. + ''', + ), + ) diff --git a/brian2/codegen/codeobject.py b/brian2/codegen/codeobject.py new file mode 100644 index 000000000..b84a5fded --- /dev/null +++ b/brian2/codegen/codeobject.py @@ -0,0 +1,176 @@ +import functools + +from brian2.core.specifiers import (ArrayVariable, Value, + AttributeValue, Subexpression) +from .functions.base import Function +from brian2.core.preferences import brian_prefs, BrianPreference +from brian2.utils.logger import get_logger +from .translation import translate +from .runtime.targets import runtime_targets + +__all__ = ['CodeObject', + 'create_codeobject', + 'get_codeobject_template', + ] + +logger = get_logger(__name__) + + +def get_default_codeobject_class(): + ''' + Returns the default `CodeObject` class from the preferences. + ''' + codeobj_class = brian_prefs['codegen.target'] + if isinstance(codeobj_class, str): + try: + codeobj_class = runtime_targets[codeobj_class] + except KeyError: + raise ValueError("Unknown code generation target: %s, should be " + " one of %s"%(codeobj_class, runtime_targets.keys())) + return codeobj_class + + +def prepare_namespace(namespace, specifiers): + namespace = dict(namespace) + # Add variables referring to the arrays + arrays = [] + for value in specifiers.itervalues(): + if isinstance(value, ArrayVariable): + arrays.append((value.arrayname, value.get_value())) + namespace.update(arrays) + + return namespace + + +def create_codeobject(name, abstract_code, namespace, specifiers, template_name, + codeobj_class=None, indices=None, template_kwds=None): + ''' + The following arguments keywords are passed to the template: + + * code_lines coming from translation applied to abstract_code, a list + of lines of code, given to the template as ``code_lines`` keyword. + * ``template_kwds`` dict + * ``kwds`` coming from `translate` function overwrite those in + ``template_kwds`` (but you should ensure there are no name + clashes. + ''' + if indices is None: # TODO: Do we ever create code without any index? + indices = {} + + if template_kwds is None: + template_kwds = dict() + else: + template_kwds = template_kwds.copy() + + if codeobj_class is None: + codeobj_class = get_default_codeobject_class() + + template = get_codeobject_template(template_name, + codeobj_class=codeobj_class) + + namespace = prepare_namespace(namespace, specifiers) + + logger.debug(name + " abstract code:\n" + abstract_code) + innercode, kwds = translate(abstract_code, specifiers, namespace, + brian_prefs['core.default_scalar_dtype'], + codeobj_class.language, indices) + template_kwds.update(kwds) + logger.debug(name + " inner code:\n" + str(innercode)) + code = template(innercode, **template_kwds) + logger.debug(name + " code:\n" + str(code)) + + specifiers.update(indices) + codeobj = codeobj_class(code, namespace, specifiers) + codeobj.compile() + return codeobj + + +def get_codeobject_template(name, codeobj_class=None): + ''' + Returns the `CodeObject` template ``name`` from the default or given class. + ''' + if codeobj_class is None: + codeobj_class = get_default_codeobject_class() + return getattr(codeobj_class.templater, name) + + +class CodeObject(object): + ''' + Executable code object. + + The ``code`` can either be a string or a + `brian2.codegen.templates.MultiTemplate`. + + After initialisation, the code is compiled with the given namespace + using ``code.compile(namespace)``. + + Calling ``code(key1=val1, key2=val2)`` executes the code with the given + variables inserted into the namespace. + ''' + + #: The `Language` used by this `CodeObject` + language = None + + def __init__(self, code, namespace, specifiers): + self.code = code + self.compile_methods = self.get_compile_methods(specifiers) + self.namespace = namespace + self.specifiers = specifiers + + # Specifiers can refer to values that are either constant (e.g. dt) + # or change every timestep (e.g. t). We add the values of the + # constant specifiers here and add the names of non-constant specifiers + # to a list + + # A list containing tuples of name and a function giving the value + self.nonconstant_values = [] + + for name, spec in self.specifiers.iteritems(): + if isinstance(spec, Value): + if isinstance(spec, AttributeValue): + self.nonconstant_values.append((name, spec.get_value)) + if not spec.scalar: + self.nonconstant_values.append(('_num' + name, + spec.get_len)) + elif not isinstance(spec, Subexpression): + value = spec.get_value() + self.namespace[name] = value + # if it is a type that has a length, add a variable called + # '_num'+name with its length + if not spec.scalar: + self.namespace['_num' + name] = spec.get_len() + + def get_compile_methods(self, specifiers): + meths = [] + for var, spec in specifiers.items(): + if isinstance(spec, Function): + meths.append(functools.partial(spec.on_compile, + language=self.language, + var=var)) + return meths + + def compile(self): + for meth in self.compile_methods: + meth(self.namespace) + + def __call__(self, **kwds): + # update the values of the non-constant values in the namespace + for name, func in self.nonconstant_values: + self.namespace[name] = func() + + self.namespace.update(**kwds) + + return self.run() + + def run(self): + ''' + Runs the code in the namespace. + + Returns + ------- + return_value : dict + A dictionary with the keys corresponding to the `output_variables` + defined during the call of `Language.code_object`. + ''' + raise NotImplementedError() + diff --git a/brian2/codegen/languages/__init__.py b/brian2/codegen/languages/__init__.py index 431f4fa21..22174d241 100644 --- a/brian2/codegen/languages/__init__.py +++ b/brian2/codegen/languages/__init__.py @@ -1,3 +1,3 @@ from .base import * -from .cpp import * -from .python import * +from .cpp_lang import * +from .numpy_lang import * diff --git a/brian2/codegen/languages/base.py b/brian2/codegen/languages/base.py index 34447a04a..e8566d025 100644 --- a/brian2/codegen/languages/base.py +++ b/brian2/codegen/languages/base.py @@ -2,21 +2,11 @@ Base class for languages, gives the methods which should be overridden to implement a new language. ''' -import functools -import numpy - -from brian2.core.preferences import brian_prefs from brian2.core.specifiers import (ArrayVariable, Value, AttributeValue, Subexpression) from brian2.utils.stringtools import get_identifiers -from brian2.utils.logger import get_logger - -from ..functions import Function -from ..translation import translate - -logger = get_logger(__name__) -__all__ = ['Language', 'CodeObject'] +__all__ = ['Language'] class Language(object): ''' @@ -30,8 +20,6 @@ class Language(object): # Subclasses should override this language_id = '' - # Subclasses should define a templater attribute - def translate_expression(self, expr): ''' Translate the given expression string into a string in the target @@ -41,14 +29,14 @@ def translate_expression(self, expr): def translate_statement(self, statement): ''' - Translate a single line Statement into the target language, returns + Translate a single line `Statement` into the target language, returns a string. ''' raise NotImplementedError def translate_statement_sequence(self, statements, specifiers, namespace, indices): ''' - Translate a sequence of Statements into the target language, taking + Translate a sequence of `Statement` into the target language, taking care to declare variables, etc. if necessary. Returns a pair ``(code_lines, kwds)`` where ``code`` is a list of the @@ -57,66 +45,6 @@ def translate_statement_sequence(self, statements, specifiers, namespace, indice ''' raise NotImplementedError - def create_codeobj(self, name, abstract_code, namespace, specifiers, - template, indices=None, template_kwds=None): - ''' - The following arguments keywords are passed to the template: - - * code_lines coming from translation applied to abstract_code, a list - of lines of code, given to the template as ``code_lines`` keyword. - * ``template_kwds`` dict - * ``kwds`` coming from `translate` function overwrite those in - ``template_kwds`` (but you should ensure there are no name - clashes. - ''' - if indices is None: # TODO: Do we ever create code without any index? - indices = {} - if template_kwds is None: - template_kwds = dict() - else: - template_kwds = template_kwds.copy() - - namespace = self.prepare_namespace(namespace, specifiers) - - logger.debug(name + " abstract code:\n" + abstract_code) - innercode, kwds = translate(abstract_code, specifiers, namespace, - brian_prefs['core.default_scalar_dtype'], - self, indices) - template_kwds.update(kwds) - logger.debug(name + " inner code:\n" + str(innercode)) - code = template(innercode, **template_kwds) - logger.debug(name + " code:\n" + str(code)) - - specifiers.update(indices) - codeobj = self.code_object(code, namespace, specifiers) - codeobj.compile() - return codeobj - - def prepare_namespace(self, namespace, specifiers): - namespace = dict(namespace) - # Add variables referring to the arrays - arrays = [] - for value in specifiers.itervalues(): - if isinstance(value, ArrayVariable): - arrays.append((value.arrayname, value.get_value())) - namespace.update(arrays) - - return namespace - - def code_object(self, code, namespace, specifiers): - ''' - Return an executable code object from the given code string. - ''' - raise NotImplementedError - - def compile_methods(self, specifiers): - meths = [] - for var, spec in specifiers.items(): - if isinstance(spec, Function): - meths.append(functools.partial(spec.on_compile, language=self, - var=var)) - return meths - def array_read_write(self, statements, specifiers): ''' Helper function, gives the set of ArrayVariables that are read from and @@ -135,101 +63,3 @@ def array_read_write(self, statements, specifiers): read = set(var for var, spec in specifiers.items() if isinstance(spec, ArrayVariable) and var in read) write = set(var for var, spec in specifiers.items() if isinstance(spec, ArrayVariable) and var in write) return read, write - - @property - def template_state_update(self): - return self.templater.stateupdate - - @property - def template_reset(self): - return self.templater.reset - - @property - def template_threshold(self): - return self.templater.threshold - - @property - def template_synapses(self): - return self.templater.synapses - - @property - def template_synapses_create(self): - return self.templater.synapses_create - - @property - def template_state_variable_indexing(self): - return self.templater.state_variable_indexing - - @property - def template_lumped_variable(self): - return self.templater.lumped_variable - - -class CodeObject(object): - ''' - Executable code object, returned by Language - - Code object is initialised by Language object, typically just by doing - ``CodeObject(code)``. The ``code`` can either be a string or a - `brian2.codegen.languages.templates.MultiTemplate`. - - After initialisation, the code is compiled with the given namespace - using ``code.compile(namespace)``. - - Calling ``code(key1=val1, key2=val2)`` executes the code with the given - variables inserted into the namespace. - ''' - def __init__(self, code, namespace, specifiers, compile_methods=[]): - self.code = code - self.compile_methods = compile_methods - self.namespace = namespace - self.specifiers = specifiers - - # Specifiers can refer to values that are either constant (e.g. dt) - # or change every timestep (e.g. t). We add the values of the - # constant specifiers here and add the names of non-constant specifiers - # to a list - - # A list containing tuples of name and a function giving the value - self.nonconstant_values = [] - - for name, spec in self.specifiers.iteritems(): - if isinstance(spec, Value): - if isinstance(spec, AttributeValue): - self.nonconstant_values.append((name, spec.get_value)) - if not spec.scalar: - self.nonconstant_values.append(('_num' + name, - spec.get_len)) - elif not isinstance(spec, Subexpression): - value = spec.get_value() - self.namespace[name] = value - # if it is a type that has a length, add a variable called - # '_num'+name with its length - if not spec.scalar: - self.namespace['_num' + name] = spec.get_len() - - - def compile(self): - for meth in self.compile_methods: - meth(self.namespace) - - def __call__(self, **kwds): - # update the values of the non-constant values in the namespace - for name, func in self.nonconstant_values: - self.namespace[name] = func() - - self.namespace.update(**kwds) - - return self.run() - - def run(self): - ''' - Runs the code in the namespace. - - Returns - ------- - return_value : dict - A dictionary with the keys corresponding to the `output_variables` - defined during the call of `Language.code_object`. - ''' - raise NotImplementedError() diff --git a/brian2/codegen/languages/cpp/__init__.py b/brian2/codegen/languages/cpp/__init__.py deleted file mode 100644 index 6da314410..000000000 --- a/brian2/codegen/languages/cpp/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .cpp import * diff --git a/brian2/codegen/languages/cpp/cpp.py b/brian2/codegen/languages/cpp_lang.py similarity index 68% rename from brian2/codegen/languages/cpp/cpp.py rename to brian2/codegen/languages/cpp_lang.py index 1e25878ef..dad6e3cba 100644 --- a/brian2/codegen/languages/cpp/cpp.py +++ b/brian2/codegen/languages/cpp_lang.py @@ -1,5 +1,5 @@ ''' -TODO: restrict keyword optimisations +TODO: use preferences to get arguments to Language ''' import itertools import os @@ -10,18 +10,14 @@ from brian2.codegen.functions.base import Function from brian2.utils.logger import get_logger -from ..base import Language, CodeObject -from ..templates import LanguageTemplater +from .base import Language from brian2.parsing.rendering import CPPNodeRenderer +from brian2.core.preferences import brian_prefs, BrianPreference + logger = get_logger(__name__) -try: - from scipy import weave -except ImportError as ex: - logger.warn('Importing scipy.weave failed: %s' % ex) - weave = None -__all__ = ['CPPLanguage', 'CPPCodeObject', +__all__ = ['CPPLanguage', 'c_data_type', ] @@ -57,25 +53,25 @@ def c_data_type(dtype): return dtype -class CPPLanguage(Language): - ''' - Initialisation arguments: - - ``compiler`` - The distutils name of the compiler. - ``extra_compile_args`` - Extra compilation arguments, e.g. for optimisation. Best performance is - often gained by using - ``extra_compile_args=['-O3', '-ffast-math', '-march=native']`` - however the ``'-march=native'`` is not compatible with all versions of - gcc so is switched off by default. - ``restrict`` - The keyword used for the given compiler to declare pointers as - restricted (different on different compilers). - ``flush_denormals`` - Adds code to flush denormals to zero, but the code is gcc and - architecture specific, so may not compile on all platforms, therefore - it is off by default. The code, for reference is:: +# Preferences +brian_prefs.register_preferences( + 'codegen.languages.cpp', + 'C++ codegen preferences', + restrict_keyword = BrianPreference( + default='__restrict__', + docs=''' + The keyword used for the given compiler to declare pointers as restricted. + + This keyword is different on different compilers, the default is for gcc. + ''', + ), + flush_denormals = BrianPreference( + default=False, + docs=''' + Adds code to flush denormals to zero. + + The code is gcc and architecture specific, so may not compile on all + platforms. The code, for reference is:: #define CSR_FLUSH_TO_ZERO (1 << 15) unsigned csr = __builtin_ia32_stmxcsr(); @@ -83,7 +79,15 @@ class CPPLanguage(Language): __builtin_ia32_ldmxcsr(csr); Found at ``_. - + ''', + ), + ) + + +class CPPLanguage(Language): + ''' + C++ language + C++ code templates should provide Jinja2 macros with the following names: ``main`` @@ -104,15 +108,9 @@ class CPPLanguage(Language): language_id = 'cpp' - templater = LanguageTemplater(os.path.join(os.path.split(__file__)[0], - 'templates')) - - def __init__(self, compiler='gcc', extra_compile_args=['-w', '-O3', '-ffast-math'], - restrict='__restrict__', flush_denormals=False): - self.compiler = compiler - self.extra_compile_args = extra_compile_args - self.restrict = restrict + ' ' - self.flush_denormals = flush_denormals + def __init__(self): + self.restrict = brian_prefs['codegen.languages.cpp.restrict_keyword'] + ' ' + self.flush_denormals = brian_prefs['codegen.languages.cpp.flush_denormals'] def translate_expression(self, expr): return CPPNodeRenderer().render_expr(expr).strip() @@ -209,14 +207,6 @@ def translate_statement_sequence(self, statements, specifiers, namespace, indice 'denormals_code_lines': stripped_deindented_lines(self.denormals_to_zero_code()), }) - def code_object(self, code, namespace, specifiers): - return CPPCodeObject(code, - namespace, - specifiers, - compile_methods=self.compile_methods(namespace), - compiler=self.compiler, - extra_compile_args=self.extra_compile_args) - def denormals_to_zero_code(self): if self.flush_denormals: return ''' @@ -227,28 +217,3 @@ def denormals_to_zero_code(self): ''' else: return '' - - -class CPPCodeObject(CodeObject): - ''' - C++ code object - - The ``code`` should be a `~brian2.codegen.languages.templates.MultiTemplate` - object with two macros defined, ``main`` (for the main loop code) and - ``support_code`` for any support code (e.g. function definitions). - ''' - def __init__(self, code, namespace, specifiers, compile_methods=[], - compiler='gcc', extra_compile_args=['-O3']): - super(CPPCodeObject, self).__init__(code, - namespace, - specifiers, - compile_methods=compile_methods) - self.compiler = compiler - self.extra_compile_args = extra_compile_args - - def run(self): - return weave.inline(self.code.main, self.namespace.keys(), - local_dict=self.namespace, - support_code=self.code.support_code, - compiler=self.compiler, - extra_compile_args=self.extra_compile_args) diff --git a/brian2/codegen/languages/python/python.py b/brian2/codegen/languages/numpy_lang.py similarity index 64% rename from brian2/codegen/languages/python/python.py rename to brian2/codegen/languages/numpy_lang.py index ecbd5b3c2..ad631d8fa 100644 --- a/brian2/codegen/languages/python/python.py +++ b/brian2/codegen/languages/numpy_lang.py @@ -1,20 +1,19 @@ import os -import numpy as np - -from ..base import Language, CodeObject -from ..templates import LanguageTemplater +from .base import Language from brian2.parsing.rendering import NumpyNodeRenderer -__all__ = ['PythonLanguage', 'PythonCodeObject'] - +__all__ = ['NumpyLanguage'] -class PythonLanguage(Language): - language_id = 'python' +class NumpyLanguage(Language): + ''' + Numpy language + + Essentially Python but vectorised. + ''' - templater = LanguageTemplater(os.path.join(os.path.split(__file__)[0], - 'templates')) + language_id = 'numpy' def translate_expression(self, expr): return NumpyNodeRenderer().render_expr(expr).strip() @@ -63,21 +62,3 @@ def translate_statement_sequence(self, statements, specifiers, namespace, indice line = line + ' = ' + var lines.append(line) return lines, {} - - def code_object(self, code, namespace, specifiers): - # TODO: This should maybe go somewhere else - namespace['logical_not'] = np.logical_not - return PythonCodeObject(code, namespace, specifiers, - self.compile_methods(namespace)) - - -class PythonCodeObject(CodeObject): - def compile(self): - super(PythonCodeObject, self).compile() - self.compiled_code = compile(self.code, '(string)', 'exec') - - def run(self): - exec self.compiled_code in self.namespace - # output variables should land in the variable name _return_values - if '_return_values' in self.namespace: - return self.namespace['_return_values'] diff --git a/brian2/codegen/languages/python/__init__.py b/brian2/codegen/languages/python/__init__.py deleted file mode 100644 index 5b57e719a..000000000 --- a/brian2/codegen/languages/python/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .python import * diff --git a/brian2/codegen/runtime/__init__.py b/brian2/codegen/runtime/__init__.py new file mode 100644 index 000000000..b424114ed --- /dev/null +++ b/brian2/codegen/runtime/__init__.py @@ -0,0 +1,2 @@ +from .numpy_rt import * +from .weave_rt import * diff --git a/brian2/codegen/runtime/numpy_rt/__init__.py b/brian2/codegen/runtime/numpy_rt/__init__.py new file mode 100644 index 000000000..308766aad --- /dev/null +++ b/brian2/codegen/runtime/numpy_rt/__init__.py @@ -0,0 +1 @@ +from .numpy_rt import * diff --git a/brian2/codegen/runtime/numpy_rt/numpy_rt.py b/brian2/codegen/runtime/numpy_rt/numpy_rt.py new file mode 100644 index 000000000..e89a3352c --- /dev/null +++ b/brian2/codegen/runtime/numpy_rt/numpy_rt.py @@ -0,0 +1,36 @@ +import os +import numpy as np + +from ...codeobject import CodeObject +from ...templates import Templater +from ...languages.numpy_lang import NumpyLanguage +from ..targets import runtime_targets + +__all__ = ['NumpyCodeObject'] + +class NumpyCodeObject(CodeObject): + ''' + Execute code using Numpy + + Default for Brian because it works on all platforms. + ''' + templater = Templater(os.path.join(os.path.split(__file__)[0], + 'templates')) + language = NumpyLanguage() + + def __init__(self, code, namespace, specifiers): + # TODO: This should maybe go somewhere else + namespace['logical_not'] = np.logical_not + CodeObject.__init__(self, code, namespace, specifiers) + + def compile(self): + super(NumpyCodeObject, self).compile() + self.compiled_code = compile(self.code, '(string)', 'exec') + + def run(self): + exec self.compiled_code in self.namespace + # output variables should land in the variable name _return_values + if '_return_values' in self.namespace: + return self.namespace['_return_values'] + +runtime_targets['numpy'] = NumpyCodeObject diff --git a/brian2/codegen/languages/python/templates/lumped_variable.py_ b/brian2/codegen/runtime/numpy_rt/templates/lumped_variable.py_ similarity index 100% rename from brian2/codegen/languages/python/templates/lumped_variable.py_ rename to brian2/codegen/runtime/numpy_rt/templates/lumped_variable.py_ diff --git a/brian2/codegen/languages/python/templates/monitor.py_ b/brian2/codegen/runtime/numpy_rt/templates/monitor.py_ similarity index 100% rename from brian2/codegen/languages/python/templates/monitor.py_ rename to brian2/codegen/runtime/numpy_rt/templates/monitor.py_ diff --git a/brian2/codegen/languages/python/templates/reset.py_ b/brian2/codegen/runtime/numpy_rt/templates/reset.py_ similarity index 100% rename from brian2/codegen/languages/python/templates/reset.py_ rename to brian2/codegen/runtime/numpy_rt/templates/reset.py_ diff --git a/brian2/codegen/languages/python/templates/state_variable_indexing.py_ b/brian2/codegen/runtime/numpy_rt/templates/state_variable_indexing.py_ similarity index 100% rename from brian2/codegen/languages/python/templates/state_variable_indexing.py_ rename to brian2/codegen/runtime/numpy_rt/templates/state_variable_indexing.py_ diff --git a/brian2/codegen/languages/python/templates/stateupdate.py_ b/brian2/codegen/runtime/numpy_rt/templates/stateupdate.py_ similarity index 100% rename from brian2/codegen/languages/python/templates/stateupdate.py_ rename to brian2/codegen/runtime/numpy_rt/templates/stateupdate.py_ diff --git a/brian2/codegen/languages/python/templates/synapses.py_ b/brian2/codegen/runtime/numpy_rt/templates/synapses.py_ similarity index 100% rename from brian2/codegen/languages/python/templates/synapses.py_ rename to brian2/codegen/runtime/numpy_rt/templates/synapses.py_ diff --git a/brian2/codegen/languages/python/templates/synapses_create.py_ b/brian2/codegen/runtime/numpy_rt/templates/synapses_create.py_ similarity index 100% rename from brian2/codegen/languages/python/templates/synapses_create.py_ rename to brian2/codegen/runtime/numpy_rt/templates/synapses_create.py_ diff --git a/brian2/codegen/languages/python/templates/threshold.py_ b/brian2/codegen/runtime/numpy_rt/templates/threshold.py_ similarity index 100% rename from brian2/codegen/languages/python/templates/threshold.py_ rename to brian2/codegen/runtime/numpy_rt/templates/threshold.py_ diff --git a/brian2/codegen/runtime/targets.py b/brian2/codegen/runtime/targets.py new file mode 100644 index 000000000..228026a45 --- /dev/null +++ b/brian2/codegen/runtime/targets.py @@ -0,0 +1,5 @@ +__all__ = ['runtime_targets'] + +# This should be filled in by subpackages of runtime +runtime_targets = { + } diff --git a/brian2/codegen/runtime/weave_rt/__init__.py b/brian2/codegen/runtime/weave_rt/__init__.py new file mode 100644 index 000000000..66c1b3a8a --- /dev/null +++ b/brian2/codegen/runtime/weave_rt/__init__.py @@ -0,0 +1 @@ +from .weave_rt import * \ No newline at end of file diff --git a/brian2/codegen/languages/cpp/templates/lumped_variable.cpp b/brian2/codegen/runtime/weave_rt/templates/lumped_variable.cpp similarity index 100% rename from brian2/codegen/languages/cpp/templates/lumped_variable.cpp rename to brian2/codegen/runtime/weave_rt/templates/lumped_variable.cpp diff --git a/brian2/codegen/languages/cpp/templates/monitor.cpp b/brian2/codegen/runtime/weave_rt/templates/monitor.cpp similarity index 100% rename from brian2/codegen/languages/cpp/templates/monitor.cpp rename to brian2/codegen/runtime/weave_rt/templates/monitor.cpp diff --git a/brian2/codegen/languages/cpp/templates/reset.cpp b/brian2/codegen/runtime/weave_rt/templates/reset.cpp similarity index 100% rename from brian2/codegen/languages/cpp/templates/reset.cpp rename to brian2/codegen/runtime/weave_rt/templates/reset.cpp diff --git a/brian2/codegen/languages/cpp/templates/state_variable_indexing.cpp b/brian2/codegen/runtime/weave_rt/templates/state_variable_indexing.cpp similarity index 100% rename from brian2/codegen/languages/cpp/templates/state_variable_indexing.cpp rename to brian2/codegen/runtime/weave_rt/templates/state_variable_indexing.cpp diff --git a/brian2/codegen/languages/cpp/templates/stateupdate.cpp b/brian2/codegen/runtime/weave_rt/templates/stateupdate.cpp similarity index 100% rename from brian2/codegen/languages/cpp/templates/stateupdate.cpp rename to brian2/codegen/runtime/weave_rt/templates/stateupdate.cpp diff --git a/brian2/codegen/languages/cpp/templates/synapses.cpp b/brian2/codegen/runtime/weave_rt/templates/synapses.cpp similarity index 100% rename from brian2/codegen/languages/cpp/templates/synapses.cpp rename to brian2/codegen/runtime/weave_rt/templates/synapses.cpp diff --git a/brian2/codegen/languages/cpp/templates/synapses_create.cpp b/brian2/codegen/runtime/weave_rt/templates/synapses_create.cpp similarity index 100% rename from brian2/codegen/languages/cpp/templates/synapses_create.cpp rename to brian2/codegen/runtime/weave_rt/templates/synapses_create.cpp diff --git a/brian2/codegen/languages/cpp/templates/threshold.cpp b/brian2/codegen/runtime/weave_rt/templates/threshold.cpp similarity index 100% rename from brian2/codegen/languages/cpp/templates/threshold.cpp rename to brian2/codegen/runtime/weave_rt/templates/threshold.cpp diff --git a/brian2/codegen/runtime/weave_rt/weave_rt.py b/brian2/codegen/runtime/weave_rt/weave_rt.py new file mode 100644 index 000000000..816f5a442 --- /dev/null +++ b/brian2/codegen/runtime/weave_rt/weave_rt.py @@ -0,0 +1,58 @@ +import os + +from scipy import weave + +from ...codeobject import CodeObject +from ...templates import Templater +from ...languages.cpp_lang import CPPLanguage +from ..targets import runtime_targets + +from brian2.core.preferences import brian_prefs, BrianPreference + +__all__ = ['WeaveCodeObject'] + +# Preferences +brian_prefs.register_preferences( + 'codegen.runtime.weave', + 'Weave runtime codegen preferences', + compiler = BrianPreference( + default='gcc', + validator=lambda pref: pref=='gcc', + docs=''' + Compiler to use for weave. + ''', + ), + extra_compile_args = BrianPreference( + default=['-w', '-O3', '-ffast-math'], + docs=''' + Extra compile arguments to pass to compiler + ''', + ), + ) + + +class WeaveCodeObject(CodeObject): + ''' + Weave code object + + The ``code`` should be a `~brian2.codegen.languages.templates.MultiTemplate` + object with two macros defined, ``main`` (for the main loop code) and + ``support_code`` for any support code (e.g. function definitions). + ''' + templater = Templater(os.path.join(os.path.split(__file__)[0], + 'templates')) + language = CPPLanguage() + + def __init__(self, code, namespace, specifiers): + super(WeaveCodeObject, self).__init__(code, namespace, specifiers) + self.compiler = brian_prefs['codegen.runtime.weave.compiler'] + self.extra_compile_args = brian_prefs['codegen.runtime.weave.extra_compile_args'] + + def run(self): + return weave.inline(self.code.main, self.namespace.keys(), + local_dict=self.namespace, + support_code=self.code.support_code, + compiler=self.compiler, + extra_compile_args=self.extra_compile_args) + +runtime_targets['weave'] = WeaveCodeObject diff --git a/brian2/codegen/languages/templates.py b/brian2/codegen/templates.py similarity index 85% rename from brian2/codegen/languages/templates.py rename to brian2/codegen/templates.py index e0b742afe..a2210ad88 100644 --- a/brian2/codegen/languages/templates.py +++ b/brian2/codegen/templates.py @@ -7,11 +7,11 @@ import os import re -__all__ = ['LanguageTemplater'] +__all__ = ['Templater'] -class LanguageTemplater(object): +class Templater(object): ''' - Class to load and return all the templates a language defines. + Class to load and return all the templates a `CodeObject` defines. ''' def __init__(self, basedir): self.basedir = basedir @@ -20,11 +20,11 @@ def __init__(self, basedir): lstrip_blocks=True, ) for name in self.env.list_templates(): - template = LanguageTemplate(self.env.get_template(name)) + template = CodeObjectTemplate(self.env.get_template(name)) setattr(self, os.path.splitext(name)[0], template) -class LanguageTemplate(object): +class CodeObjectTemplate(object): def __init__(self, template): self.template = template res = self(['']) @@ -74,5 +74,5 @@ def __str__(self): if __name__=='__main__': - lt = LanguageTemplater('python/templates') - print lt.reset('a=b\nc=d') + lt = Templater('runtime/numpy_rt/templates') + print lt.reset(['a=b', 'c=d']) diff --git a/brian2/groups/group.py b/brian2/groups/group.py index ace16564d..c5b983569 100644 --- a/brian2/groups/group.py +++ b/brian2/groups/group.py @@ -12,6 +12,7 @@ from brian2.core.namespace import get_local_namespace from brian2.units.fundamentalunits import fail_for_dimension_mismatch, Unit from brian2.units.allunits import second +from brian2.codegen.codeobject import get_codeobject_template, create_codeobject from brian2.codegen.translation import analyse_identifiers from brian2.equations.unitcheck import check_units_statements from brian2.utils.logger import get_logger @@ -72,6 +73,9 @@ def __init__(self): 'attribute, or a length to automatically ' 'provide 1-d indexing')) self.indices = Indices(N) + + if not hasattr(self, 'codeobj_class'): + self.codeobj_class = None # Add a reference to the synapses to the template self.specifiers['_indices'] = ReadOnlyValue('_indices', Unit(1), @@ -187,20 +191,21 @@ def _set_with_code(self, specifier, group_indices, code, group_indices, '', # no index, self) - codeobj = create_codeobj(self, + codeobj = create_runner_codeobj(self, abstract_code, - self.language.template_reset, + 'reset', indices, additional_specifiers=additional_specifiers, additional_namespace=additional_namespace, check_units=check_units, - language=self.language) + codeobj_class=self.codeobj_class) codeobj() -def create_codeobj(group, code, template, indices, - name=None, check_units=True, additional_specifiers=None, - additional_namespace=None, language=None, template_kwds=None): +def create_runner_codeobj(group, code, template_name, indices, + name=None, check_units=True, additional_specifiers=None, + additional_namespace=None, template_kwds=None, + codeobj_class=None): ''' Create a `CodeObject` for the execution of code in the context of a `Group`. @@ -228,6 +233,8 @@ def create_codeobj(group, code, template, indices, saved in `group`. template_kwds : dict, optional A dictionary of additional information that is passed to the template. + codeobj_class : `CodeObject`, optional + The `CodeObject` class to create. ''' logger.debug('Creating code object for abstract code:\n' + str(code)) @@ -238,6 +245,9 @@ def create_codeobj(group, code, template, indices, # If the GroupCodeRunner has specifiers, add them if additional_specifiers is not None: all_specifiers.update(additional_specifiers) + + template = get_codeobject_template(template_name, + codeobj_class=codeobj_class) if check_units: # Resolve the namespace, resulting in a dictionary containing only the @@ -264,8 +274,8 @@ def create_codeobj(group, code, template, indices, # Only pass the specifiers that are actually used specifiers = {} - for name in used_known: - specifiers[name] = all_specifiers[name] + for var in used_known: + specifiers[var] = all_specifiers[var] # Also add the specifiers that the template needs for spec in template.specifiers: @@ -288,13 +298,14 @@ def create_codeobj(group, code, template, indices, else: name = '_codeobject*' - return language.create_codeobj(name, - code, - resolved_namespace, - specifiers, - template, - indices=indices, - template_kwds=template_kwds) + return create_codeobject(name, + code, + resolved_namespace, + specifiers, + template_name, + indices=indices, + template_kwds=template_kwds, + codeobj_class=codeobj_class) class GroupCodeRunner(BrianObject): @@ -390,12 +401,12 @@ def _create_codeobj(self, additional_namespace=None): else: additional_specifiers = None - return create_codeobj(self.group, self.abstract_code, self.template, + return create_runner_codeobj(self.group, self.abstract_code, self.template, self.indices, self.name, self.check_units, additional_specifiers=additional_specifiers, additional_namespace=additional_namespace, - language=self.group.language, - template_kwds=self.template_kwds) + template_kwds=self.template_kwds, + codeobj_class=self.group.codeobj_class) def pre_run(self, namespace): self.update_abstract_code() diff --git a/brian2/groups/neurongroup.py b/brian2/groups/neurongroup.py index 7cf3ebda4..c49f94ccf 100644 --- a/brian2/groups/neurongroup.py +++ b/brian2/groups/neurongroup.py @@ -9,7 +9,6 @@ STATIC_EQUATION, PARAMETER) from brian2.equations.refractory import add_refractoriness from brian2.stateupdaters.base import StateUpdateMethod -from brian2.codegen.languages import PythonLanguage from brian2.memory import allocate_array from brian2.core.preferences import brian_prefs from brian2.core.base import BrianObject @@ -40,7 +39,7 @@ def __init__(self, group, method): indices = {'_neuron_idx': Index('_neuron_idx', True)} GroupCodeRunner.__init__(self, group, - group.language.template_state_update, + 'stateupdate', indices=indices, when=(group.clock, 'groups'), name=group.name + '_stateupdater*', @@ -98,7 +97,7 @@ class Thresholder(GroupCodeRunner): def __init__(self, group): indices = {'_neuron_idx': Index('_neuron_idx', True)} GroupCodeRunner.__init__(self, group, - group.language.template_threshold, + 'threshold', indices=indices, when=(group.clock, 'thresholds'), name=group.name+'_thresholder*') @@ -119,7 +118,7 @@ class Resetter(GroupCodeRunner): def __init__(self, group): indices = {'_neuron_idx': Index('_neuron_idx', False)} GroupCodeRunner.__init__(self, group, - group.language.template_reset, + 'reset', indices=indices, when=(group.clock, 'resets'), name=group.name + '_resetter*') @@ -164,6 +163,8 @@ class NeuronGroup(BrianObject, Group, SpikeSource): The `numpy.dtype` that will be used to store the values, or `core.default_scalar_dtype` if not specified (`numpy.float64` by default). + codeobj_class : class, optional + The `CodeObject` class to run code with. clock : Clock, optional The update clock to be used, or defaultclock if not specified. name : str, optional @@ -182,10 +183,13 @@ def __init__(self, N, model, method=None, reset=None, refractory=False, namespace=None, - dtype=None, language=None, - clock=None, name='neurongroup*'): + dtype=None, + clock=None, name='neurongroup*', + codeobj_class=None): BrianObject.__init__(self, when=clock, name=name) + self.codeobj_class = codeobj_class + try: self.N = N = int(N) except ValueError: @@ -228,12 +232,6 @@ def __init__(self, N, model, method=None, # Setup specifiers self.specifiers = self._create_specifiers() - # Code generation (TODO: this should be refactored and modularised) - # Temporary, set default language to Python - if language is None: - language = PythonLanguage() - self.language = language - # All of the following will be created in pre_run #: The threshold condition diff --git a/brian2/synapses/synapses.py b/brian2/synapses/synapses.py index c5c4f7a5d..3a773bc41 100644 --- a/brian2/synapses/synapses.py +++ b/brian2/synapses/synapses.py @@ -13,11 +13,10 @@ AttributeValue, Subexpression, ReadOnlyValue, StochasticVariable, SynapticArrayVariable, Specifier) -from brian2.codegen.languages import PythonLanguage from brian2.equations.equations import (Equations, SingleEquation, DIFFERENTIAL_EQUATION, STATIC_EQUATION, PARAMETER) -from brian2.groups.group import Group, GroupCodeRunner, create_codeobj +from brian2.groups.group import Group, GroupCodeRunner, create_runner_codeobj from brian2.memory.dynamicarray import DynamicArray1D from brian2.stateupdaters.base import StateUpdateMethod from brian2.stateupdaters.exact import independent @@ -45,7 +44,7 @@ def __init__(self, group, method): self.method_choice = method indices = {'_neuron_idx': Index('_neuron_idx', True)} GroupCodeRunner.__init__(self, group, - group.language.template_state_update, + 'stateupdate', indices=indices, when=(group.clock, 'groups'), name=group.name + '_stateupdater', @@ -89,7 +88,7 @@ def __init__(self, varname, synapses, target): GroupCodeRunner.__init__(self, group=synapses, - template=synapses.language.template_lumped_variable, + template='lumped_variable', code=code, indices=indices, # We want to update the lumped variable before @@ -152,7 +151,7 @@ def __init__(self, synapses, code, prepost, objname=None): objname = prepost + '*' GroupCodeRunner.__init__(self, synapses, - synapses.language.template_synapses, + 'synapses', code=code, indices=indices, when=(synapses.clock, 'synapses'), @@ -435,14 +434,15 @@ def _add_synapses(self, sources, targets, n, p, condition=None, 'i': Specifier('i'), 'j': Specifier('j') } - codeobj = create_codeobj(self.synapses, - abstract_code, - self.synapses.language.template_synapses_create, - {}, - additional_specifiers=specifiers, - additional_namespace=additional_namespace, - check_units=False, - language=self.synapses.language) + codeobj = create_runner_codeobj(self.synapses, + abstract_code, + 'synapses_create', + {}, + additional_specifiers=specifiers, + additional_namespace=additional_namespace, + check_units=False, + codeobj_class=self.synapses.codeobj_class, + ) codeobj() number = len(self.synaptic_pre) for variable in self._registered_variables: @@ -509,14 +509,15 @@ def __getitem__(self, index): additional_namespace = ('implicit-namespace', namespace) indices = {'_neuron_idx': Index('_neuron_idx', iterate_all=True)} abstract_code = '_cond = ' + index - codeobj = create_codeobj(self.synapses, - abstract_code, - self.synapses.language.template_state_variable_indexing, - indices, - additional_specifiers=specifiers, - additional_namespace=additional_namespace, - check_units=False, - language=self.synapses.language) + codeobj = create_runner_codeobj(self.synapses, + abstract_code, + 'state_variable_indexing', + indices, + additional_specifiers=specifiers, + additional_namespace=additional_namespace, + check_units=False, + codeobj_class=self.synapses.codeobj_class, + ) result = codeobj() return result @@ -576,8 +577,8 @@ class Synapses(BrianObject, Group): dtype : `dtype`, optional The standard datatype for all state variables. Defaults to `core.default_scalar_type`. - language : `Language`, optional - The code-generation language to use. Defaults to `PythonLanguage`. + codeobj_class : class, optional + The `CodeObject` class to use to run code. clock : `Clock`, optional The clock to use. method : {str, `StateUpdateMethod`}, optional @@ -589,9 +590,12 @@ class Synapses(BrianObject, Group): ''' def __init__(self, source, target=None, model=None, pre=None, post=None, connect=False, delay=None, namespace=None, dtype=None, - language=None, clock=None, method=None, name='synapses*'): + codeobj_class=None, + clock=None, method=None, name='synapses*'): BrianObject.__init__(self, when=clock, name=name) + + self.codeobj_class = codeobj_class if not hasattr(source, 'spikes') and hasattr(source, 'clock'): raise TypeError(('Source has to be a SpikeSource with spikes and' @@ -648,12 +652,6 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, self._given_namespace = namespace self.namespace = create_namespace(namespace) - # Code generation (TODO: this should be refactored and modularised) - # Temporary, set default language to Python - if language is None: - language = PythonLanguage() - self.language = language - self._queues = {} self._delays = {} diff --git a/brian2/tests/test_functions.py b/brian2/tests/test_functions.py index 77b9d3f72..54ad07907 100644 --- a/brian2/tests/test_functions.py +++ b/brian2/tests/test_functions.py @@ -14,13 +14,13 @@ def test_math_functions(): # We can only test C++ if weave is availabe try: import scipy.weave - languages = [PythonLanguage(), CPPLanguage()] + codeobj_classes = [WeaveCodeObject, NumpyCodeObject] except ImportError: # Can't test C++ - languages = [PythonLanguage()] + codeobj_classes = [NumpyCodeObject] with catch_logs() as _: # Let's suppress warnings about illegal values - for lang in languages: + for codeobj_class in codeobj_classes: # Functions with a single argument for func in [sin, cos, tan, sinh, cosh, tanh, @@ -42,7 +42,7 @@ def test_math_functions(): G = NeuronGroup(len(test_array), '''func = {func}(test_array) : 1'''.format(func=func_name), clock=clock, - language=lang) + codeobj_class=codeobj_class) #G.variable = test_array mon = StateMonitor(G, 'func', record=True) net = Network(G, mon) @@ -64,7 +64,7 @@ def test_math_functions(): G = NeuronGroup(len(test_array), '''func = test_array {op} scalar : 1'''.format(op=operator), clock=clock, - language=lang) + codeobj_class=codeobj_class) #G.variable = test_array mon = StateMonitor(G, 'func', record=True) net = Network(G, mon) diff --git a/brian2/tests/test_neurongroup.py b/brian2/tests/test_neurongroup.py index 9e11af02e..e4c2ebed3 100644 --- a/brian2/tests/test_neurongroup.py +++ b/brian2/tests/test_neurongroup.py @@ -7,24 +7,24 @@ from brian2.units.fundamentalunits import DimensionMismatchError from brian2.units.allunits import second from brian2.units.stdunits import ms, mV -from brian2.codegen.languages.cpp import CPPLanguage -from brian2.codegen.languages.python import PythonLanguage +from brian2.codegen.runtime.weave_rt import WeaveCodeObject +from brian2.codegen.runtime.numpy_rt import NumpyCodeObject # We can only test C++ if weave is availabe try: import scipy.weave - languages = [PythonLanguage(), CPPLanguage()] + codeobj_classes = [NumpyCodeObject, WeaveCodeObject] except ImportError: # Can't test C++ - languages = [PythonLanguage()] + codeobj_classes = [NumpyCodeObject] def test_creation(): ''' A basic test that creating a NeuronGroup works. ''' - for language in languages: + for codeobj_class in codeobj_classes: G = NeuronGroup(42, model='dv/dt = -v/(10*ms) : 1', reset='v=0', - threshold='v>1', language=language) + threshold='v>1', codeobj_class=codeobj_class) assert len(G) == 42 # Test some error conditions @@ -60,9 +60,9 @@ def test_stochastic_variable(): makes sure no error occurs. ''' tau = 10 * ms - for language in languages: + for codeobj_class in codeobj_classes: G = NeuronGroup(1, 'dv/dt = -v/tau + xi*tau**-0.5: 1', - language=language) + codeobj_class=codeobj_class) net = Network(G) net.run(defaultclock.dt) @@ -70,51 +70,51 @@ def test_unit_errors(): ''' Test that units are checked for a complete namespace. ''' - for language in languages: + for codeobj_class in codeobj_classes: # Unit error in model equations assert_raises(DimensionMismatchError, lambda: NeuronGroup(1, 'dv/dt = -v : 1', - language=language)) + codeobj_class=codeobj_class)) assert_raises(DimensionMismatchError, lambda: NeuronGroup(1, 'dv/dt = -v/(10*ms) + 2*mV: 1', - language=language)) + codeobj_class=codeobj_class)) def test_incomplete_namespace(): ''' Test that the namespace does not have to be complete at creation time. ''' - for language in languages: + for codeobj_class in codeobj_classes: # This uses tau which is not defined yet (explicit namespace) G = NeuronGroup(1, 'dv/dt = -v/tau : 1', namespace={}, - language=language) + codeobj_class=codeobj_class) G.namespace['tau'] = 10*ms net = Network(G) net.run(1*ms) # This uses tau which is not defined yet (implicit namespace) G = NeuronGroup(1, 'dv/dt = -v/tau : 1', - language=language) + codeobj_class=codeobj_class) tau = 10*ms net = Network(G) net.run(1*ms) def test_namespace_errors(): - for language in languages: + for codeobj_class in codeobj_classes: # model equations use unknown identifier - G = NeuronGroup(1, 'dv/dt = -v/tau : 1', language=language) + G = NeuronGroup(1, 'dv/dt = -v/tau : 1', codeobj_class=codeobj_class) net = Network(G) assert_raises(KeyError, lambda: net.run(1*ms)) # reset uses unknown identifier G = NeuronGroup(1, 'dv/dt = -v/tau : 1', reset='v = v_r', - language=language) + codeobj_class=codeobj_class) net = Network(G) assert_raises(KeyError, lambda: net.run(1*ms)) # threshold uses unknown identifier G = NeuronGroup(1, 'dv/dt = -v/tau : 1', threshold='v > v_th', - language=language) + codeobj_class=codeobj_class) net = Network(G) assert_raises(KeyError, lambda: net.run(1*ms)) @@ -122,10 +122,10 @@ def test_threshold_reset(): ''' Test that threshold and reset work in the expected way. ''' - for language in languages: + for codeobj_class in codeobj_classes: # Membrane potential does not change by itself G = NeuronGroup(3, 'dv/dt = 0 / second : 1', - threshold='v > 1', reset='v=0.5', language=language) + threshold='v > 1', reset='v=0.5', codeobj_class=codeobj_class) G.v = np.array([0, 1, 2]) net = Network(G) net.run(defaultclock.dt) @@ -135,47 +135,47 @@ def test_unit_errors_threshold_reset(): ''' Test that unit errors in thresholds and resets are detected. ''' - for language in languages: + for codeobj_class in codeobj_classes: # Unit error in threshold assert_raises(DimensionMismatchError, lambda: NeuronGroup(1, 'dv/dt = -v/(10*ms) : 1', threshold='v > -20*mV', - language=language)) + codeobj_class=codeobj_class)) # Unit error in reset assert_raises(DimensionMismatchError, lambda: NeuronGroup(1, 'dv/dt = -v/(10*ms) : 1', reset='v = -65*mV', - language=language)) + codeobj_class=codeobj_class)) # More complicated unit reset with an intermediate variable # This should pass NeuronGroup(1, 'dv/dt = -v/(10*ms) : 1', reset='''temp_var = -65 - v = temp_var''', language=language) + v = temp_var''', codeobj_class=codeobj_class) # throw in an empty line (should still pass) NeuronGroup(1, 'dv/dt = -v/(10*ms) : 1', reset='''temp_var = -65 - v = temp_var''', language=language) + v = temp_var''', codeobj_class=codeobj_class) # This should fail assert_raises(DimensionMismatchError, lambda: NeuronGroup(1, 'dv/dt = -v/(10*ms) : 1', reset='''temp_var = -65*mV v = temp_var''', - language=language)) + codeobj_class=codeobj_class)) # Resets with an in-place modification # This should work NeuronGroup(1, 'dv/dt = -v/(10*ms) : 1', - reset='''v /= 2''', language=language) + reset='''v /= 2''', codeobj_class=codeobj_class) # This should fail assert_raises(DimensionMismatchError, lambda: NeuronGroup(1, 'dv/dt = -v/(10*ms) : 1', reset='''v -= 60*mV''', - language=language)) + codeobj_class=codeobj_class)) def test_syntax_errors(): ''' @@ -186,20 +186,20 @@ def test_syntax_errors(): # We do not specify the exact type of exception here: Python throws a # SyntaxError while C++ results in a ValueError - for language in languages: + for codeobj_class in codeobj_classes: # Syntax error in threshold assert_raises(Exception, lambda: NeuronGroup(1, 'dv/dt = 5*Hz : 1', threshold='>1', - language=language), + codeobj_class=codeobj_class), ) # Syntax error in reset assert_raises(Exception, lambda: NeuronGroup(1, 'dv/dt = 5*Hz : 1', reset='0', - language=language)) + codeobj_class=codeobj_class)) def test_state_variables(): ''' diff --git a/brian2/tests/test_synapses.py b/brian2/tests/test_synapses.py index a7bf946d6..9235c2be2 100644 --- a/brian2/tests/test_synapses.py +++ b/brian2/tests/test_synapses.py @@ -6,10 +6,10 @@ # We can only test C++ if weave is availabe try: import scipy.weave - languages = [PythonLanguage(), CPPLanguage()] + codeobj_classes = [NumpyCodeObject, WeaveCodeObject] except ImportError: # Can't test C++ - languages = [PythonLanguage()] + codeobj_classes = [NumpyCodeObject] def _compare(synapses, expected): @@ -25,8 +25,8 @@ def test_creation(): A basic test that creating a Synapses object works. ''' G = NeuronGroup(42, 'v: 1') - for language in languages: - S = Synapses(G, G, 'w:1', pre='v+=w', language=language) + for codeobj_class in codeobj_classes: + S = Synapses(G, G, 'w:1', pre='v+=w', codeobj_class=codeobj_class) assert len(S) == 0 @@ -37,42 +37,42 @@ def test_connection_string_deterministic(): G = NeuronGroup(42, 'v: 1') G2 = NeuronGroup(17, 'v: 1') - for language in languages: + for codeobj_class in codeobj_classes: # Full connection expected = np.ones((len(G), len(G2))) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect(True) _compare(S, expected) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect('True') _compare(S, expected) - S = Synapses(G, G2, 'w:1', 'v+=w', connect=True, language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', connect=True, codeobj_class=codeobj_class) _compare(S, expected) - S = Synapses(G, G2, 'w:1', 'v+=w', connect='True', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', connect='True', codeobj_class=codeobj_class) _compare(S, expected) # Full connection without self-connections expected = np.ones((len(G), len(G))) - np.eye(len(G)) - S = Synapses(G, G, 'w:1', 'v+=w', language=language) + S = Synapses(G, G, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect('i != j') _compare(S, expected) - S = Synapses(G, G, 'w:1', 'v+=w', connect='i != j', language=language) + S = Synapses(G, G, 'w:1', 'v+=w', connect='i != j', codeobj_class=codeobj_class) _compare(S, expected) # One-to-one connectivity expected = np.eye(len(G)) - S = Synapses(G, G, 'w:1', 'v+=w', language=language) + S = Synapses(G, G, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect('i == j') _compare(S, expected) - S = Synapses(G, G, 'w:1', 'v+=w', connect='i == j', language=language) + S = Synapses(G, G, 'w:1', 'v+=w', connect='i == j', codeobj_class=codeobj_class) _compare(S, expected) @@ -84,61 +84,61 @@ def test_connection_random(): G = NeuronGroup(42, 'v: 1') G2 = NeuronGroup(17, 'v: 1') - for language in languages: - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + for codeobj_class in codeobj_classes: + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect(True, p=0.0) assert len(S) == 0 S.connect(True, p=1.0) _compare(S, np.ones((len(G), len(G2)))) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect('rand() < 0.') assert len(S) == 0 S.connect('rand() < 1.', p=1.0) _compare(S, np.ones((len(G), len(G2)))) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect(0, 0, p=0.) expected = np.zeros((len(G), len(G2))) _compare(S, expected) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect(0, 0, p=1.) expected = np.zeros((len(G), len(G2))) expected[0, 0] = 1 _compare(S, expected) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect([0, 1], [0, 2], p=1.) expected = np.zeros((len(G), len(G2))) expected[0, 0] = 1 expected[1, 2] = 1 _compare(S, expected) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect('rand() < 1.', p=1.0) _compare(S, np.ones((len(G), len(G2)))) # Just make sure using values between 0 and 1 work in principle - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect(True, p=0.3) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect('rand() < 0.3') - S = Synapses(G, G, 'w:1', 'v+=w', language=language) + S = Synapses(G, G, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect('i!=j', p=0.0) assert len(S) == 0 S.connect('i!=j', p=1.0) expected = np.ones((len(G), len(G))) - np.eye(len(G)) _compare(S, expected) - S = Synapses(G, G, 'w:1', 'v+=w', language=language) + S = Synapses(G, G, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect('i!=j', p=0.3) - S = Synapses(G, G, 'w:1', 'v+=w', language=language) + S = Synapses(G, G, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect(0, 0, p=0.3) - S = Synapses(G, G, 'w:1', 'v+=w', language=language) + S = Synapses(G, G, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect([0, 1], [0, 2], p=0.3) @@ -149,14 +149,14 @@ def test_connection_multiple_synapses(): G = NeuronGroup(42, 'v: 1') G2 = NeuronGroup(17, 'v: 1') - for language in languages: - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + for codeobj_class in codeobj_classes: + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect(True, n=0) assert len(S) == 0 S.connect(True, n=2) _compare(S, 2*np.ones((len(G), len(G2)))) - S = Synapses(G, G2, 'w:1', 'v+=w', language=language) + S = Synapses(G, G2, 'w:1', 'v+=w', codeobj_class=codeobj_class) S.connect(True, n='j') _compare(S, np.arange(len(G2)).reshape(1, len(G2)).repeat(len(G), @@ -287,20 +287,20 @@ def test_delay_specification(): def test_transmission(): delays = [[0, 0] * ms, [1, 1] * ms, [1, 2] * ms] - for language, delay in zip(languages, delays): + for codeobj_class, delay in zip(codeobj_classes, delays): # Make sure that the Synapses class actually propagates spikes :) source = NeuronGroup(2, '''dv/dt = rate : 1 rate : Hz''', threshold='v>1', reset='v=0', - language=language) + codeobj_class=codeobj_class) source.rate = [51, 101] * Hz target = NeuronGroup(2, 'v:1', threshold='v>1', reset='v=0', - language=language) + codeobj_class=codeobj_class) source_mon = SpikeMonitor(source) target_mon = SpikeMonitor(target) S = Synapses(source, target, pre='v+=1.1', connect='i==j', - language=language) + codeobj_class=codeobj_class) S.delay = delay net = Network(S, source, target, source_mon, target_mon) net.run(100*ms+defaultclock.dt+max(delay)) @@ -333,17 +333,17 @@ def test_lumped_variable(): def test_event_driven(): - for language in languages: + for codeobj_class in codeobj_classes: # Fake example, where the synapse is actually not changing the state of the # postsynaptic neuron, the pre- and post spiketrains are regular spike # trains with different rates pre = NeuronGroup(2, '''dv/dt = rate : 1 rate : Hz''', threshold='v>1', reset='v=0', - language=language) + codeobj_class=codeobj_class) pre.rate = [1000, 1500] * Hz post = NeuronGroup(2, '''dv/dt = rate : 1 rate : Hz''', threshold='v>1', reset='v=0', - language=language) + codeobj_class=codeobj_class) post.rate = [1100, 1400] * Hz # event-driven formulation taupre = 20 * ms @@ -363,7 +363,7 @@ def test_event_driven(): post='''Apost += dApost w = clip(w+Apre, 0, gmax)''', connect='i==j', - language=language) + codeobj_class=codeobj_class) # not event-driven S2 = Synapses(pre, post, '''w : 1 @@ -376,7 +376,7 @@ def test_event_driven(): Apost=Apost*exp((lastupdate-t)/taupost) +dApost w = clip(w+Apre, 0, gmax)''', connect='i==j', - language=language) + codeobj_class=codeobj_class) S1.w = 0.5*gmax S2.w = 0.5*gmax net = Network(pre, post, S1, S2) diff --git a/examples/synapses.py b/examples/synapses.py index 504cf459b..da733b868 100644 --- a/examples/synapses.py +++ b/examples/synapses.py @@ -3,18 +3,15 @@ from brian2 import * -language = PythonLanguage() - G1 = NeuronGroup(10, 'dv/dt = -v / (10*ms) : 1', threshold='v > 1', - reset='v=0.', language=language) + reset='v=0.') G1.v = 1.2 G2 = NeuronGroup(10, 'dv/dt = -v / (10*ms) : 1', threshold='v > 1', - reset='v=0', language=language) + reset='v=0') -syn = Synapses(G1, G2, 'dw/dt = -w / (50*ms): 1', pre='v+=w', - language=language) +syn = Synapses(G1, G2, 'dw/dt = -w / (50*ms): 1', pre='v+=w') syn.connect('i == j', p=0.75) diff --git a/examples/synapses_STDP.py b/examples/synapses_STDP.py index 85f9192ac..5df3c4fb7 100644 --- a/examples/synapses_STDP.py +++ b/examples/synapses_STDP.py @@ -42,7 +42,7 @@ post='''Apost+=dApost w=clip(w+Apre,0,gmax)''', connect=True, - language=CPPLanguage()) + ) S.w='rand()*gmax' start_time = time() run(100 * second, report='text') diff --git a/examples/synapses_gapjunctions.py b/examples/synapses_gapjunctions.py index 0642d2e61..253ef2ed0 100755 --- a/examples/synapses_gapjunctions.py +++ b/examples/synapses_gapjunctions.py @@ -7,9 +7,6 @@ from brian2 import * -#language = PythonLanguage() -language = CPPLanguage() - N = 10 v0 = 1.05 tau = 10*ms @@ -19,14 +16,13 @@ Igap : 1 # gap junction current ''' -neurons = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', - language=language) +neurons = NeuronGroup(N, eqs, threshold='v>1', reset='v=0') neurons.v = np.linspace(0, 1, N) trace = StateMonitor(neurons, 'v', record=[0, 5]) -S=Synapses(neurons, neurons, '''w:1 # gap junction conductance +S = Synapses(neurons, neurons, '''w:1 # gap junction conductance Igap=w*(v_pre-v_post): 1 (lumped)''', - language=language) + ) S.connect(True) S.w = .02 diff --git a/examples/synapses_licklider.py b/examples/synapses_licklider.py index a570331c0..b57ac160f 100644 --- a/examples/synapses_licklider.py +++ b/examples/synapses_licklider.py @@ -7,9 +7,6 @@ from pylab import * from brian2 import * -language = CPPLanguage() -#language = PythonLanguage() - defaultclock.dt = .02 * ms # Ear and sound @@ -17,13 +14,13 @@ tau_ear = 1 * ms sigma_ear = .1 eqs_ear = ''' -dx/dt=(sound-x)/tau_ear+sigma_ear*(2./tau_ear)**.5*xi : 1 (active) +dx/dt=(sound-x)/tau_ear+sigma_ear*(2./tau_ear)**.5*xi : 1 (unless-refractory) sound=5*sin(2*pi*frequency*t)**3 : 1 # nonlinear distorsion #sound=5*(sin(4*pi*frequency*t)+.5*sin(6*pi*frequency*t)) : 1 # missing fundamental frequency=(200+200*t*Hz)*Hz : Hz # increasing pitch ''' -receptors = NeuronGroup(2, eqs_ear, threshold='x>1', reset='x=0', language=language) -receptors.refractory = 2*ms +receptors = NeuronGroup(2, eqs_ear, threshold='x>1', reset='x=0', + refractory=2*ms) # Coincidence detectors min_freq = 50 * Hz max_freq = 1000 * Hz @@ -34,10 +31,9 @@ dv/dt=-v/tau+sigma*(2./tau)**.5*xi : 1 ''' -neurons = NeuronGroup(N, eqs_neurons, threshold='v>1', reset='v=0', language=language) +neurons = NeuronGroup(N, eqs_neurons, threshold='v>1', reset='v=0') -synapses = Synapses(receptors, neurons, 'w : 1', pre='v+=w', connect=True, - language=language) +synapses = Synapses(receptors, neurons, 'w : 1', pre='v+=w', connect=True) synapses.w=0.5 synapses.delay[1, :] = 1. / exp(linspace(log(min_freq / Hz), log(max_freq / Hz), N)) * second diff --git a/examples/synapses_nonlinear.py b/examples/synapses_nonlinear.py index 0590d432a..901c3432d 100755 --- a/examples/synapses_nonlinear.py +++ b/examples/synapses_nonlinear.py @@ -8,17 +8,14 @@ b=1/(10*ms) c=1/(10*ms) -#language = PythonLanguage() -language = CPPLanguage() - input=NeuronGroup(2, 'dv/dt=1/(10*ms):1', threshold='v>1', reset='v=0') neurons = NeuronGroup(1, """dv/dt=(g-v)/(10*ms) : 1 - g : 1""", language=language) + g : 1""") S=Synapses(input,neurons, '''dg/dt=-a*g+b*x*(1-g) : 1 (lumped) dx/dt=-c*x : 1 w : 1 # synaptic weight - ''', pre='x+=w', language=language) # NMDA synapses + ''', pre='x+=w') # NMDA synapses S.connect(True) S.w = [1., 10.] diff --git a/examples/synapses_state_variables.py b/examples/synapses_state_variables.py index 2ab1c7f5c..9d28afc99 100644 --- a/examples/synapses_state_variables.py +++ b/examples/synapses_state_variables.py @@ -5,11 +5,9 @@ from brian2 import * import numpy as np -language = CPPLanguage() - -G = NeuronGroup(100, 'v:volt', language=language) +G = NeuronGroup(100, 'v:volt') G.v = '(sin(2*pi*i/_num_neurons) - 70 + 0.25*randn()) * mV' -S = Synapses(G, G, 'w:volt', pre='v+=w', language=language) +S = Synapses(G, G, 'w:volt', pre='v+=w') S.connect('True') space_constant = 200.0 diff --git a/setup.py b/setup.py index e1755f746..69e7a1d5d 100644 --- a/setup.py +++ b/setup.py @@ -36,8 +36,9 @@ def run(self): 'brian2.codegen', 'brian2.codegen.functions', 'brian2.codegen.languages', - 'brian2.codegen.languages.cpp', - 'brian2.codegen.languages.python', + 'brian2.codegen.runtime', + 'brian2.codegen.runtime.numpy_rt', + 'brian2.codegen.runtime.weave_rt', 'brian2.core', 'brian2.equations', 'brian2.groups', @@ -50,11 +51,13 @@ def run(self): 'brian2.tests', 'brian2.units', 'brian2.utils'], - package_data={'brian2.codegen.languages.cpp': ['templates/*.cpp'], - 'brian2.codegen.languages.python': ['templates/*.py_']}, + package_data={'brian2.codegen.runtime.numpy_rt': ['templates/*.py_'], + 'brian2.codegen.runtime.weave_rt': ['templates/*.cpp']}, requires=['numpy(>=1.4.1)', 'scipy(>=0.7.0)', - 'sympy(>=0.7.1)' + 'sympy(>=0.7.1)', + 'pyparsing', + 'jinja2' ], - cmdclass = {'build_py': generate_preferences} - ) + cmdclass={'build_py': generate_preferences} + )