Skip to content

Synapse creation

Marcel Stimberg edited this page Jan 15, 2016 · 17 revisions

See also: #600, #65.

Current best option is to have two ways of creating synapses: a simplified syntax for common cases, and a very flexible but slightly more complicated generator syntax. The simplified syntax is basically what we already have:

S.connect(condition='True', p=1, n=1)
S.connect(i, j)

The first is interpreted as meaning checking the condition for a potential pair and if this connection is true, create n synapses probability p. The second means create explicit synapses using the arrays i and j.

The new generator syntax would look like this:

S.connect(j='i+k for k in range(-3, 4)')
S.connect(j='k for k in range(0, N)')
S.connect(j='k for k in random_sample(0, N, p)')
S.connect(j='k for k in fixed_sample(0, N, num_targets)')

or in general:

S.connect(j='expr for var in candidates')
where expr can be any integer expression, var can be any variable name, candidates should be one of range, random_sample or fixed_sample. range has the default meaning in Python. Random sample would be like setting p=..., and fixed_sample(0, N, k) would return a randomly selected set of k integers between 0 and N-1. Note that the names of range, random_sample and fixed_sample could be changed: those are just a first suggestion (although probably fine I think).

This syntax is easy to convert into different languages (numpy/c++/cython), very flexible, and allows for very efficient synapse construction (since range, random and fixed samples can all be efficiently iterated over). To start with, the candidates would be a fixed list (range, random, fixed), but the nice thing about this syntax is that because it's valid Python it makes it very easy to expand it in the future. It also means that it will be familiar to users, and that they can test out connectivity patterns by just writing it in standard Python code since it is actually a valid Python expression. Parsing it is also extremely easy using the Python ast module (about 20 lines of code).

As a short-cut for generator expressions that generate only a single value (e.g. for an efficient one-to-one connectivity) we might also want to allow simply:

S.connect(j='i')  # one-to-one connectivity
i.e. in general:
S.connect(j='expr')

because otherwise you'd have to write the somewhat cumbersome:

S.connect(j='i for _ in range(1)')

Conditions

The generator-based syntax becomes even more useful if the candidates are only kept if an additional condition is fulfilled. There are two possible syntaxes for that:

Syntax 1 -- generator

Example:

S.connect(j='i+k for k in range(-3, 4) if k != i')

In general:

S.connect(j='expr for var in candidates if condition')

This would follow Python's standard generator syntax. The general approach for the connect argument then is: you use either condition (with optionally p and n) or you use j.

Syntax 2 -- condition keyword

Example:

S.connect(j='i+k for k in range(-3, 4)', condition='i!=j')
In general:
S.connect(j='expr for var in candidates', condition=..., p=...)

The logic behind this would be that you can combine all keyword arguments in the natural(?) way: you can specify a set of candidates with either a condition or j or both and optionally a probability with p. Although we might consider not allowing j and p together, since the use of p would be redundant with the use of random_sample and combining the two would be weird.

Other options

syn.connect(j='i+k', foreach='k in [-3, 3]')
syn.connect(j='i+k', foreach='k in [-3, 4[')
syn.connect(j='i+k', foreach='k in {-3, 0, 3}')

This is shorter than the generator syntax, but not as flexible (e.g. no way of specifying a fixed number of targets) and the meaning of the various brackets is maybe not obvious to everyone (for example, some people use [a,b) for a half open interval rather than [a,b[).

S.connect(condition='(i-100)%3==0', target_range=('i-100', 'i+100'), p=0.01)
S.connect(num_random_targets=20)
S.connect(target='i+(-1)**m', m_range=(0, 2))

This and variants were mine and Marcel's suggestions but I think in retrospect it's really less good than the generator syntax.

S.connect('rand()<p and j>=i-100 and j<=i+100')

In this suggestion (also mine, and I'm also disavowing it), we would analyse the boolean expression into parts, check for patterns, and construct an efficient way of iterating over it. The nice thing about this is that it's in some sense no syntax at all, just a condition. The bad part is that this method has many pitfalls (e.g. complicated boolean expressions expressed in a non-canonical way could mean that the expression couldn't be analysed), and actually requires the user to know how it works internally if they don't want nasty surprises.