2.18. Structural plasticity

ANNarchy supports the dynamic addition/suppression of synapses during the simulation (i.e. after compilation).

Warning

Structural plasticity is not available with the CUDA backend and will likely never be...

Because structural plasticity adds some complexity to the generated code, it has to be enabled before compilation by setting the structural_plasticity flag to True in the call to setup():

setup(structural_plasticity=True)

If the flag is not set, the following methods will do nothing.

There are two possibilities to dynamically create or delete synapses:

  • Externally, using methods at the dendrite level from Python.
  • Internally, by defining conditions for creating/pruning in the synapse description.

2.18.1. Dendrite level

Two methods of the Dendrite class are available for creating/deleting synapses:

  • create_synapse()
  • prune_synapse()

2.18.1.1. Creating synapses

Let’s suppose that we want to add regularly new synapses between strongly active but not yet connected neurons with a low probability. One could for example define a neuron type with an additional variable averaging the firing rate over a long period of time.

LeakyIntegratorNeuron = Neuron(
    parameters="""
        tau = 10.0
        baseline = -0.2
        tau_mean = 100000.0
    """,
    equations = """
        tau * dmp/dt + mp = baseline + sum(exc)
        r = pos(mp)
        tau_mean * dmean_r/dt =  (r - mean_r) : init = 0.0
    """
)

Two populations are created and connected using a sparse connectivity:

pop1 = Population(1000, LeakyIntegratorNeuron)
pop2 = Population(1000, LeakyIntegratorNeuron)
proj = Projection(pop1, pop2, 'exc', Oja).connect_fixed_probability(weights = 1.0, probability=0.1)

After an initial period of simulation, one could add new synapses between strongly active pair of neurons:

# For all post-synaptic neurons
for post in xrange(pop2.size):
    # For all pre-synaptic neurons
    for pre in xrange(pop1.size):
        # If the neurons are not connected yet
        if not pre in proj[post].ranks:
            # If they are both sufficientely active
            if pop1[pre].mean_r * pop2[post].mean_r > 0.7:
                # Add a synapse with weight 1.0 and the default delay
                proj[post].create_synapse(pre, 1.0)

create_synapse only allows to specify the value of the weight and the delay. Other syanptic variables will take the value they would have had before compile(). If another value is desired, it should be explicitely set afterwards.

2.18.1.2. Removing synapses

Removing useless synapses (pruning) is also possible. Let’s consider a synapse type whose “age” is incremented as long as both pre- and post-synaptic neurons are inactive at the same time:

AgingSynapse = Synapse(
    equations="""
        age = if pre.r * post.r > 0.0 :
                0
              else :
                age + 1 : init = 0, int
    """
)

One could periodically track the too “old” synapses and remove them:

# Threshold on the age:
T = 100000
# For all post-synaptic neurons receiving synapses
for post in proj.post_ranks:
    # For all existing synapses
    for pre in proj[post].ranks:
        # If the synapse is too old
        if proj[post][pre].age > T :
            # Remove it
            proj[post].prune_synapse(pre)

Warning

This form of structural plasticity is rather slow because:

  • The for loops are in Python, not C++. Implementing this structural plasticity in Cython should already help.
  • The memory allocated for the synapses of a projection may have to be displaced at another location. This can lead to massive transfer of data, slowing the simulation down.

It is of course the user’s responsability to balance synapse creation/destruction, otherwise projections could become either empty or fully connected on the long-term.

2.18.2. Synapse level

Conditions for creating or deleting synapses can also be specified in the synapse description, through the creating or pruning arguments. Thise arguments accept string descriptions of the boolean conditions at which a synapse should be created/deleted, using the same notation as other arguments.

2.18.2.1. Creating synapses

The creation of a synapse must be described by a boolean expression:

CreatingSynapse = Synapse(
    parameters = " ... ",
    equations = " ... ",
    creating = "pre.mean_r * post.mean_r > 0.7 : proba = 0.5, w = 1.0"
)

The condition can make use of any pre- or post-synaptic variable, but NOT synaptic variables, as they obviously do not exist yet. Global parameters (defined with the postsynaptic or projection flags) can nevertheless be used.

Several flags can be passed to the expression:

  • proba specifies the probability according to which a synapse will be created, if the condition is met. The default is 1.0 (i.e. a synapse will be created whenever the condition is fulfilled).
  • w specifies the value for the weight which will be created (default: 0.0).
  • d specifies the delay (default: the same as all other synapses if the delay is constant in the projection, dt otherwise).

Warning

Note that the new value for the delay can not exceed the maximal delay in the projection, nor be different from the others if they were all equal.

Other synaptic variables will take the default value after creation.

Synapse creation is not automatically enabled at the start of the simulation: the Projectiom method start_creating() must be called:

proj.start_creating(period=100.0)

This method accepts a period parameter specifying how often the conditions for creating synapses will be checked (in ms). By default they would be checked at each time step (dt), what would be too costly.

Similarly, the stop_creating() method can be called to stop the creation conditions from being checked.

2.18.2.2. Deleting synapses

Synaptic pruning also rely on a boolean expression:

PruningSynapse = Synapse(
    parameters = " T = 100000 : int, projection ",
    equations = """
        age = if pre.r * post.r > 0.0 :
                0
              else :
                age + 1 : init = 0, int""",
    pruning = "age > T : proba = 0.5"
)
  • A synapse type can combine creating and pruning arguments.
  • The pruning argument can rely on synaptic variables (here age), as the synapse already exist.
  • Only the proba flag can be passed to specify the probability at which the synapse will be deleted if the condition is met.
  • Pruning has to be started/stopped with the start_pruning() and stop_pruning() methods. start_pruning() accepts a period argument.