Running simulations

After this page you can run a model with or without plasticity, continue a run, choose the time step and the floating-point precision, use several threads, and save a model to disk.

sim! and train!

using SpikingNeuralNetworks
SNN.@load_units
E = SNN.IF(N = 100)
EE = SNN.SpikingSynapse(E, E, :ge; conn = (p = 0.1, μ = 0.5nS), LTPParam = SNN.STDPGerstner())
drive = SNN.Stimulus(SNN.PoissonFixed(rate = 2kHz), E, :ge)
model = SNN.compose(; E, EE, drive, silent = true)

SNN.sim!(model, 500ms)                  # weights frozen
SNN.train!(model, 500ms)                # STDP changes EE.W
SNN.sim!(; model, duration = 1s)       # keyword form
SNN.get_time(model.time)                # 2000.0 (ms): runs continue from the current time
2000.0f0

sim! and train! run the same step; only train! applies plasticity. Both accept the model positionally (sim!(model, duration)) or as a keyword (sim!(; model, duration)). The time step is the keyword dt (default 0.125ms). Further keywords: pbar = true (progress bar), perturbation! (a function called before each step), neurons_per_thread and flush_subnormals (below).

One step does, in this order:

<!– norun –>

update_time!(T, dt)                       # the clock advances first
for s in stimuli;     stimulate!(s, s.param, T, dt); record!(s, T); end
for p in populations
    update_traces!(p, p.param, dt, T)     # train! only
    integrate!(p, p.param, dt)
    plasticity!(p, p.param, dt, T)        # train! only
    record!(p, T)
end
for c in connections
    update_traces!(c, c.param, dt, T)     # train! only
    forward!(c, c.param, dt, T)
    plasticity!(c, c.param, dt, T)        # train! only
    record!(c, T)
end

Input written by a connection in one step is integrated by its target in the next. The spike list of a population is built once per step and shared by its connections and recorder, so a custom component must not write pop.fire between integrate! and the end of the step. A step that throws is a fatal error: build the model again.

Precision

Models run in Float32 by default. Build the populations with VFT = Vector{Float64} for a Float64 model; connections and stimuli take the precision of the population they write into, and compose gives the model a clock of the same type. float_type returns the precision of any component.

using SpikingNeuralNetworks
SNN.@load_units
E = SNN.IF(N = 100, VFT = Vector{Float64})
EE = SNN.SpikingSynapse(E, E, :ge; conn = (p = 0.1, μ = 0.5nS),
                        LTPParam = SNN.STDPGerstner(FT = Float64))
model = SNN.compose(; E, EE, silent = true)
SNN.train!(model, 100ms)
SNN.float_type(EE), eltype(model.time.t)    # (Float64, Float64)
(Float64, Float64)

A rule built in Float32 is converted to the precision of its synapse but keeps its Float32-rounded values; build it with FT = Float64 for exact Float64 parameters. Mixing a Float32 parameter object into a Float64 population is an ArgumentError. A few components are Float32 only (see Known limitations).

Threads

Start Julia with several threads (julia -t 8) and set the number of neurons per chunk:

using SpikingNeuralNetworks
SNN.@load_units
E = SNN.IF(N = 4000)
drive = SNN.Stimulus(SNN.PoissonFixed(rate = 2kHz), E, :ge)
model = SNN.compose(; E, drive, silent = true)

SNN.sim!(model, 100ms; neurons_per_thread = 512)    # this run only
SNN.set_neurons_per_thread!(512)                    # every following run
SNN.neurons_per_thread()                            # 512
SNN.set_neurons_per_thread!(0)                      # off (the default)

Neuron updates, forward! and the event-driven STDP, iSTDP and STP updates of SpikingSynapse are split by neuron range into at most 8 chunks. Results are bit-identical for every setting and thread count. set_optimal_neurons_per_thread!(model) times a copy of the model for several settings and keeps the fastest; with_neurons_per_thread(f, n) runs f() with a temporary setting.

Subnormal numbers

sim! and train! flush subnormal floating-point results to zero (flush_subnormals = true), which keeps decaying variables of silent neurons and synapses from slowing the run down. Values above floatmin are unaffected. Pass flush_subnormals = false for IEEE gradual underflow.

Saving and loading

