Skip to content
GitHub

sparx.graph

Circuits and connectomes: populations of neurons joined by projections, simulated on one clock.

sparx.dynamics holds the models of single neurons, synapses and plasticity rules; this package wires them into networks (design.md sections 5 and 6), builds the canonical ones and the published models on connectomes, and simulates them over dew’s meshes and checkpoints.

The names here are the ones a model and a run are written with. The types of the state, the layout rules and the connectome constants stay in their modules (sparx.graph.network, sparx.graph.simulate, sparx.graph.connectome).

Module
sparx.graph.simulateRunning a network for a long time: compiled chunks, state carried between them, records on the host.
Name
AllToAllEvery presynaptic neuron to every postsynaptic one (without self-connections unless autapses).
ArrivalInputWeights arriving on receptor of target at the end of each step, from the drive passed under name ([T, size]): recorded spike trains replayed into a network, in the receptor’s unit.
ConnectomeNeurons ids (e.g. FlyWire root IDs) and edges pre -> post (indices) with signed synapse counts.
CurrentInputA current (pA) into target, from the drive passed to the network under name ([T, size] or [T] per step, or a constant).
FixedInDegreeEach postsynaptic neuron receives exactly k inputs, drawn without replacement unless multapses.
FixedOutDegreeEach presynaptic neuron projects to exactly k targets. NEST’s fixed_outdegree.
FixedProbabilityEach pair independently with probability p (Erdős-Rényi), NEST’s pairwise_bernoulli.
FixedTotalNumberExactly n edges between the two populations, each pair drawn uniformly. NEST’s fixed_total_number.
FromEdgesGiven edges, as from a connectome table. Pairs may repeat (multapses).
GapJunctionElectrical synapses between populations a and b, each passing I = g (v_partner - v).
ModulatorA neuromodulator that the spikes of source release and volume transmission spreads, one concentration for the whole network.
ModulatorTraceThe concentration of the modulator named modulator after each step.
MonitorSomething recorded every step: record(outputs, states, dt, modulators) with outputs and states keyed by population, dt the step in ms, and the modulators’ concentrations by name.
NetworkPopulations and projections stepped together; see the module docstring.
OneToOneNeuron i to neuron i; the populations must be the same size.
OutputTraceWhat population sends each step, for the neurons neurons (indices) or all of them: a graded population’s values (a release, an activity), or a spiking one’s spikes as 0 and 1.
PoissonInputcount independent Poisson sources of rate Hz onto each neuron of target, through receptor.
Populationsize neurons of one model, with the synapses onto them by receptor name.
PopulationRateThe rate of population in each step, in Hz: the fraction of its neurons that fired in the step, over the step’s length in seconds.
ProjectionSynapses from population pre onto receptor receptor of population post.
SimulationWhat simulate returns: each monitor’s records over the steps this call ran, on the host and under the monitor’s name, and the final variables.
SpikeCountsEach neuron’s spike count over the run, summed as it goes: rates of large populations.
SpikeRasterWhich neurons of population fired, as booleans.
SpikeTimesThe indices of up to capacity neurons of population that fired each step, padded with -1: a raster of a large population at the cost of capacity integers per step.
StateMonitorread(state) of population each step, for the neurons neurons (indices) or all of them.
brunelBrunel’s (J. Comput. Neurosci. 2000) sparse network of excitatory and inhibitory LIF neurons, model A.
cobaVogels and Abbott’s (2005) network with conductance-based synapses (Brette et al.’s COBA benchmark).
cubaVogels and Abbott’s (2005) network with current-based synapses: Brette et al.’s (2007) CUBA benchmark.
from_recordThe network a {"class": builder, "fields": {...}} record names, built from its fields.
matched_w_synShiu et al.’s w_syn scaled so connectome’s median neuron gets the input FlyWire’s did.
microcircuitPotjans and Diesmann’s (Cereb. Cortex 2014) cortical microcircuit: 1 mm^2 of cortex, layers 2/3 to 6.
shiu2024Shiu et al.’s (Nature 2024) leaky integrate-and-fire model of the whole fly brain.
class AllToAll

sparx.graph.connectivity on GitHub

Every presynaptic neuron to every postsynaptic one (without self-connections unless autapses).

