`PoissonGroup` vs. `PoissonInput` and their parameters

Description of problem

I’m trying to test my understanding of some code:

import numpy as np
from brian2 import Synapses, NeuronGroup, ms, mV, Hz, SpikeMonitor, Network, PoissonInput

params = {
    'v_0'       : -52 * mV,               # resting potential
    'v_rst'     : -52 * mV,               # reset potential after spike
    'v_th'      : -45 * mV,               # threshold for spiking
    't_mbr'     :  20 * ms,               # membrane time scale (capacitance * resistance = .002 * uF * 10. * Mohm)
    't_rfc'     : 2.2 * ms,
    'tau'       : 5 * ms,                 # time constant
    'r_poi'     : 150 * Hz,
    'f_poi'     : 250,
    'w_syn'     :0.275 * mV,
    "t_run": 1000 * ms,  # duration of trial
}

if __name__ == '__main__':
    connectivity_matrix = np.asarray([
        [0, 5, 0],
        [0, 0, -2],
        [0, 0, 0],
    ])
    eqs = '''
    dv/dt = (v_0 - v + g) / t_mbr : volt (unless refractory)
    dg/dt = -g / tau               : volt (unless refractory) 
    rfc                            : second
    '''

    neu = NeuronGroup(  # create neurons
        N=len(connectivity_matrix),
        model=eqs,
        method='linear',
        threshold='v > v_th',
        reset='v = v_rst; w = 0; g = 0 * mV',
        refractory='rfc',
        name='default_neurons',
        namespace=params
    )
    neu.v =-52 * mV
    neu.g = 0
    neu.rfc = 2.2 * ms

    sources, targets = connectivity_matrix.nonzero()
    syn = Synapses(neu, neu, 'w : volt', on_pre='g += w', delay=1.8 * ms, name='default_synapses')
    syn.connect(i=sources, j=targets)

    weight_per_synapse = 0.5*mV
    syn.w[:] = connectivity_matrix[(sources, targets)] * weight_per_synapse

    # Creating the poisson inputs

    p_groups = []
    for i in range(len(connectivity_matrix)):
        p = PoissonInput(
            target=neu[i], 
            target_var='v', 
            N=1, 
            rate=params['r_poi'], 
            weight=params['w_syn']*params['f_poi']
            )
        p_groups.append(p)


    spike_monitor = SpikeMonitor(neu)
    net = Network(neu, syn, spike_monitor, *p_groups)
    net.run(duration=params['t_run'], report="stdout")
    print(spike_monitor.spike_trains().values())

The above is saying

  1. create a collection (NeuronGroup) of 3 neurons
  2. create a collection of weighted synapses between the neuron groups and connect all of them according to the given connectivity matrix
  3. Set the weights according to the connectivity matrix
  4. create a PoissonInput where we apply a PoissonInput to each element in our circuit
  5. create the network and join everything up
  6. run the network simulation

Question 1:

What’s the difference between doing

    p_groups = []
    for i in range(len(connectivity_matrix)):
        p = PoissonInput(
            target=neu[i], 
            target_var='v', 
            N=1, 
            rate=params['r_poi'], 
            weight=params['w_syn']*params['f_poi']
            )
        p_groups.append(p)

and doing

    pois = [PoissonInput(
        target=neu,
        target_var='v',
        N=len(neu),
        rate=params['r_poi'],
        weight=params['w_syn'] * params['f_poi']
    )]

I came across Efficient Poisson inputs via PoissonInput and want to make sure that I’m doing things correctly…

1.1) In both cases we’re applying PoissonInputs to all elements in the network, right? The main difference is that the first method has an independent poisson process for each neuron, and the second one is using the same poisson process for all neurons? That’s my takeaway from the following quoted section

Note that the PoissonInput class is however more restrictive than PoissonGroup, it only allows for a constant rate across all neurons.

1.2) Assuming my understanding above is correct, it should be no surprise that all the neurons in my circuit have the exact spike times, right? If I want more “interesting” dynamics, I might want to create multiple PoissonInput collections?

I ask because my connectivity matrix of interest is (3000, 3000) in size, and if I’m trying to simulate applying a current to each neuron, the for-loop method is very slow. To be fair, the performance is pretty good when I’m doing the for-loop method on 300 of the neurons in that (3k, 3k) connectivity matrix.

1.3) What is the purpose of the N? I opened the documentation, but I don’t really understand it.

Question 2:

The code above also takes into account incoming signal from “upstream” neurons, right? i.e. if an upstream neuron spikes then we would have a correspondingly higher membrane potential than another neuron that didn’t have an upstream neuron spike?

Question 4:

brian2 can handle negative synapse weights, right? Where the negative indicates an inhibitory connection? My code should be fine?

Hi @itq. Sorry for the late reply, I was away for vacation. Regarding the PoissonInputs, there is indeed a bit of a misunderstanding here. As a quick background: the idea is that we don’t want to model some neurons in detail – typically, “input” neurons external to the modelled system. We can do this using a PoissonGroup, which only defines a firing rate and then generates spikes according to a Poisson process, but without modelling a membrane potential, etc. You can connect such a group to a NeuronGroup in the same way as you would for a normal NeuronGroup, i.e. with Synapses. This works fine, and you have a lot of flexibility, e.g. each neuron could fire with a different rate, the rate could even change over time, each connection could have weights of different strengths, etc. However, this kind of simulation is quite costly if you have many of these simplified Poisson neurons. Quite often, you want to have many of these external neurons, all firing with the same, constant rate, and having the same strength. In this case, you can make a big simplification in terms of simulation: instead of determining for each individual Poisson neuron whether it spikes or not, you determine how many of the neurons that connect to a neuron spike. This is a random number from a binomial distribution (if there are many of them, it is well approximated by a Gaussian distribution). This is what PoissonInput does. You specify the number of Poisson neurons that (this is the N) which connect to each target neuron, and the rate with which the neurons fire. The simulation will then increase the target variable (v in your case), by the respective amount at each time step. Hope that makes things a bit clearer!

Re Question 1:

The two are not the same, but they would be if you also used N=1 in the second variant. The second is much preferable, though, since it is more efficient. There is no reason to create a PoissonInput for each target neuron.

Re 1.1:

No, the Poisson processes are independent in both cases (the rate is the same, but the individual “spike trains” are different)

Re 1.2:

In both cases, spike times will be different across neurons.

Re 1.3:

Hopefully my explanation above made this clearer, the N is the number of “input neurons” that impinge on each target neuron.

Re Question 2:
I don’t quite understand this question – what are the “upstream neurons” in your case?

Re Question 4 (I did not see a question 3 :wink:):

Negative weights are not a problem, they will have an inhibitory effect.

Thank you so much! That cleared a lot of things up. I’ll change my title and open up a new thread with some followup questions I have.

Edit: looks like I can’t change the title. I think a more appropriate one would be PoissonGroup vs PoissonInput and parameters

Happy to hear that it cleared things up :blush: . Regarding the title: The forum has quite an elaborate system of giving rights for certain actions depending on how long a user has been around. It errs a bit on the strict side, and we’ve never changed the defaults. I think you should get upgraded to a new “trust level” soon, and this should allow editing your post titles. For now, I edited the title for you.

1 Like