import inspect
from brian2.core.base import BrianObject
__all__ = ['NetworkOperation', 'network_operation']
[docs]class NetworkOperation(BrianObject):
"""Object with function that is called every time step.
Parameters
----------
function : function
The function to call every time step, should take either no arguments
in which case it is called as ``function()`` or one argument, in which
case it is called with the current `Clock` time (`Quantity`).
dt : `Quantity`, optional
The time step to be used for the simulation. Cannot be combined with
the `clock` argument.
clock : `Clock`, optional
The update clock to be used. If neither a clock, nor the `dt` argument
is specified, the `defaultclock` will be used.
when : str, optional
In which scheduling slot to execute the operation during a time step.
Defaults to ``'start'``.
order : int, optional
The priority of this operation for operations occurring at the same time
step and in the same scheduling slot. Defaults to 0.
See Also
--------
network_operation, Network, BrianObject
"""
add_to_magic_network = True
def __init__(self, function, dt=None, clock=None, when='start', order=0):
BrianObject.__init__(self, dt=dt, clock=clock, when=when, order=order, name='networkoperation*')
#: The function to be called each time step
self.function = function
is_method = inspect.ismethod(function)
if (hasattr(function, 'func_code') or # Python 2
hasattr(function, '__code__')): # Python 3:
argcount = function.func_code.co_argcount
if is_method:
if argcount == 2:
self._has_arg = True
elif argcount == 1:
self._has_arg = False
else:
raise TypeError(('Method "%s" cannot be used as a network '
'operation, it needs to have either only '
'"self" or "self, t" as arguments, but it '
'has %d arguments.' % (function.__name__,
argcount)))
else:
if (argcount >= 1 and
function.func_code.co_varnames[0] == 'self'):
raise TypeError('The first argument of the function "%s" '
'is "self", suggesting it is an instance '
'method and not a function. Did you use '
'@network_operation on a class method? '
'This will not work, explicitly create a '
'NetworkOperation object instead -- see '
'the documentation for more '
'details.' % function.__name__)
if argcount == 1:
self._has_arg = True
elif argcount == 0:
self._has_arg = False
else:
raise TypeError(('Function "%s" cannot be used as a '
'network operation, it needs to have '
'either only "t" as an argument or have '
'no arguments, but it has %d '
'arguments.' % (function.__name__,
argcount)))
else:
self._has_arg = False
[docs] def run(self):
if self._has_arg:
self.function(self._clock.t)
else:
self.function()
[docs]def network_operation(*args, **kwds):
"""
network_operation(when=None)
Decorator to make a function get called every time step of a simulation.
The function being decorated should either have no arguments, or a single
argument which will be called with the current time ``t``.
Parameters
----------
dt : `Quantity`, optional
The time step to be used for the simulation. Cannot be combined with
the `clock` argument.
clock : `Clock`, optional
The update clock to be used. If neither a clock, nor the `dt` argument
is specified, the `defaultclock` will be used.
when : str, optional
In which scheduling slot to execute the operation during a time step.
Defaults to ``'start'``.
order : int, optional
The priority of this operation for operations occurring at the same time
step and in the same scheduling slot. Defaults to 0.
Examples
--------
Print something each time step:
>>> from brian2 import *
>>> @network_operation
... def f():
... print('something')
...
>>> net = Network(f)
Print the time each time step:
>>> @network_operation
... def f(t):
... print('The time is', t)
...
>>> net = Network(f)
Specify a dt, etc.:
>>> @network_operation(dt=0.5*ms, when='end')
... def f():
... print('This will happen at the end of each timestep.')
...
>>> net = Network(f)
Notes
-----
Converts the function into a `NetworkOperation`.
If using the form::
@network_operations(when='end')
def f():
...
Then the arguments to network_operation must be keyword arguments.
See Also
--------
NetworkOperation, Network, BrianObject
"""
# Notes on this decorator:
# Normally, a decorator comes in two types, with or without arguments. If
# it has no arguments, e.g.
# @decorator
# def f():
# ...
# then the decorator function is defined with an argument, and that
# argument is the function f. In this case, the decorator function
# returns a new function in place of f.
#
# However, you can also define:
# @decorator(arg)
# def f():
# ...
# in which case the argument to the decorator function is arg, and the
# decorator function returns a 'function factory', that is a callable
# object that takes a function as argument and returns a new function.
#
# It might be clearer just to note that the first form above is equivalent
# to:
# f = decorator(f)
# and the second to:
# f = decorator(arg)(f)
#
# In this case, we're allowing the decorator to be called either with or
# without an argument, so we have to look at the arguments and determine
# if it's a function argument (in which case we do the first case above),
# or if the arguments are arguments to the decorator, in which case we
# do the second case above.
#
# Here, the 'function factory' is the locally defined class
# do_network_operation, which is a callable object that takes a function
# as argument and returns a NetworkOperation object.
class do_network_operation(object):
def __init__(self, **kwds):
self.kwds = kwds
def __call__(self, f):
new_network_operation = NetworkOperation(f, **self.kwds)
# Depending on whether we were called as @network_operation or
# @network_operation(...) we need different levels, the level is
# 2 in the first case and 1 in the second case (because in the
# first case we go originalcaller->network_operation->do_network_operation
# and in the second case we go originalcaller->do_network_operation
# at the time when this method is called).
new_network_operation.__name__ = f.__name__
new_network_operation.__doc__ = f.__doc__
new_network_operation.__dict__.update(f.__dict__)
return new_network_operation
if len(args)==1 and callable(args[0]):
# We're in case (1), the user has written:
# @network_operation
# def f():
# ...
# and the single argument to the decorator is the function f
return do_network_operation()(args[0])
else:
# We're in case (2), the user has written:
# @network_operation(...)
# def f():
# ...
# and the arguments must be keyword arguments
return do_network_operation(**kwds)