FieldTypeDefault
autapsesboolFalse
def edges(rng: np.random.Generator, pre: int, post: int, same: bool) -> EdgeList
class ArrivalInput

sparx.graph.network on GitHub

Weights arriving on receptor of target at the end of each step, from the drive passed under name ([T, size]): recorded spike trains replayed into a network, in the receptor’s unit.

FieldTypeDefault
targetstr
namestr
receptorstr
class Connectome

sparx.graph.connectome on GitHub

Neurons ids (e.g. FlyWire root IDs) and edges pre -> post (indices) with signed synapse counts.

FieldTypeDefault
idsnp.ndarray
prenp.ndarray
postnp.ndarray
synapsesnp.ndarray
sizeint
def inputs() -> np.ndarray

Each neuron’s input synapses, excitatory and inhibitory alike.

def index(ids: Iterable[int]) -> np.ndarray

The neuron indices of ids; raises for an ID not in the connectome.

def without(silenced: Iterable[int]) -> Connectome

The connectome with every synapse from the neurons silenced (indices) removed.

def from_shiu(completeness: str | Path, connectivity: str | Path) -> Connectome

Read Shiu et al.’s (2024) tables (github.com/philshiu/Drosophila_brain_model).

completeness is the CSV of neurons, indexed by FlyWire root ID, whose row order defines the neuron indices; connectivity the parquet file of edges with Presynaptic_Index, Postsynaptic_Index and Excitatory x Connectivity (the signed synapse count). Both FlyWire v630, which their paper used, and the public v783 tables read alike.

def from_malecns(annotations: str | Path, neurotransmitters: str | Path, weights: str | Path, statuses: Sequence[str] = ('Traced', 'Anchor'), signs: dict[str, int] | None = None) -> Connectome

Read Janelia’s male CNS (brain and nerve cord) release tables, gs://flyem-male-cns/v0.9.

annotations (body-annotations-*.feather) lists the bodies; those whose status is in statuses are the neurons (165,114 Traced and 785 Anchor in v0.9, against 1.8M segments in all). neurotransmitters (body-neurotransmitters-*.feather) gives each body’s predicted transmitter, consensus_nt or, where that is unclear, predicted_nt; signs maps it to the sign of the neuron’s synapses. weights (connectome-weights-*.feather) holds the synapse count of every connected pair of segments, filtered here to pairs of neurons. Neurons are ordered by body ID.

class CurrentInput

sparx.graph.network on GitHub

A current (pA) into target, from the drive passed to the network under name ([T, size] or [T] per step, or a constant).

FieldTypeDefault
targetstr
namestr
class FixedInDegree

sparx.graph.connectivity on GitHub

Each postsynaptic neuron receives exactly k inputs, drawn without replacement unless multapses.

Brunel (2000) connects this way: every neuron receives C_E excitatory and C_I inhibitory inputs. NEST’s fixed_indegree.

FieldTypeDefault
kint
autapsesboolFalse
multapsesboolFalse
def edges(rng: np.random.Generator, pre: int, post: int, same: bool) -> EdgeList
class FixedOutDegree

sparx.graph.connectivity on GitHub

Each presynaptic neuron projects to exactly k targets. NEST’s fixed_outdegree.

FieldTypeDefault
kint
autapsesboolFalse
multapsesboolFalse
def edges(rng: np.random.Generator, pre: int, post: int, same: bool) -> EdgeList
class FixedProbability

sparx.graph.connectivity on GitHub

Each pair independently with probability p (Erdős-Rényi), NEST’s pairwise_bernoulli.

FieldTypeDefault
pfloat
autapsesboolFalse
def edges(rng: np.random.Generator, pre: int, post: int, same: bool) -> EdgeList
class FixedTotalNumber

sparx.graph.connectivity on GitHub

Exactly n edges between the two populations, each pair drawn uniformly. NEST’s fixed_total_number.

Potjans and Diesmann’s (2014) cortical microcircuit connects this way, with n set so that a pair is connected at least once with the measured probability. With multapses each edge draws its pair independently, so a pair may repeat; without, n distinct pairs.

FieldTypeDefault
nint
autapsesboolFalse
multapsesboolFalse
def edges(rng: np.random.Generator, pre: int, post: int, same: bool) -> EdgeList
class FromEdges