using SpikingNeuralNetworks
E = SNN.IF(N = 10, name = "E")
model = SNN.compose(; E, silent = true)
dir = mktempdir()
SNN.save_model(; model, path = dir, name = "toy", info = (seed = 1,))
loaded = SNN.load_model(dir, "toy", (seed = 1,))
loaded.model.pop.E.N                    # 10
10

info is a NamedTuple of metadata that becomes part of the folder name. Files saved with version 1 cannot be loaded (see What changes from v1 to v2).

API

SNNModels.ChunkPtr — Type
ChunkPtr()

Cache of the sub-row pointers of a sparse synapse (SpikingSynapse.chunkptr): ptr[c, j] : ptr[c+1, j] - 1 are the entries of presynaptic column j whose postsynaptic neuron lies in chunk c. Built on first use (chunk_ptr!), rebuilt when colptr, I, Npost or the chunking change; invalidate_chunk_ptr! forces a rebuild (update_sparse_matrix! calls it). builds counts the (re)builds, buildtime is the duration (s) of the last one.

source
SNNModels.chunk_ptr! — Method
chunk_ptr!(cp::ChunkPtr, colptr, I, Npost, bpc) -> Matrix{Int32}

The sub-row pointer table for Npost postsynaptic neurons and bpc batches per chunk, (re)built if needed. Not thread-safe: call before the fork. If cp.sorted is false (unsorted columns) the connection must run serially.

source
SNNModels.chunk_range — Method
chunk_range(N, bpc, c) -> UnitRange{Int}

Neurons of chunk c: whole batches, boundaries multiples of 128; the chunks 1:nchunks(N, bpc) cover 1:N exactly once.

source
SNNModels.foreach_chunk — Method
foreach_chunk(f, N, bpc = batches per chunk setting)

Call f(range) for every chunk range of 1:N (see nchunks, chunk_range), in parallel on the Polyester workers when there is more than one chunk and more than one Julia thread; otherwise f(1:N) once on the calling thread. f must write only the entries of its own range and must not allocate. Typical use:

foreach_chunk(p.N) do rng
    @inbounds @simd for i in rng
        ...
    end
end
source
SNNModels.foreach_chunk_c — Method
foreach_chunk_c(f, N, bpc = batches per chunk setting)

Like foreach_chunk, but f(c, range) also gets the chunk index c (1-based), for per-chunk scratch memory or tables. On the serial path f(1, 1:N) is called once.

source
SNNModels.nchunks — Method
nchunks(N, bpc) -> Int

Number of chunks of a population of N neurons for bpc batches (of 128 neurons) per chunk (bpc <= 0: 1).

source
SNNModels.neurons_per_thread — Method
neurons_per_thread() -> Int
set_neurons_per_thread!(n)

Neurons per chunk of the threaded updates, rounded to whole 128-neuron batches (default 0: threading off; set_neurons_per_thread!(500) sets 512; 0 or Inf switches it off). At most 8 chunks per population; see foreach_chunk. sim! and train! accept it as a keyword for the duration of the run.

source
SNNModels.@chunks — Macro
@chunks N [bpc] rng body

Macro form of foreach_chunk: @chunks p.N r begin ... end runs the block once per chunk with r bound to the range of the chunk (@chunks N bpc r body with an explicit number of batches per chunk).

source
SNNModels.set_optimal_neurons_per_thread! — Method
set_optimal_neurons_per_thread!(model; candidates = [128, 256, 512, 1024, 2048, Inf], steps = 200,
                                repeats = 5, dt = 0.125f0, set = true, verbose = true)

Time a short sim! of a copy of model (modelcopy; model is not touched) for every candidate neurons_per_thread (Inf = threading off) and, with set = true, make the fastest the current setting. Returns a vector of named tuples (neurons_per_thread, nchunks, us_per_step, speedup) (nchunks: the largest over the populations; speedup relative to threading off). With one Julia thread nothing is measured and threading stays off. The optimum depends on the machine, the thread count and the model; short runs measure the initial, not the stationary, activity.

source
SNNModels.with_flush_subnormals — Method
with_flush_subnormals(f, flush::Bool = true)

Run f() with flush-to-zero (set_zero_subnormals(true)) on the calling thread and, when threading is on (see neurons_per_thread), on the Polyester workers, then restore the previous mode of every thread. flush = false runs f() unchanged. Used by sim! and train! (keyword flush_subnormals).

source