Network#

class braintools.conn.Network(populations, projections=())#

A set of populations and the projections between them.

Neurons of all populations are numbered globally by concatenating the populations in the given order: neuron i of population p has global index offsets[p] + i.

Parameters:
  • populations (Sequence[Population]) – Populations, with unique names.

  • projections (Sequence[Projection]) – Projections, with unique names, whose pre and post are among populations.

Variables:
  • populations (dict of str to Population) – Populations by name, in order.

  • projections (dict of str to Projection) – Projections by name, in order.

  • offsets (dict of str to int) – Global index of the first neuron of each population.

  • n_neurons (int) – Total number of neurons.

Raises:
  • TypeError – If an element is not a Population/Projection.

  • ValueError – If population or projection names are duplicated, or a projection refers to a population that is not part of the network.

Notes

Building is host-side NumPy (not traceable by jax.jit). The block matrix from to_csr() is a brainevent.CSR whose products support jit/vmap/grad.

Examples

An E/I network with fixed in-degrees (each neuron receives 80 excitatory and 20 inhibitory inputs):

>>> import numpy as np
>>> import brainunit as u
>>> from braintools.conn import Population, Projection, Network, FixedInDegree
>>> E = Population(800, name='E')
>>> I = Population(200, name='I')
>>> net = Network(
...     [E, I],
...     [
...         Projection(E, E, FixedInDegree(80, weight=0.6 * u.mS, seed=1)),
...         Projection(E, I, FixedInDegree(80, weight=0.6 * u.mS, seed=2)),
...         Projection(I, E, FixedInDegree(20, weight=-6.7 * u.mS, seed=3)),
...         Projection(I, I, FixedInDegree(20, weight=-6.7 * u.mS, seed=4)),
...     ],
... )
>>> net.offsets
{'E': 0, 'I': 800}
>>> results = net.build()
>>> list(results)
['E->E', 'E->I', 'I->E', 'I->I']
>>> W = net.to_csr()
>>> W.shape, W.nse
((1000, 1000), 100000)
>>> dense = np.asarray(u.get_mantissa(W.todense()))
>>> exc_inputs = (dense[:800] > 0).sum(axis=0)   # from E rows
>>> inh_inputs = (dense[800:] < 0).sum(axis=0)   # from I rows
>>> np.unique(exc_inputs), np.unique(inh_inputs)
(array([80]), array([20]))
build(recompute=False)[source]#

Generate every projection.

Parameters:

recompute (bool) – Redraw even where a generator has a cached result.

Returns:

Results with local (per-population) indices, keyed by projection name in order.

Return type:

Dict[str, ConnectionResult]

to_csr(data='weight')[source]#

Assemble all projections into one block matrix over the whole network.

Parameters:

data (str) – Which edge values to store. Missing weights count as 1.0 and missing delays as 0.0 (dimensionless).

Returns:

Matrix of shape (n_neurons, n_neurons) with edge (offsets[pre] + i, offsets[post] + j) for every edge (i, j) of every projection. Values of compatible units are converted to the unit of the first non-empty projection. If every projection has the same homogeneous value, the data stays a scalar.

Return type:

CSR

Raises:

ValueError – If the projections’ values have different physical dimensions (e.g. nS and dimensionless), or data is invalid.

Notes

Builds the projections (or reuses the generators’ cached results). Memory is O(total edges); rows are sorted by global presynaptic index and columns ascending within a row.