sparx.graph.connectivity on GitHub

Given edges, as from a connectome table. Pairs may repeat (multapses).

FieldTypeDefault
prenp.ndarray
postnp.ndarray
def edges(rng: np.random.Generator, pre: int, post: int, same: bool) -> EdgeList
class GapJunction

sparx.graph.network on GitHub

Electrical synapses between populations a and b, each passing I = g (v_partner - v).

Each edge of connectivity (from a neuron of a to one of b) is one junction of conductance weight (nS), symmetric: the current into the neuron of a is g (v_b - v_a), and the current into the neuron of b is its negative. a and b may be one population. Both need a membrane voltage v in mV, as the physical models have.

The coupling has no delay, and each step solves it in two passes. Every coupled population first advances with its partners’ voltages held at their values at the start of the step, which predicts their voltages at its end. It then advances again from the start, with each partner’s voltage moving linearly from its start to its predicted end. A neuron integrates the coupling against its own voltage as a conductance, and its partners’ side as a current waveform (sparx.dynamics.Gap), which a linear membrane solves exactly and stably. The result is second order in dt. This is one iteration of the waveform relaxation NEST uses (Hahne et al., Frontiers in Neuroinformatics 2015), which iterates until the interpolated voltages agree to a tolerance and so converges to the coupled solution; without it NEST holds each partner at its voltage of the step before. tests/test_signalling.py compares sparx with the analytic solution of two coupled passive cells and with NEST. Coupled populations advance twice per step; the others once.

FieldTypeDefault
astr
bstr
connectivityConnectivity
weightPerEdge1.0
namestr | NoneNone
keystr
class Modulator

sparx.graph.network on GitHub

A neuromodulator that the spikes of source release and volume transmission spreads, one concentration for the whole network.

dc/dt = -c / tau + release * sum_k delta(t - t_k)

over the spikes t_k of source’s neurons, or of the neurons neurons (indices) when given, such as the dopaminergic neurons of a connectome. Each spike adds release to the concentration, which decays with tau (ms) between spikes, exactly on the step grid. The concentration is in the network’s state (NetworkState["modulators"], by name), and each Plasticity rule reads it every step as a third factor.

FieldTypeDefault
namestr
sourcestr
taufloat
releasefloat1.0
neuronstuple[int, ...] | NoneNone
class ModulatorTrace(Monitor)

sparx.graph.network on GitHub

The concentration of the modulator named modulator after each step.

FieldTypeDefault
modulatorstr
def record(outputs, states, dt, modulators)
class Monitor

sparx.graph.network on GitHub

Something recorded every step: record(outputs, states, dt, modulators) with outputs and states keyed by population, dt the step in ms, and the modulators’ concentrations by name.

A record is one array per step, stacked over the run into [T, ...]. A monitor with accumulate = True is summed over the steps of a run instead of stacked, so its memory does not grow with the run. Monitors are passed by name (monitors={"rate": PopulationRate("e")}), and the records come back under the same names.

FieldTypeDefault
accumulateboolFalse
def record(outputs: Mapping[str, jax.Array], states: Mapping[str, PointNeuronState], dt: float, modulators: Mapping[str, jax.Array]) -> jax.Array
class Network(nn.Module)

sparx.graph.network on GitHub

Populations and projections stepped together; see the module docstring.

FieldTypeDefault
populationsSequence[Population]
projectionsSequence[Projection]()
inputsSequence[PoissonInput | CurrentInput | ArrivalInput]()
dtfloat0.1
dtypejax.typing.DTypeLikejnp.float32
junctionsSequence[GapJunction]()
modulatorsSequence[Modulator]()
def connections(variables: Variables) -> dict[str, Connections]

Every projection’s synapses after a run, by projection key, as NEST’s GetConnections reads them.

pre, post, weight, delay = network.connections(result.variables)["e->e:ampa"]

Weights come from where the projection keeps them: the state for a plastic one (as learned so far), params for a trainable one, the connectome otherwise. A dense projection stores a matrix and no edge list, so its connections are the matrix’s nonzero entries, with repeated pairs summed.

def __call__(drive: Drive | None = None, steps: int | None = None, monitors: Mapping[str, Monitor] | None = None) -> dict[str, jax.Array]

