Simulation
Compiling the network
Once all the relevant information has been defined, one needs to actually compile the network, by calling the ANNarchy.compile()
method:
compile() ann.
The optimized C++ code will be generated in the annarchy/
subfolder relative to your script, compiled, the underlying objects created and made available to the Python interface.
You can specify the following arguments to compile()
:
directory
: relative path to the directory where files will be generated and compiled (default:annarchy/
)populations
andprojections
: to compile only a subpart of the network, see Network.compiler
: to select which C++ compiler will be used. By defaultg++
on Linux andclang++
on OS X are used, you can change it here. Note that only these two compilers are supported for now, and that they must be in your$PATH
.compiler_flags
: to select which flags are passed to the compiler. By default it is-march=native -O3
, but you can fine-tune it here.
Simulating the network
After the network is correctly compiled, the simulation can be run for the specified duration (in milliseconds) through the ANNarchy.simulate()
method:
1000.0) # Simulate for 1 second ann.simulate(
The provided duration should be a multiple of dt
. If not, the number of simulation steps performed will be approximated.
In some cases, you may want to perform only one step of the simulation, instead of specifing the duration. The ANNarchy.step()
can then be used.
# Simulate for 1 step ann.step()
Early-stopping
In some cases, it is desired to stop the simulation whenever a criterion is fulfilled (for example, a neural integrator exceeds a certain threshold), not after a fixed amount of time.
There is the possibility to define a stop_condition
at the Population
level:
= ann.Population( ... , stop_condition = "r > 1.0") pop1
When calling the simulate_until()
method instead of simulate()
:
= ann.simulate_until(max_duration=1000.0, populations=pop1) t
the simulation will be stopped whenever the stop_condition
of pop1
is met, i.e. when the firing rate of any neuron of pop1 is above 1.0. If the condition is never met, the simulation will last maximally max_duration
. The methods returns the effective duration of the simulation (to compute reaction times, for example).
The stop_condition
can use any logical operation on the parameters and variables of the neuron associated to the population:
= ann.Population( ... , stop_condition = "(r > 1.0) and (mp < 2.0)") pop1
By default, the simulation stops when at least one neuron in the population fulfills the criterion. If you want to stop the simulation when all neurons fulfill the condition, you can use the flag all
after the condition:
= ann.Population( ... , stop_condition = "r > 1.0 : all") pop1
The flag any
is the default behavior and can be omitted.
The stop criterion can depend on several populations, by providing a list of populations to the populations
argument instead of a single population:
= ann.simulate_until(max_duration=1000.0, populations=[pop1, pop2]) t
The simulation will then stop when the criterion is met in both populations at the same time. If you want that the simulation stops when at least one population meets its criterion, you can specify the operator
argument:
= simulate_until(max_duration=1000.0, populations=[pop1, pop2], operator='or') t
The default value of operator
is a 'and'
function between the populations’ criteria.
Global operations (min, max, mean) are not possible inside the stop_condition
. If you need them, store them in a variable in the equations
argument of the neuron and use it as the condition:
= """
equations r = ...
max_r = max(r)
"""
Setting inputs periodically
In most cases, your simulation will be decomposed into a series of fixed-duration trials, where you basically set inputs at the beginning of the trial, run the simulation for a fixed duration, and possibly read out results at the end:
# Iterate over 100 trials
= []
result for trial in range(100):
# Set inputs to the network
= ann.Uniform(0.0, 1.0)
pop.I # Simulate for 1 second
1000.)
ann.simulate(# Save the output
result.append(pop.r)
For convenience, we provide the decorator every
, which allows to register a python method and call it automatically during the simulation with a fixed period:
= []
result
@ann.every(period=1000.)
def set inputs(n):
# Set inputs to the network
= ann.Uniform(0.0, 1.0)
pop.I # Save the output of the previous step
if n > 0:
result.append(pop.r)
100 * 1000.) ann.simulate(
In this example, set_inputs()
will be executed just before the steps corresponding to times t = 0., 1000., 2000., and so on until t = 100000.
The method can have any name, but must accept only one argument, the integer n
which will be incremented at each call of the method (i.e. it will take the values 0, 1, 2 until 99). This can for example be used to access data in a numpy array:
= np.random.random((100, 640, 480))
images
@ann.every(period=1000.)
def set inputs(n):
# Set inputs to the network
= images[n, :, :]
pop.I
100 * 1000.) ann.simulate(
One can define several methods that will be called in the order of their definition:
@ann.every(period=1000.)
def set inputs(n):
= 1.0
pop.I
@ann.every(period=1000.)
def reset inputs(n):
= 0.0 pop.I
In this example, set_inputs()
will be called first, followed by reset_inputs
, so pop.I
will finally be 0.0. The decorator every
accepts an argument offset
defining a delay within the period to call the method:
@ann.every(period=1000.)
def set inputs(n):
= 1.0
pop.I
@ann.every(period=1000., offset=500.)
def reset inputs(n):
= 0.0 pop.I
In this case, set_inputs()
will be called at times 0, 1000, 2000... while reset_inputs()
will be called at times 500, 1500, 2500..., allowing to structure a trial more effectively. The offset
can be set negative, in which case it will be relative to the end of the trial:
@every(period=1000., offset=-100.)
def reset inputs(n):
= 0.0 pop.I
In this example, the method will be called at times 900, 1900, 2900 and so on. The offset
value can not be longer than the period
, by definition. If you try to do so, a modulo operation will anyway be applied (i.e. an offset of 1500 with a period of 1000 becomes 500).
Finally, the wait
argument allows to delay the first call to the method from a fixed interval:
@every(period=1000., wait=5000.)
def reset inputs(n):
= 0.0 pop.I
In this case, the method will be called at times 5000, 6000 and so on.
Between two calls to simulate()
, the callbacks can be disabled or re-enabled using the following methods:
@every(period=1000.)
def reset inputs(n):
= 0.0
pop.I
# Simulate with callbacks
10000.)
ann.simulate(
# Disable callbacks
ann.disable_callbacks()
# Simulate without callbacks
10000.)
ann.simulate(
# Re-enable callbacks
ann.enable_callbacks()
# Simulate with callbacks
10000.) ann.simulate(
Note that the period is always relative to the time when simulate()
is called, so if no offset is defined, the callbacks will be called before the first step of a simulation, no matter how long the previous simulation lasted. In the current state, it is not possible yet to enable/disable callbacks selectively, it is all or none.
Callbacks can only be used with simulate()
, not with step()
or simulate_until()
.