Scheduling and custom progress reporting¶
Scheduling¶
Every simulated object in Brian has three attributes that can be specified at
object creation time: dt, when, and order. The time step of the
simulation is determined by dt, if it is specified, a new Clock with the
given dt will be created for the object. Alternatively, a clock object
can be specified directly, this can be useful if a clock should be shared
between several objects – under most circumstances, however, a user should not
have to deal with the creation of Clock objects and just define dt. If
neither a dt nor a clock argument is specified, the object will use the
defaultclock. Setting defaultclock.dt will therefore change the dt of
all objects that use the defaultclock.
Note that directly changing the dt attribute of an object is not allowed,
neither it is possible to assign to dt in abstract code statements. To
change dt between runs, change the dt attribute of the respective
Clock object (which is also accessible as the clock attribute of each
BrianObject). The when and the order attributes can be changed by
setting the respective attributes of a BrianObject.
During a single time step, objects are updated according to their when
argument’s position in the schedule. This schedule is determined by
Network.schedule which is a list of strings, determining “execution slots” and
their order. It defaults to: ['start', 'groups', 'thresholds', 'synapses',
'resets', 'end']. In addition to the names provided in the schedule, names
such as before_thresholds or after_synapses can be used that are
understood as slots in the respective positions. The default
for the when attribute is a sensible value for most objects (resets will
happen in the reset slot, etc.) but sometimes it make sense to change it,
e.g. if one would like a StateMonitor, which by default records in the
end slot, to record the membrane potential before a reset is applied
(otherwise no threshold crossings will be observed in the membrane potential
traces). Note that you can also add new slots to the schedule and refer to them
in the when argument of an object.
Finally, if during a time step two objects fall in the same execution
slot, they will be updated in ascending order according to their
order attribute, an integer number defaulting to 0. If two objects have
the same when and order attribute then they will be updated in an
arbitrary but reproducible order (based on the lexicographical order of their
names).
Note that objects that don’t do any computation by themselves but only
act as a container for other objects (e.g. a NeuronGroup which contains a
StateUpdater, a Resetter and a Thresholder), don’t have any value for
when, but pass on the given values for dt and order to their
containing objects.
Every new Network starts a simulation at time 0; Network.t is a read-only
attribute, to go back to a previous moment in time (e.g. to do another trial
of a simulation with a new noise instantiation) use the mechanism described
below.
Note that while it is allowed to change the dt of an object between runs (e.g.
to simulate/monitor an initial phase with a bigger time step than a later
phase), this change has to be compatible with the internal representation of
clocks as an integer value (the number of elapsed time steps). For example, you
can simulate an object for 100ms with a time step of 0.1ms (i.e. for 1000 steps)
and then switch to a dt of 0.5ms, the time will then be internally
represented as 200 steps. You cannot, however, switch to a dt of 0.3ms, because
100ms are not an integer multiple of 0.3ms.
Progress reporting¶
For custom progress reporting (e.g. graphical output, writing to a file, etc.),
the report keyword accepts a callable (i.e. a function or an object with a
__call__ method) that will be called with four parameters:
elapsed: the total (real) time since the start of the runcompleted: the fraction of the total simulation that is completed, i.e. a value between 0 and 1start: The start of the simulation (in biological time)duration: the total duration (in biological time) of the simulation
The function will be called every report_period during the simulation, but
also at the beginning and end with completed equal to 0.0 and 1.0,
respectively.
For the C++ standalone mode, the same standard options are available. It is
also possible to implement custom progress reporting by directly passing the
code (as a multi-line string) to the report argument. This code will be
filled into a progress report function template, it should therefore only
contain a function body. The simplest use of this might look like:
net.run(duration, report='std::cout << (int)(completed*100.) << "% completed" << std::endl;')
Examples of custom reporting¶
Progress printed to a file
from brian2.core.network import TextReport
report_file = open('report.txt', 'w')
file_reporter = TextReport(report_file)
net.run(duration, report=file_reporter)
report_file.close()
“Graphical” output on the console
This needs a “normal” Linux console, i.e. it might not work in an integrated console in an IDE.
Adapted from http://stackoverflow.com/questions/3160699/python-progress-bar
import sys
class ProgressBar(object):
def __init__(self, toolbar_width):
self.toolbar_width = toolbar_width
self.ticks = 0
def __call__(self, elapsed, complete, start, duration):
if complete == 0.0:
# setup toolbar
sys.stdout.write("[%s]" % (" " * self.toolbar_width))
sys.stdout.flush()
sys.stdout.write("\b" * (self.toolbar_width + 1)) # return to start of line, after '['
else:
ticks_needed = int(round(complete * 40))
if self.ticks < ticks_needed:
sys.stdout.write("-" * (ticks_needed-self.ticks))
sys.stdout.flush()
self.ticks = ticks_needed
if complete == 1.0:
sys.stdout.write("\n")
net.run(duration, report=progress_bar, report_period=1*second)