Advance steps steps (or as many as drive has); returns each monitor’s records over time, under the monitor’s name.

class OneToOne

sparx.graph.connectivity on GitHub

Neuron i to neuron i; the populations must be the same size.

def edges(rng: np.random.Generator, pre: int, post: int, same: bool) -> EdgeList
class OutputTrace(Monitor)

sparx.graph.network on GitHub

What population sends each step, for the neurons neurons (indices) or all of them: a graded population’s values (a release, an activity), or a spiking one’s spikes as 0 and 1.

FieldTypeDefault
populationstr
neuronstuple[int, ...] | NoneNone
def record(outputs, states, dt, modulators)
class PoissonInput

sparx.graph.network on GitHub

count independent Poisson sources of rate Hz onto each neuron of target, through receptor.

Brunel’s (2000) external drive: their sum is one Poisson process of count * rate Hz, sampled per neuron and step. neurons limits the input to those indices of target, as an optogenetic stimulus does.

FieldTypeDefault
targetstr
ratefloat
weightfloat
receptorstr
countint1
neuronstuple[int, ...] | NoneNone
class Population

sparx.graph.network on GitHub

size neurons of one model, with the synapses onto them by receptor name.

neuron is any neuron model of sparx.dynamics, physical or dimensionless; a dimensionless one (ALIFCell, say) takes its input through delta receptors, as voltage jumps. A conductance receptor is named for a reversal potential of the neuron model (LIF’s defaults are ampa, nmda, gaba_a and gaba_b).

initial sets where each neuron starts, by name: a field of the neuron model’s state ("v") or a receptor, whose synapse state it sets, each to one value for all, an array [size] or f(rng, size) drawn from a NumPy generator seeded by the network’s key. Everything else starts at rest. reset_synapses and freeze_synapses are PointNeuron’s options.

FieldTypeDefault
namestr
sizeint
neuronNeuronModel
receptorsMapping[str, Receptor]field(default_factory=dict)
holdLiteral['mean', 'start']'mean'
initialMapping[str, PerNeuron]field(default_factory=dict)
reset_synapsesboolFalse
freeze_synapsesboolFalse
point_neuronPointNeuron
gradedbool
class PopulationRate(Monitor)

sparx.graph.network on GitHub

The rate of population in each step, in Hz: the fraction of its neurons that fired in the step, over the step’s length in seconds.

Hz is the unit of PoissonInput.rate and sparx.spiketrains.rates_hz, so a rate read here compares with them without conversion.

FieldTypeDefault
populationstr
def record(outputs, states, dt, modulators)
class Projection

sparx.graph.network on GitHub

Synapses from population pre onto receptor receptor of population post.

receptor has no default: it sets the unit of weight (pA, nS or mV) and, through the reversal potential, the sign of a conductance, so a weight means nothing without it. delay is in ms and must be a whole number of steps (whole_steps); either may be per edge. plasticity (a Plasticity rule, pair or triplet STDP) makes the weights state that evolves with the spikes; short_term scales each spike by its presynaptic neuron’s release; release makes each edge transmit a spike only with a probability, drawn from the network’s noise key. trainable puts fixed weights in params for gradient training. A projection from a graded population transmits weight times the presynaptic value every step, and takes none of the spike-driven options.

FieldTypeDefault
prestr
poststr
connectivityConnectivity
weightPerEdge1.0
delayPerEdge1.0
receptorstrfield(kw_only=True)
plasticityPlasticity | NoneNone
short_termTsodyksMarkram | NoneNone
releaseStochasticRelease | NoneNone
trainableboolFalse
namestr | NoneNone
formatLiteral['auto', 'edges', 'dense', 'events']'auto'
per_passint16
keystr
class Simulation

sparx.graph.simulate on GitHub

What simulate returns: each monitor’s records over the steps this call ran, on the host and under the monitor’s name, and the final variables.

FieldTypeDefault
recordsdict[str, np.ndarray]
variablesVariables
dtfloat
stepsint
startfloat0.0
timesnp.ndarray
class SpikeCounts(Monitor)

sparx.graph.network on GitHub

Each neuron’s spike count over the run, summed as it goes: rates of large populations.

FieldTypeDefault
populationstr
def record(outputs, states, dt, modulators)
class SpikeRaster(Monitor)

sparx.graph.network on GitHub

Which neurons of population fired, as booleans.

FieldTypeDefault
populationstr
def record(outputs, states, dt, modulators)
class SpikeTimes(Monitor)

sparx.graph.network on GitHub

The indices of up to capacity neurons of population that fired each step, padded with -1: a raster of a large population at the cost of capacity integers per step.

FieldTypeDefault
populationstr
capacityint64
def record(outputs, states, dt, modulators)
class StateMonitor(Monitor)

sparx.graph.network on GitHub

read(state) of population each step, for the neurons neurons (indices) or all of them.

read takes the population’s PointNeuronState and returns one value per neuron; it defaults to the membrane voltage. A voltage trace of every neuron costs steps * size values, 32 MB for 800 neurons over 1 s at 0.1 ms, so neurons keeps the few a figure shows.

FieldTypeDefault
populationstr
readCallable[[PointNeuronState], jax.Array]_membrane
neuronstuple[int, ...] | NoneNone
def record(outputs, states, dt, modulators)
def brunel(order: int = 2500, g: float = 5.0, eta: float = 2.0, j: float = 0.1, delay: float = 1.5, epsilon: float = 0.1, dt: float = 0.1) -> Network

sparx.graph.models on GitHub

Brunel’s (J. Comput. Neurosci. 2000) sparse network of excitatory and inhibitory LIF neurons, model A.

4 order excitatory and order inhibitory neurons (order=2500 is the paper’s 12,500); each receives epsilon of each population’s neurons, with delta synapses of j mV (excitatory) and -g j (inhibitory), all after delay ms, and Poisson input from C_E external neurons at eta times the rate that brings a free membrane to threshold. Membranes: 20 ms, threshold 20 mV, reset 10 mV, 2 ms refractory, rest 0. The regimes of his Figure 8: g=3, eta=2 synchronous regular; g=5, eta=2 asynchronous irregular; g=6, eta=4 synchronous irregular, fast; g=4.5, eta=0.9 synchronous irregular, slow. These are the parameters of NEST’s brunel_delta_nest.py, and as there inputs are drawn with replacement, self-connections allowed (NEST’s fixed_indegree defaults).

def coba(dt: float = 0.1) -> Network

sparx.graph.models on GitHub

Vogels and Abbott’s (2005) network with conductance-based synapses (Brette et al.’s COBA benchmark).

The CUBA network’s structure with conductances of 6 and 67 nS, decaying in 5 and 10 ms, reversing at 0 and -80 mV, on membranes of 200 pF and 10 nS resting at -60 mV (the reversal potentials are LeakyIntegrateAndFire’s defaults for ampa and gaba_a). Activity is sustained from random initial voltages and conductances (excitatory N(40, 15) nS, inhibitory N(200, 120) nS), as Brian’s example sets them.

def cuba(dt: float = 0.1) -> Network

sparx.graph.models on GitHub

Vogels and Abbott’s (2005) network with current-based synapses: Brette et al.’s (2007) CUBA benchmark.

As Brian2’s examples/CUBA.py: 3,200 excitatory and 800 inhibitory neurons connected with probability 0.02, without delay; membranes of 20 ms resting at -49 mV, above the -50 mV threshold, reset to -60 mV, 5 ms refractory; exponential currents of 5 and 10 ms. Brian2 states the synapses in volts, 1.62 and -9 mV over the membrane time constant; on a 200 pF membrane they are 16.2 and -90 pA. Voltages start uniform between reset and threshold.

def from_record(record: Record) -> Network

sparx.graph.models on GitHub

The network a {"class": builder, "fields": {...}} record names, built from its fields.

The builder is a short name of sparx.registry.networks or an import path. A field that is itself a record names its class or function by import path and is built first, so a model on a connectome is configured by its reader and paths: {"class": "shiu2024", "fields": {"connectome": {"class": "sparx.graph.connectome:Connectome.from_shiu", "fields": {"completeness": ..., "connectivity": ...}}, "stimuli": ...}}.

def matched_w_syn(connectome: Connectome, w_syn: float = 0.275, reference: float = FLYWIRE_630_MEDIAN_INPUTS) -> float

sparx.graph.connectome on GitHub

Shiu et al.’s w_syn scaled so connectome’s median neuron gets the input FlyWire’s did.

w_syn was fit to FlyWire v630’s synapse counts, and a connectome that counts more synapses per neuron drives every neuron harder at the same weight. The male CNS v0.9 counts a median 347 input synapses per neuron against FlyWire’s 206 (its mean, 748 against 414, grows alike), so it takes 0.163 mV: with 0.275 mV its sugar neurons recruit 18,000 neurons, with 0.163 mV about 670 (FlyWire: about 400), and MN9 fires as Shiu et al.’s does (docs/fidelity.md).

def microcircuit(neurons: float = 1.0, indegrees: float = 1.0, background: Literal['dc', 'poisson'] = 'dc', dt: float = 0.1) -> Network

sparx.graph.models on GitHub

Potjans and Diesmann’s (Cereb. Cortex 2014) cortical microcircuit: 1 mm^2 of cortex, layers 2/3 to 6.

Eight populations, an excitatory and an inhibitory one per layer, of iaf_psc_exp neurons (10 ms, 250 pF, rest and reset -65 mV, threshold -50 mV, 2 ms refractory, 0.5 ms currents), 77,169 at full scale. Each pair of populations is joined by a fixed total number of synapses, drawn with replacement, self-connections allowed, set so that a pair of neurons is connected at least once with the measured probability. Weights are normal around a current that peaks at 0.15 mV (twice that from L4E to L2/3E, -4 times it from inhibitory neurons), 10% standard deviation, drawn again where they change sign; delays are normal around 1.5 ms (excitatory) and 0.75 ms (inhibitory), 50% standard deviation, drawn again below half a step and rounded to steps, as NEST rounds them. The cortico-cortical input is a constant current per population (background="dc", the reference’s default) or Poisson spikes at 8 Hz from each of its inputs. A population fires at a few Hz, so a step’s events from one population are few, and each projection delivers them in passes of 4: at a fifth, 8.8 s per simulated second on a 4-core CPU, against 9.7 s for passes of 2, 9.9 s for 8 and 12.8 s for 16 (benchmarks/bench_networks.py).

neurons scales the population sizes and indegrees the inputs per neuron. With fewer inputs, each weight grows by one over the square root of the scale and a current makes up the mean input lost at the full model’s rates, which keeps each population’s rate. At a fifth of both (microcircuit(0.2, 0.2): 15,435 neurons, 12 million synapses) the populations fire as in the full model; at a tenth, the currents leave all but L6I below threshold and the network falls silent, in NEST as here. Every number is from the PyNEST implementation of INM-6/microcircuit-PD14-model (commit f79f8ac), which tests/test_microcircuit.py compares against.

def shiu2024(connectome: Connectome, stimuli: Sequence[tuple[Sequence[int], float]] = (), silenced: Sequence[int] = (), dt: float = 0.1, w_syn: float = 0.275, stimulus_scale: float = 250.0) -> Network

sparx.graph.connectome on GitHub

Shiu et al.’s (Nature 2024) leaky integrate-and-fire model of the whole fly brain.

Every neuron is one LIF: membrane 20 ms, rest and reset -52 mV, threshold -45 mV, 2.2 ms refractory, driven by dv/dt = (v_0 - v + g) / tau_m with dg/dt = -g / tau, tau = 5 ms. A spike adds w_syn (0.275 mV) times the signed synapse count to the target’s g after 1.8 ms. stimuli are (neuron indices, rate in Hz): each neuron gets Poisson spikes at the rate that move its voltage by stimulus_scale * w_syn (enough to fire it) and has no refractory period, their model of optogenetic activation. silenced neurons (indices) lose their outgoing synapses. On a connectome other than FlyWire v630, matched_w_syn scales w_syn to its synapse counts.

The model is theirs as their Brian2 code runs it (model.py, checked spike for spike on a small graph in tests/test_graph.py), quirks included: a spike also clears g, g holds while refractory and drops input arriving then, and a stimulus lands after the threshold test. Brian2 counts the refractory period from the start of the spiking step, so its 2.2 ms is sparx’s 2.1 (21 steps of 0.1 ms).