snn_mbt

    Bit-exact MoonBit port of SpikingNeuralNetworks.jl plus CNN primitives + 5D spatiotemporal tensors + full optimiser suite + normalisation layers + learning-rate schedulers + generic Chain + pre-made bottles + ResNet foundations + surrogate gradients + transformer activations. Covers IF, AdEx, Izhikevich (IZ), HH, Morris-Lecar, Poisson neurons; Markram STP, Gerstner / MexicanHat / AntiSymmetric STDP, vSTDP, Confavreux2025, Receptors (AMPA / NMDA / GABAa / GABAb), multicompartment dendritic neurons (BallAndStick, Tripod, Multipod); Conv2d / MaxPool2d / ReLU / Flatten / Linear forward + backward primitives; Tensor struct with broadcasting + softmax + log-softmax + cross-entropy; image adapter (BMP/QOI/TGA/PNG/GIF/JPEG/ICO/TIFF) via riantr/moonbit_image; 5D [T,B,C,H,W] STImage for spiking-CNN time-series input; SGD + SGD-with-momentum + Adam (with bias correction) + AdamW (decoupled weight decay) + RMSprop + AdaGrad optimisers; BatchNorm2d (with running stats + training/inference modes) + LayerNorm (per-sample, no running stats) with forward + backward; StepLR + ExponentialLR + CosineAnnealingLR + ReduceLROnPlateau schedulers; generic Chain / Sequential over Conv2d / ReLU / MaxPool2d / Flatten / Linear / BatchNorm2d / LayerNorm via tagged-enum dispatch with shape tracking and per-layer parameter gradients; pre-made MLP / SimpleCNN / LeNet5 bottles with He-init + xoshiro RNG; elementwise Add + AvgPool2d / GlobalAvgPool2d + ResidualBlock (identity + projection variants) ResNet foundations; fast-sigmoid surrogate gradient (Zenke & Ganguli 2018) for BPTT through IF / LIF Heaviside spikes; GELU (Gaussian Error Linear Unit) tanh-approximation activation for transformer FFN; Multi-Head Self-Attention (Vaswani 2017) with Q/K/V/O projections, scaled dot-product, head split/merge, optional additive mask, and full backward through softmax + linear projections; sinusoidal (fixed) and learnable (Xavier-init) positional encoding with backward for learnable variant; Pre-Norm Transformer block (LN → MHA → + → LN → FFN(GELU) → +) with full BPTT-style backward; attention mask utilities (causal mask + head-broadcast + allowed-keys → additive mask); inverted dropout regularisation (Bernoulli mask with 1/(1-p) scale, training/inference mode toggle); Spiking Self-Attention (SpikeFormer-style) that replaces softmax along key axis with fast-sigmoid surrogate from v0.22.0, with full BPTT-compatible backward using the surrogate gradient; Pre-Norm Spiking Transformer block (LN → SpikingAttention → + → LN → FFN(GELU) → +) demonstrating the full SNN-Transformer training pipeline; Mini-SpikeFormer training demo (synthetic 28×28 dataset + patch embedding + learnable class token + SpikingTransformerBlock + classifier + cross-entropy + per-parameter gradient clipping + SGD); exact (sparse) GELU activation `x · Φ(x)` via libm `erff` for the cumulative normal distribution; Adafactor optimizer (Shazeer 2018) with factorised second-moment estimation for memory-efficient 2D + 1D updates; LayerScale (Touvron 2021) per-channel learnable scale γ (init=1e-4) on residual branches for stable training of deep transformers; T5-style relative position bias (Raffel 2020) added directly to attention scores per (head, offset) with linear-bucket clamping; consolidated SAttention module (sattention.mbt) unifying three spiking attention variants — SpikingMultiHeadAttention (multi-head self-attention, default), SpikingSelfAttention (single-head convenience), SpikingCrossAttention (cross-attention with separate Q/KV inputs) — all using fast-sigmoid forward + surrogate backward from v0.22.0; SRNN training demo (CSNN-style) with Poisson-encoded 2D images → T-step IF state evolution → linear readout → softmax + cross-entropy + BPTT through time using fast_sigmoid_surrogate; SCNN v2 with full K-step SGD training loop, per-layer gradient clipping, and accuracy tracking on the synthetic dataset. Float32 end-to-end with libm FFI (expf, tanhf, logf, sqrtf, cosf, sinf, erff). 1177 tests passing, 76 examples ported from Julia.

    spiking-neural-network
    neuroscience
    bit-exact
    izhikevich
    if-neuron
    adex
    hodgkin-huxley
    morris-lecar
    stdp
    stp
    poisson
    dendritic
    snns
    julia-port
    simulation
    conv2d
    cnn
    maxpool
    image
    tensor
    video
    sgd
    adam
    adamw
    rmsprop
    adagrad
    optimizer
    batch-norm
    layer-norm
    normalization
    lr-scheduler
    cosine-annealing
    reduce-on-plateau
    chain
    sequential
    mlp
    cnn
    lenet
    add
    avgpool
    global-avgpool
    residual
    resnet
    surrogate-gradient
    fast-sigmoid
    bp-friendly-spike
    gelu
    transformer-activation
    multi-head-attention
    self-attention
    transformer-block
    positional-encoding
    sinusoidal
    transformer-block
    pre-norm
    causal-mask
    attention-mask
    dropout
    inverted-dropout
    spiking-attention
    spikeformer
    spiking-transformer
    snnt-transformer
    sparse-gelu
    exact-gelu
    adafactor
    layer-scale
    t5-relative-position
    sattention
    spiking-cross-attention
    spiking-self-attention
    srnn
    csnn
    bptt
    spikeformer-demo
    eval-accuracy
    argmax
    prediction
    classification-accuracy
    Download zip
    Author
    Version
    0.53.0
    License
    MIT
    Last updated
    5 hours ago
    Downloads
    8

    Dependencies

    # snn_mbt 鈥?MoonBit port of SpikingNeuralNetworks.jl

    A bit-exact MoonBit re-implementation of the SpikingNeuralNetworks.jl ecosystem. Long-term goal: every example in SpikingNeuralNetworks.jl/examples/ runs under MoonBit and produces the same numerical trajectories (last-bit Float32) as the Julia run.

    #Status (v0.53.0, 2026-10-01)

    ComponentStatusNotes
    Unit system鉁?done30+ Float32 unit constants; units_test.mbt (8 tests pass)
    Time struct鉁?donet, tt, dt; update_time etc. (4 tests pass)
    Xoshiro RNG鉁?donexoshiro256++ (NOT **); Float32/Float64 paths match Julia (5 tests pass)
    Native math FFI鉁?doneexpf, tanhf, logf via libm; sigmoid_f32 derived (5 tests pass)
    math.ln/cos/sin鉁?doneRequired for Box-Muller in IZ init; via moonbitlang/core/math
    IF neuron鉁?doneForward-Euler update; DoubleExpSynapse state; IFParameter::with_el (4 tests pass)
    IF + Gsyn (Duarte2019 / LKD2014)鉁?doneneuron_if_gsyn.mbt 鈥?port of IFParameterGsyn from refs/SNNModels.jl/src/populations/generized_if/if.jl. IFParameterGsyn { base : IFParameter, gsyn_e : Float, gsyn_i : Float } wraps IFParameter + population-wide synaptic conductance scales. IF::with_gsyn(n, base, gsyn_e, gsyn_i, rng) convenience constructor + IFParameterGsyn::apply(p) setter that propagates the population scale to every neuron's gsyn_e[i] / gsyn_i[i] array (5 tests pass)
    AdEx neuron鉁?doneBrette-Gerstner 2005 defaults; exponential term via expf; AdExParameter::with_vr (5 tests pass)
    IZ (Izhikevich)鉁?doneTwo half-step midpoint Euler; ge/gi decay; v > 30 reset; fs() constructor (4 tests pass)
    HH (Hodgkin-Huxley)鉁?donem/n/h gating with sigmoid+expf; Na/K currents; v > -20 reset (2 tests pass)
    MorrisLecar鉁?donetanhf-based activation; K recovery w; v > 20 reset (3 tests pass)
    Poisson (population)鉁?donefire[i] = rand(Float32) < rate*dt; rate matches frequency (3 tests pass)
    InhomogeneousPoisson (variable-rate)鉁?doneneuron_inhomogeneous_poisson.mbt 鈥?port of inhomogeneous_poisson.jl (Julia's VariablePoisson). InhomogeneousPoissonParam{beta, tau, r0, rate_timescale} + InhomogeneousPoisson::new(n, param, rng) + step_inhomogeneous_poisson(p, dt, rng) (Ornstein-Uhlenbeck noise + rate adaptation toward r0; bit-exact integration) (7 tests pass)
    PoissonStimulus鉁?doneKnuth's algorithm; Float32 位; mean verified 鈮?位 (3 tests pass)
    CurrentStimulus鉁?doneDirect current injection with optional Gaussian noise; set_active, set_i_base (3 tests pass)
    SpikeTimeStimulus鉁?doneSpikeTimeParameter(spiketimes, neurons) (auto-sorted) + SpikeTimeStimulus::new(pop, sym, ...) + stimulate_spiketime(s, t, w). Wired into compose via TimedStim_(stim, w) (4 tests pass)
    CurrentStimulusArray鉁?doneGeneric current injection into raw Array[Float] for any population
    WilsonCowan rate model鉁?donex += dt*(-x+g+I); r=tanhf(x); init Normal(0, 0.5); g reset (5 tests pass)
    RateSynapse鉁?doneCSR forward_rate: g[post] += w * rJ[pre]; weights Normal(0, 渭/鈭?pN))
    AnyPop dispatcher鉁?doneEnum-based heterogeneous sim loop; includes WC_, PoissonIF_, CurrentIF_, CurrentArr_ (3 tests pass)
    AnyStim dispatcher鉁?donePoissonIF_, CurrentIF_ variants; stimulate_any dispatch
    Monitor sr鉁?doneMonitor::new_v_sr(pop, n, sr_hz) honours rec_step = 1/(sr*dt) (4 tests pass)
    vecplot text dump + ascii_plot鉁?donecount_spikes, count_spikes_interval, dump_summary, dump_csv_stdout, duration, ascii_plot(width?, height?), firing_rate, firing_rate_interval, spike_times, mean, min, max (17 tests pass)
    SparseMatrixCSR鉁?donefrom_dense, random, random_with_rule, set, get, forward, forward_rate (10 tests pass). Subset port of sparse_matrix_test.jl (matrix_size / get / set / nnz / bulk-set) in sparse_matrix_extra_test.mbt (8 tests pass)
    Population analysis鉁?doneanalysis_populations.mbt 鈥?port of populations.jl (subset). PopIndex struct (1-based inclusive ranges) + population_indices(pops) (assigns non-overlapping ranges) + filter_items(pops) (default drops "noise*" labels) + filter_items_with(pops, rule) (named FilterRule enum: Greater / Less / Equal / DropNoise; MoonBit lacks first-class fn refs) + average_conn_strength(M, pops, 渭) (block-mean / 渭 of dense weight matrix) (13 tests pass)
    ConnectRule enum鉁?doneBernoulli, FixedIn, FixedOut rules (3 tests pass)
    SpikingSynapse (CSR)鉁?donenew, random, random_with_rule, random_with_delays (delay_dist), spiking_connect, set_constant_delay, init_rho (Markram STP), forward_synapse, deliver_pending_synapse. Pending-event queue drains scheduled spikes at delivery time. v0.10.57: added name field (mirrors Julia's name kwarg) + with_name setter (returns new struct). EmptyConnection placeholder (no-op, mirrors Julia's EmptySynapse) (10 tests pass in connection_spiking_extra_test.mbt)
    Conv2d (NCHW forward)鉁?doneconv2d.mbt 鈥?NCHW row-major flat Array[Float] conv2d forward. Conv2dParam { weight, bias, c_out, c_in, kh, kw, stride, pad }; Conv2dParam::new with optional stride=1, pad=0 defaults; conv2d_forward(input, n, c_in, h, w, param) -> Array[Float] returns [n, c_out, ho, wo] flat. Naive CPU loop (no im2col/matmul yet). 9 tests in conv2d_test.mbt (1x1 conv, 3x3 valid 5x5 -> 3x3, same-pad preserves shape, multi-channel in/out + stride=2 downsample, batch N=2, 1x1 multi-channel out-ch=2, all-zero input -> bias broadcast, 3x3 valid 4x4 known center pixel + bias, 3x3 box-blur sums window)
    MaxPool2d (NCHW forward)鉁?donemaxpool2d.mbt 鈥?NCHW row-major flat Array[Float] max-pool forward. MaxPool2dParam { kh, kw, stride, pad }; MaxPool2dParam::new(kh, kw, stride?, pad?) with stride defaulting to kh; maxpool2d_forward(input, n, c, h, w, param) -> Array[Float] returns [n, c, ho, wo] flat. Out-of-bounds positions treated as -inf so padding never wins the max. 8 tests in maxpool2d_test.mbt (2x2 non-overlapping, stride=1 overlapping, multi-channel per-channel max, batch N=2, pad=1 + stride=2 on 4x3, all-negative picks least-negative, 3x3 stride=1 on 5x5 diagonal, 3x3 on 5x5 single-corner pick)
    ReLU✅ donerelu.mbt — element-wise max(x, 0). relu_forward(input : Array[Float]) -> Array[Float] returns a new array (does not mutate input). 4 tests in relu_test.mbt (mixed pos/neg / all-pos unchanged / all-neg -> zero / no-mutation)
    Flatten (NCHW → (N, C·H·W))✅ doneflatten.mbt — flatten_forward(input, n, c, h, w) -> Array[Float] returns flat array of length n*c*h*w. No data movement: NCHW row-major index (n, c*H*W + h*W + w) already matches the (n, c*h*w) row-major layout, so the output is a copy of the input. 3 tests in flatten_test.mbt (single-channel / multi-channel / batch N=2)
    Linear (dense / fully-connected)✅ donelinear.mbt — LinearParam { weight, bias, in_features, out_features } + LinearParam::new(weight, bias, in_features, out_features) builder; linear_forward(input, n, param) -> Array[Float] returns [n, out_features]. Per-batch matvec + bias. 5 tests in linear_test.mbt (single-batch matvec+bias / batch N=3 / zero input -> bias / zero weight -> bias / single-output scalar projection)
    CNN forward chain (Conv → ReLU → MaxPool → Flatten → Linear)✅ donecnn_chain_test.mbt — end-to-end mini-CNN forward pass test. Verifies shape flow (4x4 image -> Conv2d -> ReLU -> MaxPool2d -> Flatten -> Linear produces a 3-element output vector) and bit-exact repeatability. 2 tests (full chain on 4x4 / bit-exact repeat on 3x3)
    ReLU backward✅ donerelu_backward.mbt — relu_backward(input, d_output) -> Array[Float]; d_input[i] = d_output[i] if input[i] > 0 else 0. 4 tests in relu_backward_test.mbt (pass-through / zero-where-inactive / no-mutation / gradient check vs numerical)
    Flatten backward✅ doneflatten_backward.mbt — identity copy (NCHW layout is already collapseable). 3 tests in flatten_backward_test.mbt
    Linear backward✅ donelinear_backward.mbt — linear_backward(input, d_output, n, param) -> (d_input, d_weight, d_bias). d_weight via outer product, d_bias via sum, d_input via weight matvec. 4 tests in linear_backward_test.mbt (single-batch known / N=2 batch / d_input gradient check / d_weight gradient check)
    MaxPool2d backward✅ donemaxpool2d_backward.mbt — maxpool2d_forward_with_idx(input, n, c, h, w, param) -> (output, argmax_idx) records argmax indices during forward; maxpool2d_backward(d_output, argmax_idx, n, c, h, w, param) -> d_input routes gradient to argmax positions only. 4 tests in maxpool2d_backward_test.mbt
    Conv2d backward✅ doneconv2d_backward.mbt — conv2d_backward(input, d_output, n, c_in, h, w, param) -> (d_input, d_weight, d_bias). Three accumulated gradients via 7-nested loops. 6 tests in conv2d_backward_test.mbt (d_bias known / 1x1 d_weight / 1x1 d_input / d_input gradient check / d_weight gradient check / d_bias known)
    CNN backward chain (Conv → ReLU → MaxPool → Flatten → Linear)✅ donecnn_backward_chain_test.mbt — end-to-end mini-CNN backward. Hand-stitches each layer's backward. Verifies d_input and d_weight against numerical gradients (eps=1e-3, tol=1e-2) and bit-exact repeat across re-runs. 3 tests (d_input gradient check / bit-exact re-run / d_weight gradient check)
    Numerical gradient check helper✅ donegradient_check.mbt — numerical_gradient(input, eps, forward) central-difference helper + max_abs_diff(a, b) comparison. Reused by all backward tests
    Tensor struct + elementwise ops + broadcasting✅ donetensor.mbt — Tensor { data, shape } struct (row-major flat Array[Float]); Tensor::zeros / Tensor::ones / Tensor::from / Tensor::reshape / Tensor::numel. Element-wise: tensor_add / tensor_sub / tensor_mul / tensor_div with right-aligned broadcasting (scalar / row / column broadcast). 9 tests in tensor_test.mbt (zeros / ones / reshape / add same-shape / add scalar / add row vector / add col vector / sub-mul-div / no-alias)
    Tensor matmul + softmax + log_softmax✅ donetensor_ops.mbt — tensor_matmul(a, b) 2D matmul (naive O(mnk)); tensor_softmax(x) numerically-stable softmax along last axis (subtract-max trick); tensor_log_softmax(x) log-softmax. Uses libm expf / logf via FFI. 9 tests in tensor_ops_test.mbt (matmul 2x3×3x2 / identity / 1x1 / softmax uniform / softmax translation-invariant / softmax batched / log_softmax roundtrip / log_softmax sum=1 / matmul all-ones × values)
    Cross-entropy loss✅ donecross_entropy.mbt — CrossEntropyLoss { mean : Tensor, per_batch : Tensor } struct; cross_entropy_loss(log_probs, targets) -> CrossEntropyLoss returns mean + per-batch loss. 4 tests in cross_entropy_test.mbt (perfect predictions / uniform / batch mixed / softmax+cross_entropy pipeline)
    Image adapter (BMP/QOI/TGA/PNG/GIF/JPEG/ICO/TIFF)✅ doneimage.mbt — 4D NCHW Float32 Image { data, n, c, h, w } plus image_from_bytes(bytes) -> Image raise DecodeError (auto-detect format via riantr/moonbit_image@0.3.4), image_from_moonbit_image(img) (3-channel RGB), image_from_moonbit_image_rgba(img) (4-channel with alpha). Encode path not exposed — upstream's PixelFormat enum variants are read-only from outside the package, so users wanting to write image files call upstream directly. 4 tests in image_test.mbt (zeros shape / 24-bit BMP 2x2 round-trip with 4 distinct pixels / uniform 4x4 BMP / RGBA alpha=1.0 for opaque)
    STImage 5D [T, B, C, H, W] tensor✅ donespatio_temporal.mbt — 5D Float32 STImage { data, t, b, c, h, w } for spiking-CNN time-series input. Row-major flat Array[Float] of length T*B*C*H*W; flat offset t * (B*C*H*W) + b * (C*H*W) + c * (H*W) + h * W + w. Helpers: STImage::zeros / shape / numel / frame_block / batch_slice / offset / get / set / get_frame(t) (returns 4D Image { n: 1, c, h, w }, ready for v0.11.x Conv2d/MaxPool2d) / get_batch(b) (returns 4D Image { n: T, c, h, w }, time-flattened for vSTDP) / STImage::from_frames(frames) (stack [N=1, C, H, W] images along new T axis) / st_image_concat_t(a, b) (concatenate along T) / st_image_repeat_t(img, t) (broadcast a single image across T). 9 tests in spatio_temporal_test.mbt (zeros / flat offset computation / set-get round-trip / get_frame independence + no alias / get_batch time-flatten / from_frames stacking / concat_t ordering / repeat_t identical frames / Float32 bit-exact round-trip)
    SGD optimiser (vanilla + momentum)✅ doneoptimizer_sgd.mbt — generic array-level sgd_update_arrays(weight, bias, d_weight, d_bias, lr) plus SGDMomentumState { v_w, v_b } for SGD-with-momentum (PyTorch convention: v <- momentum * v + g; param <- param - lr * v). Layer wrappers sgd_update_linear / sgd_update_conv and momentum wrappers sgd_momentum_update_linear / sgd_momentum_update_conv return fresh parameter structs (immutable update). 9 tests in optimizer_sgd_test.mbt (vanilla step / zero-gradient no-op / no-alias / Linear wrapper preserves shape / Conv wrapper preserves stride+pad / first momentum step = SGD with v=0 / second step accumulates velocity / momentum=0 == vanilla SGD / loss monotonically decreases on y=2x+1 fit, 200 steps)
    Adam optimiser (with bias correction)✅ doneoptimizer_adam.mbt — AdamState { m_w, v_w, m_b, v_b : Array[Float]; step : Int } with adam_init(weight_len, bias_len), adam_next_step(state) accessor, adam_update_arrays(...) (PyTorch convention: m <- beta1*m + (1-beta1)*g; v <- beta2*v + (1-beta2)*g^2; m_hat <- m/(1-beta1^t); v_hat <- v/(1-beta2^t); param <- param - lr*m_hat/(sqrt(v_hat)+eps)). Layer wrappers adam_update_linear / adam_update_conv. sqrtf Float32 FFI added (was missing from math_native.mbt). 9 tests in optimizer_adam_test.mbt (zero state / next_step no-mutation / first step moves param in correct direction / 600-step linear regression converges w→2 b→1 / step counter monotonic via returned state / zero gradient = no-op / Linear shape preserved / Conv shape preserved with stride+pad)
    AdamW optimiser (decoupled weight decay)✅ doneoptimizer_adamw.mbt — AdamWState { m_w, v_w, m_b, v_b : Array[Float]; step : Int } (same shape as AdamState) with adamw_init, adamw_update_arrays (Loshchilov & Hutter 2019 convention: decoupled param <- param - lr*(m_hat/(sqrt(v_hat)+eps) + weight_decay*param), where weight_decay acts on the raw parameter not on the gradient). Layer wrappers adamw_update_linear / adamw_update_conv. 7 tests in optimizer_adamw_test.mbt (zero state / weight_decay=0 matches Adam direction / large weights shrink more absolutely than small weights under zero gradient / step counter via returned state / 600-step linear regression converges with mild decay / Linear shape / Conv shape)
    RMSprop optimiser✅ doneoptimizer_rmsprop.mbt — RMSpropState { v_w, v_b } (no first moment, no bias correction — Hinton 2012 lecture 6e form) with rmsprop_init, rmsprop_update_arrays (v <- alpha*v + (1-alpha)*g^2; param <- param - lr*g/(sqrt(v)+eps)). Layer wrappers rmsprop_update_linear / rmsprop_update_conv. 7 tests in optimizer_rmsprop_test.mbt (zero state / first step moves param in negative-gradient direction / alpha=0 ≈ raw gradient sign / large gradient gets shrunk by adaptive scaling / 600-step linear regression converges w→2 b→1 / Linear shape / Conv shape)
    AdaGrad optimiser✅ doneoptimizer_adagrad.mbt — AdaGradState { v_w, v_b } with adagrad_init, adagrad_update_arrays (Duchi et al. JMLR 2011: v <- v + g^2; param <- param - lr*g/(sqrt(v)+eps). Accumulating sum, not EMA — v monotonically grows, so effective per-parameter learning rate shrinks over time. Sparse-feature friendly; can stagnate on dense problems.) Layer wrappers adagrad_update_linear / adagrad_update_conv. 7 tests in optimizer_adagrad_test.mbt (zero state / first step moves in negative-gradient direction / v accumulates g^2 monotonically (not EMA) / per-parameter effective lr monotonically shrinks / 1500-step linear regression converges w→2 b→1 / Linear shape / Conv shape)
    BatchNorm2d (NCHW, with running stats)✅ donebatch_norm2d.mbt — BatchNorm2d { gamma, beta, mut running_mean, mut running_var, momentum, eps, mut training }. Forward batch_norm2d_forward(input, n, c, h, w, bn) -> (output, BatchNormCache); backward batch_norm2d_backward(d_output, cache, bn) -> (d_input, d_gamma, d_beta). Per-channel batch stats in training mode (across nhw), running stats for inference. PyTorch convention EMA on running stats: running <- (1 - momentum) * running + momentum * batch. Population variance (no Bessel correction). 8 tests in batch_norm2d_test.mbt (output shape / per-channel mean~0 std~1 / gamma/beta affine / running stats EMA update / backward shapes / zero d_output no-op / d_input numerical-gradient check).
    LayerNorm (per-sample, no running stats)✅ donelayer_norm.mbt — LayerNorm { gamma, beta, eps, c, h, w }. Forward layer_norm_forward(input, n, c, h, w, ln) -> (output, LayerNormCache); backward layer_norm_backward(d_output, cache, ln) -> (d_input, d_gamma, d_beta). Per-sample statistics (across chw). gamma/beta have shape [c*h*w] (per-feature). No running stats — deterministic at inference. 8 tests in layer_norm_test.mbt (output shape / per-sample mean~0 std~1 / different samples normalised independently / gamma/beta affine / backward shapes / zero d_output no-op / d_input numerical-gradient check).
    Learning-rate schedulers (StepLR + ExponentialLR + CosineAnnealingLR + ReduceLROnPlateau)✅ donescheduler.mbt — 4 schedulers sharing the step(s) -> Float / next(s) -> S API (state is not mutated; caller threads the returned state). StepLR::new(base_lr, step_size, gamma) drops lr by gamma every step_size steps; ExponentialLR::new(base_lr, gamma) does continuous exponential decay; CosineAnnealingLR::new(eta_max, eta_min, t_max) follows a half-cosine from eta_max to eta_min over t_max steps (wraps and repeats). ReduceLROnPlateau::new(base_lr, factor, patience, threshold) is reactive: feeds step(metric) returns (new_lr, new_state), drops lr by factor after patience consecutive non-improving calls (PyTorch convention with threshold-filtered improvements). cosf Float32 FFI added. 10 tests in scheduler_test.mbt (StepLR: step 0 = base, drops at step_size boundaries, next() does not mutate; ExponentialLR: lr = base*gamma^t; CosineAnnealingLR: t=0 -> eta_max, t=T_max/2 -> midpoint, monotonic decrease in first half; ReduceLROnPlateau: first call records metric, improvement resets bad counter, patience exhausted triggers decay, threshold prevents micro-improvements).
    Generic Chain / Sequential (tagged-enum dispatch)✅ donechain.mbt — Layer enum (Conv2d / ReLU / MaxPool2d / Flatten / Linear / BatchNorm2d / LayerNorm) + public Layer::conv2d / relu / max_pool2d / flatten / linear / batch_norm2d / layer_norm constructor helpers (MoonBit enum variants are read-only from other files). chain_forward(layers, input, n, c, h, w) -> (output, Array[LayerCache], n, c, h, w) runs layers in order, tracking shape transitions; chain_backward(layers, caches, d_output, n, c, h, w) -> (d_input, Array[LayerGrad]) runs them in reverse, returning per-layer LayerGrad enum (Linear(d_w, d_b) / Conv2d(d_w, d_b) / BatchNorm2d(d_g, d_b) / LayerNorm(d_g, d_b) / Empty / MaxPool2d). 7 tests in chain_test.mbt (empty chain / MLP forward / MLP backward with shape + grads / CNN Conv-ReLU-Pool-Flatten-Linear / zero d_output no-op / d_input numerical-gradient check vs central differences with non-zero pre-ReLU activations / BN inside MLP).
    Pre-made bottles (MLP / SimpleCNN / LeNet5)✅ donebottles.mbt — three ready-to-train architectures built on top of v0.19.0 Chain with He-init (sqrt(2/fan_in)) + Float32 Box-Muller via libm cosf/sinf/logf + xoshiro RNG seeded by caller for reproducibility. (1) MLP::new(sizes=[in,h1,...,out], seed) — sizes-matched Linears with ReLU between every pair (no ReLU after the final); mlp_forward / mlp_backward / MLP::num_params. (2) SimpleCNN::new(in_c, c1, c2, seed) — Conv-ReLU-Pool × 2 → Flatten → FC(10) for 28x28 inputs. (3) LeNet5::new(seed) — classic LeNet-5 (C1: 1->6 5x5, S2: MaxPool 2x2, C3: 6->16 5x5, S4: MaxPool 2x2, C5: 16->120 5x5, F6: 120->84, Out: 84->10). 10 tests in bottles_test.mbt (MLP: layer count, reproducible seed, different seeds differ, num_params, forward shape, backward shape + grad variants; SimpleCNN: forward shape + 8 caches; LeNet5: forward shape + 12 caches + zero d_output zero d_input + zero grads).
    Elementwise Add✅ doneelementwise_add.mbt — add_forward(a, b) -> Array[Float] elementwise (no aliasing); add_backward(d_output) -> (d_a, d_b) returns two independent copies. 5 tests (elementwise sum / no alias / zero-tensor identity / backward independence / gradient flow).
    AvgPool2d + GlobalAvgPool2d✅ doneavgpool2d.mbt — AvgPool2dParam::new(kh, kw, stride?, pad?) + avgpool2d_forward / avgpool2d_backward (count_include_pad=True: divisor is kh*kw regardless of pad). global_avg_pool2d_forward(input, n, c, h, w) -> Array[Float] pools to [n, c] (ResNet terminal pool); global_avg_pool2d_backward distributes gradient uniformly. 8 tests (output shape / 2x2 average / per-channel independence / single-pool gradient / overlapping pools accumulate / GAP shape / GAP backward uniform / GAP round-trip).
    ResidualBlock (basic + projection)✅ doneresidual_block.mbt — ResidualBlock struct with optional 1x1 conv + BN projection shortcut. ResidualBlock::identity(c, seed) for same-shape stacks; ResidualBlock::projection(c_in, c_out, stride, seed) for downsample blocks. residual_block_forward(b, x, n, c, h, w) -> (out, ResidualCache) runs conv1 → bn1 → relu → conv2 → bn2 → add(shortcut) → relu, caching sum and bn1_out for backward; residual_block_backward(b, cache, d_output) -> (d_input, ResidualGrads) runs the full reverse path including the shortcut BN/conv and merges d_x_main + d_x_shortcut. 5 tests (identity forward shape / zero d_output no-op / projection forward halves spatial / projection backward d_input shape + shortcut grads populated / conv1 weight numerical gradient check vs central differences).
    Surrogate gradient (fast sigmoid)✅ donesurrogate.mbt — Zenke & Ganguli 2018 fast-sigmoid surrogate for BPTT through IF / LIF Heaviside spikes. fast_sigmoid_surrogate(x, beta) -> Float is the backward surrogate σ'(x) = 1 / (1 + β·x)²; peak σ'(0) = 1, symmetric, decays asx→ ∞. fast_sigmoid_forward(x, beta) -> Float is the soft differentiable forward s(x) = x / (1 + β·x) saturating to ±1/β. heaviside_step(x, vt) -> Float is the hard 0/1 forward spike (matches the IF neuron step exactly). fast_sigmoid_surrogate_array / fast_sigmoid_forward_array elementwise forms. spike_surrogate(u, vt, beta) -> SpikeSurrogate combined envelope that returns both spike : Array[Float] (hard 0/1) and grad : Array[Float] (surrogate) in one pass — avoids recomputing u - vt in BPTT loops. 7 tests (peak=1 + symmetry + decay / β controls width + σ'(1/β)=0.25 invariant / soft forward monotonicity + saturation at ±1/β / elementwise consistency + peak-index / antisymmetric input / hard step at threshold / spike_surrogate combined envelope).
    GELU activation (tanh approximation)✅ donegelu.mbt + gelu_backward.mbt — Gaussian Error Linear Unit for transformer FFN. gelu(x) ≈ 0.5·x·(1 + tanhf(√(2/π)·(x + 0.044715·x³))) (matches PyTorch's F.gelu(approximate='tanh')). gelu_grad(x) = 0.5·(1 + t) + 0.5·x·sech²(inner)·√(2/π)·(1 + 3·0.044715·x²). gelu_forward(input) -> Array[Float] and gelu_backward(input, d_output) -> Array[Float]. 6 tests (origin=0 + saturation at ±5 + gelu(±1)≈±0.1587/0.8413 / grad(0)=0.5 + positive-side steeper + saturation at ±5 / element-wise consistency + no-mutation / gradient check vs central difference / element-wise backward + zero d_output no-op).
    Multi-Head Self-Attention✅ donemulti_head_attention.mbt — Vaswani 2017 standard MHA. MultiHeadAttention { d_model, num_heads, d_k, w_q, w_k, w_v, w_o } with MultiHeadAttention::new(d_model, num_heads, seed) (Xavier-normal init via Float32 Box-Muller + zero bias). multi_head_attention_forward(x, mha, mask) -> (out, AttnCache) self-attention: Q/K/V linear projections → per-head scaled dot-product q@k^T/sqrt(d_k) → optional additive mask → softmax along key axis → weighted sum → head merge → W_o projection. multi_head_attention_backward(cache, d_output, mha) -> (d_x, MHAGrad) full reverse path including softmax backward (d_scores = w*(d_w - w·d_w)), per-head Q/K gradient accumulation, and 3× linear backward (Q/K/V) summed into d_x. 8 tests (shape / softmax sum-to-1 / deterministic same-seed / different-seed independence / masked positions contribute zero weight + sum-to-1 / d_x+d_weight+d_bias shapes / zero d_output no-op / gradient check vs central difference on w_o[0]).
    Position encoding (sinusoidal + learnable)✅ doneposition_encoding.mbt — two complementary encodings for transformer input. (1) sinusoidal_position_encoding(max_len, d_model) -> Array[Float] non-trainable PE table with PE[pos, 2i] = sin(pos / 10000^(2i/d_model)) and PE[pos, 2i+1] = cos(...) (Vaswani 2017 §3.5). (2) PositionalEmbedding { max_len, d_model, weight } learnable parameter struct + PositionalEmbedding::new(max_len, d_model, seed) with Xavier-normal init via Float32 Box-Muller + positional_embedding_forward(pe, seq_len) -> Array[Float] slicing + positional_embedding_backward(pe, d_output, seq_len) -> Array[Float] (only active positions accumulate gradients). 5 tests (sinusoidal shape + boundary PE[0,*] = 0/1 / parity (even=sin, odd=cos) + determinism + distinct positions / learnable shape + determinism + non-zero init / forward slicing correctness + backward active-position routing).
    Transformer block (Pre-Norm)✅ donetransformer_block.mbt — Pre-Norm sub-layer stack: x2 = x + MHA(LN1(x)); out = x2 + FFN(LN2(x2)) where FFN(h) = Linear2(GELU(Linear1(h))) and d_ff defaults to 4·d_model. TransformerBlock { d_model, num_heads, d_ff, ln_1, ln_2, mha, ffn_w1, ffn_w2 } (LN gamma=1, beta=0 by default; FFN Xavier-normal). transformer_block_forward(x, block, mask) -> (out, TransformerBlockCache) composes LN → MHA → residual → LN → Linear → GELU → Linear → residual. transformer_block_backward(cache, d_output, block) -> (d_x, TransformerBlockGrad) walks 7 reverse steps including two residual splits and the cross-residual gradient accumulation. 7 tests (constructor shape + default d_ff=4·d_model / custom d_ff override / forward shape preservation + cache sanity / same-seed determinism / backward shapes including LN gamma/beta / zero d_output → zero d_x + zero all 8 grad arrays / gradient check vs central difference on ffn_w2[0]).
    Attention mask utilities✅ doneattention_mask.mbt — additive mask helpers for MHA. causal_mask(seq_len) -> Array[Float] upper-triangular -1e9 mask for autoregressive attention. mask_broadcast(mask_2d, seq_len, num_heads) replicates (seq_len × seq_len) → (num_heads × seq_len × seq_len). mask_from_allowed(seq_len, allowed : Array[Array[Bool]]) arbitrary boolean-table → additive mask (0 allowed, -1e9 blocked). causal_mask_heads(seq_len, num_heads) convenience: causal directly broadcast to per-head. 5 tests (shape + diagonal=0 + above=-1e9 / row 0 only attends to self / mask_broadcast shape + values replicated across heads / mask_from_allowed arbitrary boolean matrix / causal end-to-end via MHA: position i weights[j]=0 for j>i and row sum=1).
    Dropout (inverted)✅ donedropout.mbt — Dropout { p, mut training } (pub(all) for cross-file mut) with Dropout::new(p? = 0.5). dropout_forward(input, d, rng) -> (out, mask) returns the per-element Bernoulli mask + scaled activations (training) or identity (inference). dropout_backward(d_output, mask, d) -> Array[Float] reuses the same mask for the backward (so gradient corresponds to actual sparse activations). 8 tests (defaults + out-of-range guard / inference identity / training keeps ~p=0.5 fraction of n=1000 + 1/(1-p) inverted scale / same-seed determinism / p=0 no-op identity / p=1 all-zero / backward matches forward (mask × scale) + zero d_output no-op / inference backward identity).
    Spiking Self-Attention (SpikeFormer-style)✅ donespiking_attention.mbt — standard MHA structure (Q/K/V/O Linear projections + scaled dot-product) but replaces softmax along key axis with the fast_sigmoid_forward from v0.22.0. weights = fast_sigmoid_forward(scores, beta) range (-1/β, 1/β). Backward uses fast_sigmoid_surrogate (peak 1 at x=0, decays atx→∞) — this is the BPTT-compatible gradient that lets the spike emit non-zero learning signal. SpikingAttention { d_model, num_heads, d_k, beta, w_q, w_k, w_v, w_o } + SpikingAttention::new(d_model, num_heads, beta, seed) (Xavier-normal init) + spiking_attention_forward(x, sa, mask) -> (out, SpikingAttnCache) + spiking_attention_backward(cache, d_output, sa) -> (d_x, MHAGrad) (reuses MHAGrad since structure is identical). 8 tests (constructor shapes / output shape + weights bounded in (-1/β, 1/β) / weights = fast_sigmoid_forward(scores, β) / mask blocks positions / zero d_output → zero d_x + zero grads / gradient check vs central difference on w_o[0] / gradient check on w_q[0] / zero input + large beta → zero weights).
    Spiking Transformer block (Pre-Norm)✅ donespiking_transformer_block.mbt — Pre-Norm sub-layer stack using SpikingAttention instead of MultiHeadAttention: x2 = x + SpikingAttn(LN1(x)); out = x2 + FFN(LN2(x2)). SpikingTransformerBlock { d_model, num_heads, d_ff, beta, ln_1, ln_2, sa, ffn_w1, ffn_w2 } + SpikingTransformerBlock::new(d_model, num_heads, beta, seed, d_ff? = 4*d_model). spiking_transformer_block_forward / _backward mirrors the standard TransformerBlock but routes through spiking_attention_backward instead of multi_head_attention_backward. 6 tests (constructor + default d_ff / forward shape preservation + cache sanity / same-seed determinism / zero d_output → zero d_x + zero all 8 grad arrays / gradient check on ffn_w2[0] / gradient check on sa.w_q[0]).
    AdEx SpikingSynapse (CSR)鉁?donenew, random, random_with_rule; supports :ge/:he/:gi/:hi/:gaba routing
    ReceptorSynapse (4-receptor routing)鉁?doneconnection_receptor.mbt 鈥?port of ReceptorSynapse.jl (subset). Per-edge target_receptor : Array[Int] parallel to matrix.colptr selects which of the 4 receptors (AMPA/NMDA/GABAa/GABAb) each edge targets. forward_receptor_synapse(s) routes spike weights into s.glu[k] (if target in glu_receptors = [0, 1]) or s.gaba[k] (if in gaba_receptors = [2, 3]). set_target_receptor(s, idx, r) re-targets edge idx (5 tests pass)
    SpikingSynapseIZ (CSR)鉁?doneIZ-targeting: :ge/gi routed directly into post.ge/post.gi (no DoubleExp rise) (4 tests pass)
    SpikingSynapseHH (CSR)鉁?doneHH-targeting: same structure as SpikingSynapseIZ
    STDP (Gerstner 1996)鉁?doneSTDPGerstner params + STDPVariables traces + stdp_step mutating weights. Auto-integrated into compose layer via STDPEntry. gerstner_kernel(螖t, ...) for visualising the 螖W curve. stdp_kernel_plot(param) ASCII plot (11 tests pass)
    STDP (MexicanHat)鉁?doneSTDPMexicanHat params + mexican_hat_kernel(x) pure fn + stdp_mexican_hat_step (pre-spike + post-spike passes; trace decay via dt * -x/蟿). stdp_mexican_hat_plot ASCII viz. Auto-integrated into compose layer via STDPEntryMexicanHat + STDPEntryKind::MexicanHat_ (5+1 tests pass)
    STDP (AntiSymmetric)鉁?doneSTDPAntiSymmetric params + STDPAntiSymmetricVariables (tr_x, to_y) + stdp_antisymmetric_step (pre-spike pass uses to_y[i], post-spike pass uses tr_x[j]). stdp_antisymmetric_plot ASCII viz. Auto-integrated into compose layer via STDPEntryAntiSymmetric + STDPEntryKind::AntiSymmetric_ (5+1 tests pass)
    STDPEntryKind enum鉁?donepub(all) enum STDPEntryKind { Gerstner_(STDPEntry), MexicanHat_(STDPEntryMexicanHat), AntiSymmetric_(STDPEntryAntiSymmetric) }. Replaces the previously-hardcoded STDPEntry in HeterogeneousModel.stdp_entries (1 smoke test)
    STP (Markram 1998)鉁?doneMarkramSTPParameter (蟿D/蟿F/U/Wmax/Wmin) + MarkramSTPVariables (u/x/rho_pre/last_spike/active per pre) + markram_stp_step (event-based update_traces!: u=x=1 recovery via exp, then u+=U*(1-u)/x-=u*x bump) + init_rho on SpikingSynapse + 蟻-broadcast to outgoing edges. Auto-integrated into compose layer via STPEntryKind::MarkramSTP_ (12 tests pass)
    STPEntryKind enum鉁?donepub(all) enum STPEntryKind { MarkramSTP_(MarkramSTPEntry) | MarkramSTPHet_(MarkramSTPEntryHet) }. Run before forward in step_heterogeneous so 蟻 is fresh when spikes propagate. The _Het variant uses per-pre-neuron Array[Float] 蟿D/蟿F/U (matches Julia's MarkramSTPParameterHet)
    Float32 logf FFI鉁?doneextern "C" fn logf(x : Float) -> Float = "logf" added to math_native.mbt (1 test pass)
    compose() + sim鉁?doneHeterogeneousModel with pops+conns+stims+monitors+stdp+stp; step_heterogeneous (7-phase: stimulate 鈫?deliver_pending 鈫?STP 鈫?forward 鈫?STDP 鈫?integrate 鈫?record 鈫?update_time); heterogeneous_sim_for, get_time_heterogeneous, reset_time_heterogeneous (7 tests pass)
    sim! loop鉁?donesim_for, Monitor, record_one (single-pop case)
    AdEx sim! loop鉁?doneadex_sim_for, MonitorAdEx
    chain.jl鈿?partialRuns; final voltages + Monitor summary printed
    AdEx_neuron.jl鈿?partialRuns; 65 pA 鈫?tonic spiking; v range tracked
    IF_neuron.jl鈿?partial445 spikes in 1 s of 10 s
    izhikevich.jl鈿?partialRS neuron, 10 pA, 2 s 鈫?45 spikes
    hh_neuron.jl鈿?partialDefault HH, 10 pA, 1 s; current too small to fire
    morris_lecar.jl鈿?partialDefault ML, 100 pA, 1 s 鈫?1 spike; converges to v=1.92, w=0.53
    poisson_pop.jl鈿?partial1000 neurons @ 5 Hz 脳 100 s; 499,189 fires vs 500,000 expected (within 0.2%)
    if_net.jl鈿?partial32+8 IF, 337 random connections, 4 exc spikes in 100ms
    poisson_if.jl鈿?partial32+8 IF + Poisson inputs; E[0] fires at 345 Hz, I[0] silent
    iz_net.jl鈿?partial16 RS + 4 FS IZ + Gaussian noise; E fires 14 spikes over 1s
    if_noise.jl鈿?partialSingle IF + CurrentStimulus (400 pA + 蟽=100 noise); 48.5 Hz firing rate
    ei_inhibition.jl鈿?partialE-only: 13 spikes; E/I with feedback: 0 spikes (inhibition reduces E rate)
    adex_net.jl鈿?partial8 AdEx + 32 EE connections + 1000 pA tonic drive; v range [-70.6, 20] mV
    out_degree.jl鈿?partialCompares FixedIn/Bernoulli/FixedOut: FixedOut has std=0 (perfect uniformity)
    rate_net.jl鈿?partial100 WilsonCowan + all-to-all RateSynapse; rates evolve smoothly in (-1, 1)
    potjans.jl鈿?partialSimplified 2-layer Potjans-Diesmann microcircuit; E fires at 245 Hz, I at 255 Hz
    hh_current.jl鈿?partialSingle HH + CurrentStimulusArray; v[0] settles at -63 mV (current too low to fire at 10 碌A/cm虏)
    iz_net.jl鈿?partial16 E + 4 I IZ neurons with EE/EI/IE/II SpikingSynapseIZ; E fires 4, I fires 13 spikes in 1s
    hh_net.jl鈿?partial8 E + 4 I HH neurons with EE/EI/IE/II SpikingSynapseHH; E fires 0, I fires 1 spikes in 200ms
    tsodyks.jl鈿?partialScaled-down Tsodyks1997 (8 AdEx + 4 IF); E fires 143 spikes in 1s with Poisson-like drive
    adex_threshold.jl鈿?partialAdEx with Vr=-50mV, At=10mV, 蟿A=10ms; fires 2 spikes in 200ms
    adex_balanced.jl鈿?partialAdEx balanced (exc + inh Poisson); fires 1 spike in 1s with near-balanced input
    stdp_demo.jl鈿?partial4-IF identity EE; pre-then-post pairing 鈫?total 螖W 鈮?+2.5e-4 across 4 synapses
    cuba_net.jl鈿?partialCUBA.jl scaled 100脳 down (80E+20I); IFParameter custom (R=100M惟); 150pA drive for 5s 鈫?259 E + 402 I spikes; drive off 鈫?0 spikes both
    coba_net.jl鈿?partialCOBA.jl scaled 100脳 down (80E+20I); same IF params; 150pA for 1s 鈫?57 E + 41 I spikes; drive off for 5s 鈫?0 spikes both. delay_dist=Normal(0.8ms, 0) now implemented via random_with_delays + pending-event queue (v0.10.3+)
    stdp_compose_demo.mbt鈿?partial4-IF identity EE via compose layer's auto STDP (Gerstner 1996). 4 pairings 脳 5ms 鈫?+2.5e-4 螖W across 4 synapses (matches manual stdp_demo result)
    festa2024.mbt鈿?partialFesta2024 StructuredInhibition network scaled 10脳 down (80E+20I1); Gerstner STDP on I1鈫扙 (auto-integrated); 1s sim: E=308 Hz, I1=286 Hz, I1鈫扙 weights grew 885.6鈫?030.8
    timed_stim.mbt鈿?partialtimed_stim.jl port: 3 IF neurons + SpikeTimeStimulus at t=100/200/300 ms (渭=100 nS). E[0] fires 4 times from its spike; E[1,2] receive but don't fire (insufficient drive with default IF)
    potjans_diesmann.mbt鈿?partialPotjans-Diesmann cortical microcircuit simplified to 4 layers + scaled 100脳 down (120 E + 40 I); Poisson drive 500Hz + 350 pA tonic on E. 1s sim: E[0]=302 Hz, I[0]=165 Hz
    lkd2014.mbt鈿?partialLitwinKumar2014 vSTDP network scaled 100脳 down (40 E + 10 I); 4 SpikingSynapses (渭=2.76/1.27/48.7/16.2); Poisson 4.5/2.5 Hz + tonic 350/250 pA. 1s sim: E[0]=217 Hz, I[0]=194 Hz
    stdp_kernel.mbt鈿?partialSTDP_kernel.jl port: plots Gerstner (asymmetric, classical 1996) and symmetric Gerstner kernel curves using stdp_kernel_plot. STDPMexicanHat kernel now implemented (see calcium_kernel); STDPAntiSymmetric also added and both are now auto-integrated via STDPEntryKind compose dispatch
    afferent_response.mbt鈿?partialafferent_response.jl port (simplified, single 谓_a = 20Hz); 40 E + 10 I + Poisson drive. E[0]=183 Hz, I[0]=185 Hz
    lagzi2022.mbt鈿?partialLagzi2022 Assembly Formation simplified to 2 IF populations (16+16) + 4 SpikingSynapses + Gerstner STDP on W11+W22 (auto-integrated). 1s sim: E1[0]=419 Hz, E2[0]=419 Hz; weights modified by STDP
    izhikevich_debug.mbt鈿?partialDebugging variant of izhikevich.mbt. Records v[0] for 100 ms with fixed seed; prints min/max/mean. Used for bit-exactness verification against Julia's Izikievich_neuron.jl
    calcium_kernel.mbt鈿?partialCalciumPlasticity_kernel.jl port (partial): plots reversed-polarity + classical Gerstner kernels + STDPMexicanHat (sombrero) kernel + decorrelated weights. iSTDPTime / SymmetricSTDP still TODO
    oja_rule.mbt鈿?partialOja_rule.jl port: 100 Wilson-Cowan rate neurons + all-to-all RateSynapse (渭=1.2, p=1.0). 100ms sim: r[mean]=-0.019, r[max]=0.87
    stp_demo.mbt鈿?partialMarkram STP dynamics on a single forced-fire pre IF neuron: shows isolated spike (u=0.36, x=0.64, 蟻=0.2), paired-pulse 10ms (蟻=0.236, facilitation > depression at short ISI), 5-spike 50ms burst (x drops from 0.235鈫?.059, depression catches up), and full recovery (蟻 鈫?0.2 after 10s silence). Also includes a 200-spike train @ 50ms ISI: 蟻 converges to steady-state 鈮?0.21, u鈫?.907 (saturated facilitation, 蟿F=1500ms 鈮?ISI), x鈫?.022 (deep depression, 蟿D=200ms < ISI) 鈥?validates Markram 1998 analytical steady-state
    stp_onecell.mbt鈿?partialSimplified port of Mongillo2008 STP_onecell.jl. 6 bursts 脳 240 spikes at 8kHz drive u鈫?.9997 (fully facilitated), x鈫?.7e-7 (fully depleted), 蟻鈫?e-4. Documents that recovery requires integrate! to apply exp(-dt/蟿D); see stp_demo for the recovery phase
    lkd2014_adex.mbt鈿?partialLitwin-Kumar-Doiron 2014 vSTDP network using AdExSinExpParameter (LKD defaults: El=-70mV, Vt=-52mV, Vr=-60mV, 蟿m=20ms, R=1/15nS, At=10mV, 蟿e=6ms, 蟿i=2ms). Scaled 100脳 down (40 E + 10 I); manual pre鈫抪ost routing (SpikingSynapse is hardcoded to IF-to-IF); 1s sim: E[0]=6 Hz, I[0]=9 Hz
    simulation_speed.mbt鈿?partialPort of SpikingNeuralNetworks.jl/examples/simulation_speed.jl (AdEx + PoissonLayer benchmark, first part). Scaled 10脳 down (10 AdEx + 100 Poisson per pop); 1s sim; prints Poisson spike counts, AdEx spike counts, v checksum. The TripodHet / BallAndStick parts of the Julia example require multi-compartment neurons (TODO)
    AdEx network (full)鉁?doneAdExParameterHet (per-ne Vector{Float] vt/vr/el/tm/r/dt_slope/tw/a/b) + AdExHet population + step_adex_het (4 tests pass). Matches Julia's AdExParameter{Vector{Float32}} + update_neuron! for the Vector variant
    AdExSinExp (single-exp synapse)鉁?doneAdExSinExpParameter (pub(all) struct, same fields as AdExParameter) + AdExSinExp population + step_adex_sinexp (same as step_adex) + adex_sinexp_step_synapses (single-exp: ge += glu; ge += dt*(-ge/蟿e); gi + gaba similar) + adex_sinexp_synaptic_current (same as AdEx). Auto-integrated via AdExSinExp_ in AnyPop. Matches Julia's AdExSinExpParameter + SingleExpSynapse. 6 tests + 1 new example (lkd2014_adex)
    Network experiments (Festa, Lagzi)鈴?TODOv0.7.5+
    Tripod / BallAndStick neurons鈴?TODOv0.7.5+
    Dendrite (passive compartment)鉁?doneDendrite struct (per-ne El/C/gax/gm/l/d/gax_parent) + Dendrite::new (Julia defaults: El=-70.6mV, C=10pF, gax=10nS, gm=1nS, l=150渭m, d=4渭m) + Dendrite::custom(...) + dendrite_step (passive forward-Euler: (El-v)*gm + (v_parent-v)*gax + i_ext) / C) + g_axial/g_mem/c_mem helpers (Julia G_axial/G_mem/C_mem formulas). Foundational building block for BallAndStick (1 dendrite) and Tripod (2 dendrites) multi-compartment neurons (7 tests pass)
    BallAndStick (soma + 1 dendrite)鉁?doneBallAndStick struct (soma AdEx + passive dendrite via Dendrite::new + single-exp synapses on both + Heun predictor-corrector integration) + step_ballandstick (bit-exact port of Julia BallAndStick integrate!: update_synapses 鈫?synaptic_current 鈫?2脳 update_neuron! 鈫?Heun averaging 鈫?spike detection on soma with ap_membrane=10mV per Julia's PostSpike). 6 tests pass
    Tripod (soma + 2 dendrites)鉁?doneTripod struct (soma AdEx + 2 passive dendrites via Dendrite::new + single-exp synapses on soma+d1+d2 + Heun predictor-corrector for 4 螖v components per neuron: dv_s, dv_d1, dv_d2, dw_s) + step_tripod (bit-exact port of Julia Tripod integrate!: soma axial current = ic1+ic2 = -(v_d - v_s) * gax for both dendrites). 6 tests + 1 new example (tripod)
    Metaplasticity (homeostatic weight normalization)鉁?doneMultiplicativeNorm (渭[i] = (W0[i]-W1[i])/W1[i]; W *= (1+渭)) + AdditiveNorm (渭[i] = W0[i]-W1[i]; W += 渭) + NormParam enum + SynapseTarget struct + SynapseNormalization::new(targets, param) (captures W0[i] at construction) + metaplasticity_step(norm). 6 tests + 1 new example (metaplasticity)
    examples/ballandstick + examples/dendrite鉁?donev0.10.16: first two standalone single-neuron examples for the multi-compartment infrastructure. examples/ballandstick runs 1 BallAndStick neuron (AdEx soma + 1 passive dendrite) with 1500 pA tonic drive for 1 s; final v_s/v_d + spike count + spike envelope. examples/dendrite runs 1 passive Dendrite with parent held at -50 mV and 50 pA current injection for 200 ms then 0 pA (passive relaxation); final v_d + peak v_d. Both also exercise the v0.10.16 Heun fix: predictor/corrector extrapolate v + dv*dt (not v + dv), spike detection runs before Heun apply with predictive criterion v_s + corrector_dv*dt >= -10mV (Julia's exact form), fire && continue skips the v_d apply (prevents corrector exp_term explosion from polluting v_d), and tabs_steps = round(Int, (up + 蟿abs) / dt) (Julia's full refractory window; previously half 鈥?only 蟿abs was used). The same fix is back-ported into Tripod
    Heun fix (BallAndStick + Tripod)鉁?donev0.10.16: re-aligned step_ballandstick and step_tripod apply loops with Julia's exact per-neuron order (decrement tabs 鈫?update threshold 鈫?refractory branch with v_d += dt*(v_s-v_d)*gax/C 鈫?active branch with predictive fire detection 鈫?fire overrides continue). Fixes v_d runaway when soma spikes (predictor dv_s explodes exp_term, corrector dv_d inherits the bogus extrapolation, v_d was being updated before spike detection)
    HetRec (heterogeneous-timescale non-recurrent layer)鉁?donev0.10.17: HetRecParameter (Nd/overlap/蟿d-low/蟿d-high/rate-low/rate-high/蟿abs/steepness/蟿m/蟿rate) + HetRec struct (v_d/v_s/is_/r/tau_d/fire/tabs/trace/randcache + sparse CSC colptr/i_syn/w_syn) + HetRec::new (samples r and 蟿d from Uniform, builds sparse M with own-dendrite-always + cross-dendrite-per-overlap) + hetrec_refresh_random + step_hetrec (Euler: v_d += dt*(-v_d-is)/蟿d; soma: v_s += (W路v_d - v_s)dt/蟿m per synapse; stochastic fire: rand < rsigmoid(steepness路(v_s-trace))路dt; 蟿abs refractory). Auto-integrated via HetRec_ in AnyPop. 8 tests + 1 new example (hetrec)
    examples/wilson_cowan鉁?donev0.10.18: standalone single-population Wilson-Cowan rate-model example. 20 WilsonCowan + all-to-all self-RateSynapse (渭=0.5, p=1.0) + I=0.2 tonic drive; 100 ms sim; samples r at t=0/12.5/50/99.9 ms to show transient 鈫?fixed-point convergence (meanr鈮?.21, r[0]=-0.06鈫?.34). Exercises the rate-mode forward (pre.r 鈫?post.g via sparse matrix)
    examples/spikesynapse鉁?donev0.10.19: minimal 2-IF + 1-SpikingSynapse network (E1 鈫?E2 onto :ge, 渭=1.62 nS, p=1.0, delay=Normal(3ms, 2ms)). E1 driven by 200 pA tonic current; E2 receives the synaptic input via the pending-event queue + delay (built v0.10.3). Result: E1 fires 381 spikes, E2 fires 379 spikes (1:1 with small loss to delay + refractory). Demonstrates the manual sim loop pattern (deliver_pending_synapse 鈫?forward_synapse 鈫?step_synapses 鈫?synaptic_current 鈫?step_neuron)
    STTC (Spike-Time Tiling Coefficient) analysis鉁?donev0.10.20: analysis_sttc.mbt (new) 鈥?tile_fraction (sorted train, 卤dt intervals union, divided by T+2dt) + coincident_fraction (binary-search in B for each spike in A) + sttc_pair (symmetric formula 0.5*((PA-TB)/(1-PA*TB) + (PB-TA)/(1-PB*TA))) + sttc_matrix (N脳N symmetric, diagonal=1) + sort_floats helper. 13 tests + 1 new example (sttc)
    ISI / CV2鉁?doneanalysis_isi.mbt 鈥?port of spikes.jl::ISI_CV2 (Holt et al. 1996). isi_cv2_one(spike_times) single-neuron CV2 (handles 0-spike / 1-spike / 2-spike / NaN edge cases); isi_cv2(spike_times_per_neuron) per-neuron CV2 array. CV2 bounded by [0, 2] (9 tests pass)
    LIF closed-form verification鉁?doneanalysis_lif_closedform_test.mbt 鈥?closed-form LIF analytical verification. For an IF neuron with constant input I, the membrane voltage follows V(t) = V_rest + I*R*(1 - exp(-t/蟿m)). Verifies that the MoonBit simulator matches the analytical solution to within forward-Euler drift tolerance (4 tests pass: decay transient, steady-state, decay-to-rest, 蟿m time-constant). This serves as a "reference-output" check analogous to a Julia trajectory comparison (4 tests pass)
    PoissonLayer (population of N Poisson sources)鉁?donev0.10.21: PoissonLayer struct (pub(all); rate / n_sources / active / 渭 / 蟽 / p / dist / rule) + 4 constructors (new, with_n, with_active, with_conn) + PoissonLayerStimulus (wraps IF target + per-(pre, post) sparse weights drawn at construction via Box-Muller for Normal or Fixed for :Fixed) + stimulate_layer(s, t, dt) (per-step: reset fire buffer, sample Poisson per active source, apply weight to glu/gaba if fires). Auto-integrated via PoissonLayer_ in AnyStim. Mirrors Julia's PoissonLayer + Stimulus + stimulate! pattern (11 tests + 1 new example)
    examples/poisson_layer鉁?donev0.10.21: minimal port of poisson_layer.jl. 10 Poisson sources @ 2 Hz 鈫?200 IF (El=-49mV, vr=-60mV, vt=-50mV); Normal(渭=2.0, 蟽=1.0) weights, p=0.2 connectivity. Manual sim loop (stimulate_layer 鈫?step_synapses 鈫?synaptic_current 鈫?step_neuron) for 1 s at dt=0.125 ms. 410 actual connections (expected ~400); total 5322 spikes 鈫?26.6 Hz mean rate per neuron (high because El is only 1 mV below vt)
    examples/spike_analysis鉁?donev0.10.22: exercises vecplot analysis infrastructure (Monitor::count_spikes / firing_rate / spike_times / count_spikes_interval / firing_rate_interval / dump_summary / ascii_plot) on a small driven IF population (20 neurons, El=-49mV, 500ms sim). Per-neuron spike counts (135鈥?73 spikes), rates (270鈥?46 Hz), spike times, sub-interval analysis, v summary, ASCII plot
    Compartment-targeted SpikingSynapse鉁?donev0.10.23: CompartmentSynapseBall (targets :soma or :d of BallAndStick) + CompartmentSynapseTripod (targets :soma, :d1, or :d2 of Tripod) 鈥?same CSR matrix + delays + pending queue + rho-scaling semantics as SpikingSynapse, but routes incoming weights to a single compartment buffer. forward_compartment_ball / forward_compartment_tripod / deliver_pending_compartment_ball / deliver_pending_compartment_tripod (11 tests + 1 new example). First piece of the multi-compartment synapse infrastructure that unblocks tripod_network.jl / tripod_current.jl / stimuli.jl. Note: multi-receptor (AMPA + NMDA / GABA_A + GABA_B) is still TODO
    TripodHet (heterogeneous-parameter Tripod)鉁?donev0.10.24: TripodHet struct (soma AdExParameterHet + 2 shared dendrites + Heun predictor-corrector that reads per-neuron vt/vr/el/tm/r/dt_slope/tw/a inside the per-neuron loop) + TripodHet::new(n, soma_param, rng) + step_tripod_het (same Julia integrate! order as step_tripod but with per-ne arrays). 8 tests + 1 new example (tripod_het). Mirrors Julia's Tripod(..., param=AdExParameter{Vector{Float32}}). Final unblocker before tripod_network.jl / multipod.jl / tripod_current.jl (still need NMDA multi-receptor)
    NMDA primitives (multi-receptor + voltage-dependent gating)鉁?donev0.10.25: receptor.mbt (new) 鈥?NMDAVoltageDependency (Eyal/Soma defaults) + nmda_gating(v, dep) (B(v) = 1/(1 + (mg/b)exp(kv))) + Receptor (e_rev / tau_r / tau_d / g0 / gsyn / alpha / inv / is_nmda / target) + Receptors (4-element collection AMPA/NMDA/GABAa/GABAb) + step_receptor(g, h, target, r, dt) (2-state ODE: h += target伪; g = exp(-dt/蟿d鈦?(g + dth); h = exp(-dt/蟿r鈦?h; consumes target) + receptor_current(g, v, r, nmda, out) (sums I = gsyng(v-e_rev)*B(v) for NMDA, no B for others). 10 tests + 1 new example (nmda). Foundation for tripod_network.jl / tripod_current.jl / stimuli.jl
    ReceptorSynapse for TripodHet (multi-compartment + multi-receptor wiring)鉁?donev0.10.26: connection_receptor_tripod.mbt (new) 鈥?ReceptorSynapseTripod struct (pre IF, post TripodHet, CSR matrix, target_compartment 鈭?{soma, d1, d2}, Receptors collection, NMDAVoltageDependency, g_state[N脳4], h_state[N脳4], ge_out[N], gi_out[N]) + forward_receptor_tripod_synapse(s, target_receptor, t_now) (AMPA/NMDA 鈫?glu; GABAa/GABAb 鈫?gaba; routes to the right compartment buffer) + step_receptors_tripod_synapse(s, dt) (runs 2-state ODE for each of the 4 receptors on the target compartment, applies NMDA gating when is_nmda; sums into ge_out/gi_out). 6 tests + 1 new example (tripod_receptor). Wires the NMDA primitives into TripodHet, enabling tripod_network.jl / tripod_current.jl / stimuli.jl ports
    PoissonLayerStimulusTripod (Poisson Stimulus on TripodHet compartments)鉁?donev0.10.27: stimulus_poisson_layer_tripod.mbt (new) 鈥?PoissonLayerStimulusTripod struct (param : PoissonLayer, post : TripodHet, weights[N_post 脳 n_sources], connectivity, target_compartment 鈭?{soma, d1, d2}, target_kind 鈭?{glu, gaba}, rng) + stimulate_layer_tripod(s, t, dt) (per-step: per active Poisson source, sample Poisson(rate*dt); if fires, add weights[j, i] to the target compartment buffer). 7 tests + 1 new example (tripod_current 鈥?scaled-down port of tripod_current.jl: 4 TripodHet + 20 Poisson exc @ 20 Hz + 20 Poisson inh @ 3 Hz, both on :d1, 500 ms sim). Final piece of the tripod_current.jl infrastructure stack (alongside ReceptorSynapseTripod)
    change_plasticity! (runtime STDP parameter swap)鉁?donev0.10.28: Made param field mutable on STDPEntry / STDPEntryMexicanHat / STDPEntryAntiSymmetric + STDPEntry::change_plasticity(e, new_param) / STDPEntryMexicanHat::change_plasticity(e, new_param) / STDPEntryAntiSymmetric::change_plasticity(e, new_param) 鈥?runtime swap of LTP parameters (preserves vars). 5 tests + 1 new example (change_plasticity). Mirrors Julia's change_plasticity!(syn; LTP = STDPConfavreux2025()) from with_plasticity.jl. Note: still need to port STDPConfavreux2025 + iSTDPRate + iSTDPPotential + vSTDPParameter + MarkramSTPParameterTimestep (rule bodies) for the full with_plasticity.jl test
    PoissonLayerStimulusBallAndStick (Poisson Stimulus on BallAndStick :soma/:d)鉁?donev0.10.29: stimulus_poisson_layer_ballandstick.mbt (new) 鈥?PoissonLayerStimulusBallAndStick struct (param : PoissonLayer, post : BallAndStick, weights[N_post 脳 n_sources], connectivity, target_compartment 鈭?{soma, d}, target_kind 鈭?{glu, gaba}, rng) + stimulate_layer_ball(s, t, dt). 6 tests + 1 new example (stimuli 鈥?simplified port of stimuli.jl: 1 BallAndStick + 1 TripodHet + 4 Poisson Stimuli on :d / :d1). Final piece of the stimuli.jl infrastructure stack (alongside PoissonLayerStimulusTripod)
    vSTDPParameter (Litwin-Kumar-Doiron 2014 voltage-dependent STDP)鉁?donev0.10.30: stdp_vstdp.mbt (new) 鈥?VstdpParameter struct (a_ltd / a_ltp / theta_ltd / theta_ltp / tau_pre / tau_post / w_max / w_min) + VstdpVariables::new(n_pre, n_post) + vstdp_step(vars, param, pre_v, post_v, pre_fire, post_fire) (LTD on pre-fires when v_post > 胃_LTD; LTP on post-fires when v_pre > 胃_LTP; weight clamped to [w_min, w_max]) + vstdp_plot(param) ASCII visualization of the (v_pre, v_post) rule. 9 tests + 1 new example (vstdp). Mirrors Julia's vSTDPParameter from plasticity_params.jl. Note: struct renamed from vSTDPParameter to VstdpParameter because MoonBit requires type names to start with uppercase
    BalancedStimulus (feedback-driven inhomogeneous Poisson)鉁?donev0.10.31: stimulus_balanced.mbt (new) 鈥?BalancedParameter struct (kIE / beta / tau / r0 / w / wIE / same_input) + BalancedStimulus::new(pop, sym_e?, sym_i?, param?, seed?) (hooks into pop.glu / pop.gaba) + stimulate_balanced(s, t, dt) (inhomogeneous Poisson on :gi at rate r0*kIE; per-neuron rate adaptation r[i] += (r0 - Erate) / 400ms * dt driven by low-pass-filtered noise noise[i] = (noise[i] - re) * (1 - dt/蟿) + re; Poisson at rate Erate writes to :ge). Same_input=true broadcasts a single trace across all neurons. 9 tests + 1 new example (balanced 鈥?port of balanced.jl: 200 IF @ El=-49mV + BalancedParameter(kIE=2, 尾=0.1, 蟿=100ms, r0=2kHz, wIE=2, same_input=true); 1s sim, total 81295 spikes, mean 406 Hz/neuron). Wired into compose.mbt via new BalancedIF_ enum variant. Mirrors Julia's BalancedStimulus(E, :ge, :gi; param=BalancedParameter()) from stim/balanced.jl
    STDPConfavreux2025 (Confavreux 2025 STDP with 伪/尾 baseline dependencies)鉁?donev0.10.32: stdp.mbt (extended) 鈥?STDPConfavreux2025 struct (eta / alpha / beta / kappa / gamma / tau_pre / tau_post / w_max / w_min) + STDPEntryConfavreux2025 (mutable param, STDPVariables, t_now) + change_plasticity(entry, new_param) runtime swap + stdp_confavreux_step(w, pre_fire, post_fire, colptr, rowptr, vars, param, t_now, dt) (continuous tpre/tpost decay + spike bump, then single fused connection loop: pre-fire contributes eta * (kappa * 螖post[i] + alpha), post-fire contributes eta * (gamma * 螖pre[j] + beta); clamp to [w_min, w_max]). 9 tests + 1 new example (stdp_confavreux 鈥?port of with_plasticity.jl "STDPConfavreux2025" testset: 4脳4 dense synapse, 100 steps random pre/post firing; verifies weights stay finite and within bounds). Wired into compose.mbt via new Confavreux2025_(...) variant in STDPEntryKind. Mirrors Julia's STDPConfavreux2025 from STDP_traces.jl
    IstdpRate (Vogels 2011 inhibitory STDP with rate homeostasis)鉁?donev0.10.33: istdp.mbt (new) 鈥?IstdpRate struct (eta / r / tau_y / w_max / w_min) + IstdpRateVariables (just tpre / tpost arrays, no last_* bookkeeping) + IstdpRateEntry (mutable param, vars, t_now) + change_plasticity(entry, new_param) runtime swap + istdp_rate_step(w, pre_fire, post_fire, colptr, rowptr, vars, param, t_now, dt) (continuous tpre/tpost decay t += dt*(-t)/tau_y + spike bump, then fused connection loop: pre-fire contributes eta * (tpost[i] - 2*r*tau_y), post-fire contributes eta * tpre[j]; clamp to [w_min, w_max]). 10 tests + 1 new example (istdp_rate 鈥?port of with_plasticity.jl "iSTDPRate" testset: 4脳4 dense synapse, 100 steps random pre/post firing; final weights stay finite within [0.01, 243]). Wired into compose.mbt via new IstdpRate_(...) variant in STDPEntryKind. Mirrors Julia's iSTDPRate from iSTDP.jl. Note: struct renamed from iSTDPRate to IstdpRate because MoonBit requires type names to start with uppercase
    IstdpPotential (Vogels 2011 inhibitory STDP with potential-based post trace)鉁?donev0.10.34: istdp.mbt (extended) 鈥?IstdpPotential struct (eta / v0 / tau_y / w_max / w_min) + IstdpPotentialVariables (tpre / tpost arrays) + IstdpPotentialEntry (mutable param, vars, t_now) + change_plasticity(entry, new_param) runtime swap + istdp_potential_step(w, pre_fire, post_fire, colptr, rowptr, v_post, vars, param, t_now, dt) 鈥?tpre[j] decays toward 0; tpost[i] is a low-pass filter of v_post[i] (decays toward v_post with time constant tau_y, plus +1 bump on post-spike). Weight update: pre-fire contributes eta * (tpost[i] - v0); post-fire contributes eta * tpre[j]; clamp to [w_min, w_max]. 10 tests + 1 new example (istdp_potential 鈥?port of with_plasticity.jl "iSTDPPotential" testset: 4脳4 dense synapse, 100 steps random pre/post firing + varying v_post in [-70, 50] mV; final weights stay finite within [0.01, 243]). Wired into compose.mbt via new IstdpPotential_(...) variant in STDPEntryKind which threads syn.post.v into the step function. Mirrors Julia's iSTDPPotential from iSTDP.jl. Note: struct renamed from iSTDPPotential to IstdpPotential because MoonBit requires type names to start with uppercase
    MarkramSTPParameterTimestep (continuous-time Euler Markram STP)鉁?donev0.10.35: stp.mbt (extended) 鈥?MarkramSTPParameterTimestep struct (u / tau_f / tau_d / w_max / w_min) + MarkramSTPEntryTimestep (uses same MarkramSTPVariables state) + markram_stp_step_timestep(syn, vars, param, t_now, dt) (per step: spike bumps u += U*(1-u), x += -u*x; then continuous Euler u += dt*(U-u)/tau_f, x += dt*(1-x)/tau_d for every pre-neuron; recompute rho_pre[j] = u[j] * x[j]; broadcast rho_pre to all outgoing connections via syn.matrix.rowptr[j]). 8 tests + 1 new example (stp_timestep 鈥?port of with_plasticity.jl "MarkramSTPParameterTimestep" testset: 4脳4 dense synapse, 200 steps random pre firing; verifies weights stay finite and rho / u stay in [0, 1]). Wired into compose.mbt via new MarkramSTPTimestep_(...) variant in STPEntryKind. Mirrors Julia's MarkramSTPParameterTimestep from STP.jl
    STDPSymmetric (Festa et al. 2024 zero-integral inhibitory STDP) + IstdpTime (parameter type only)鉁?donev0.10.36: stdp.mbt (extended) 鈥?STDPSymmetric struct (a_x / a_y / tau_x / tau_y / alpha_pre / alpha_post / w_max / w_min) + STDPSymmetricVariables (4 trace arrays: tr_x, tr_y, to_x, to_y) + STDPEntrySymmetric (mutable param, vars, t_now) + change_plasticity(entry, new_param) runtime swap + stdp_symmetric_step(w, pre_fire, post_fire, colptr, rowptr, vars, param, t_now, dt) 鈥?continuous decay of all 4 traces (tr_x, tr_y bump on pre-spike, to_x, to_y bump on post-spike); fused connection loop: pre-fire contributes alpha_pre + (a_x/(2*tau_x))*to_x[i] - (a_y/(2*tau_y))*to_y[i], post-fire contributes alpha_post + (a_x/(2*tau_x))*tr_x[j] - (a_y/(2*tau_y))*tr_y[j]; clamp to [w_min, w_max]. istdp.mbt (extended) 鈥?IstdpTime struct (eta / tau_y / w_max / w_min) 鈥?parameter type only, no step function (Julia's iSTDP.jl defines iSTDPTime but doesn't provide a separate step rule for it). 11 tests + 1 new example (stdp_symmetric 鈥?port of plasticity_params.jl "STDPSymmetric / STDPAntiSymmetric" testset: 4脳4 dense synapse, 100 steps random pre/post firing; verifies weights stay finite within [0, 30]). Wired into compose.mbt via new Symmetric_(...) variant in STDPEntryKind. Mirrors Julia's STDPSymmetric from STDP_structured.jl and iSTDPTime from iSTDP.jl
    TripodNetwork (multi-population E-I Tripod network wiring)鉁?done (partial)v0.10.37: examples/tripod_network/main.mbt (new) 鈥?port of test/network/tripod_network.jl. 20 Tripod (E, homogeneous AdEx soma) + 5 IF (I1, 蟿m=7ms) + 5 IF (I2, 蟿m=20ms) + CompartmentSynapseTripod for I1鈫扙 :soma with IstdpRate + I2鈫扙 :d1 with IstdpPotential. 500ms manual sim loop with forward 鈫?step_neuron 鈫?step_tripod 鈫?STDP!. Final: E soma fires at 1287 Hz/neuron, I1 at 422 Hz, I2 at 348 Hz. Partial port 鈥?E鈫扞1, E鈫扞2 routed via tonic current approximation (Tripod can't be pre in CompartmentSynapseTripod); E鈫扙 recurrent SKIPPED (same constraint); homeostatic SynapseNormalization + MultiplicativeNorm not ported yet. Demonstrates that the existing Tripod / CompartmentSynapseTripod / IstdpRate / IstdpPotential infrastructure composes correctly into a multi-population network
    Population + synapse parameter type sanity tests鉁?donev0.10.38: parameters_test.mbt (new) 鈥?port of test/pop/parameters.jl "Population parameters" + "Synaptic parameter types" testsets. 10 tests verifying field existence + Julia-matching defaults for IFParameter, AdExParameter, IZParameter, MorrisLecarParameter, HetRecParameter, PostSpike, AdExPostSpike, plus IF's tre/tde/tri/tdi time constants (DoubleExpSynapse-style) and Receptor/NMDAVoltageDependency constructors. Field names mapped from Julia camelCase to MoonBit lowercase (Vt鈫抈vt, 蟿m鈫抈tm, gCa鈫抈gca, Nd鈫抈nd, etc.). Demonstrates that all major parameter types have the expected property surface
    Synapse parameter types + vars + step functions鉁?donev0.10.39: synapse_params.mbt (new) 鈥?DeltaSynapse (empty), SingleExpSynapse (tau_e, tau_i, e_e, e_i, gsyn_e, gsyn_i), DoubleExpSynapse (tau_re/de/ri/di, e_e, e_i, gsyn_e, gsyn_i), CurrentSynapse (tau_e, tau_i) structs with Julia-matching defaults; companion *Vars structs with ge/gi/he/hi state arrays; step functions (delta_synapse_step, single_exp_synapse_step, double_exp_synapse_step, current_synapse_step) implementing the Julia update rules (instantaneous / single-exp decay / 2-state rise+decay / current-based decay); current functions (delta_synapse_current syn_curr=-(ge-gi) then reset; current_synapse_current syn_curr=-(ge-gi); conductance_synapse_current / double_exp_conductance_current syn_curr=ge*(v-E_e)gsyn_e + gi(v-E_i)*gsyn_i). 12 tests + 1 new example (synapse_params 鈥?constructs all four synapse types, runs a single step with synthetic input glu=[1,0,0,0], computes synaptic current at v=-50mV). Mirrors Julia's DeltaSynapse / SingleExpSynapse / DoubleExpSynapse / CurrentSynapse parameter types from synapses/
    SNNUtils.jl parameter constants (LKD2014 + Duarte2019)鉁?donev0.10.40: snn_utils_params.mbt (new) 鈥?LKD2014 struct (tm / vt / el / vr / r / tau_abs / e_i / e_e / at / pv_tm / pv_el / pv_vr / pv_vt) matching Julia's SNNUtils.LKD2014 named-tuple from lkd2014.jl. Duarte2019 struct (pv_, sst_, adex_* parameters) matching Julia's SNNUtils.duarte2019 named-tuple from duarte2019.jl. C/nS ratios computed manually as Float32 constants (300pF/15nS = 20.0ms for LKD2014; 104.52pF/9.75nS 鈮?10.72ms, 102.86pF/4.61nS 鈮?22.31ms, 116.5pF/4.64nS 鈮?25.11ms for Duarte2019). 3 tests + 1 new example (snn_utils_params 鈥?displays all parameters and demonstrates LKD2014 鈫?IFParameter::custom conversion). Mirrors Julia's SNNUtils.jl/src/models/lkd2014.jl + duarte2019.jl
    Simulation control (sim_control.jl port)鉁?donev0.10.41: sim_control_test.mbt (new) 鈥?port of test/sim/sim_control.jl. 6 tests covering get_time_heterogeneous (zero before sim, advances after sim_for), reset_time_heterogeneous (resets to zero), Monitor::new_fire spike accumulation over a 50ms sim, Monitor::new_v_sr sampling rate storage, and compose with multiple pops + conns. Uses the existing HeterogeneousModel infrastructure from compose.mbt
    Spatial utilities (spatial_test.jl port)鉁?donev0.10.42: spatial.mbt (new) 鈥?port of utils/spatial.jl. Point2D struct (x, y), PlacedPops struct (e/i). place_populations_e_i(n_e, n_i, grid_x, grid_y, rng) (random Float32 placement on a periodic grid via next_f32). periodic_distance_scalar(p1, p2, grid_size) (1D torus distance: min of |p1-p2| and grid_size-|p1-p2|). linear_network(n) + linear_network_with(n, sigma_w, w_max) (ring-shaped Gaussian weight matrix on circle of circumference 2pi). MoonBit parser limitation workaround: instead of returning Array[Array[Float]] (which fails to parse in our setup), return a flat row-major Array[Float] of length n*n; index via row_major_get(W, n, i, j). Diagonal entries set to 0. 4 tests + new example spatial (recommended in subsequent turn). Mirrors Julia's utils/spatial.jl (place_populations, periodic_distance, linear_network)
    AggregateScaling (aggregate_scaling.jl port)鉁?donev0.10.43: aggregate_scaling.mbt (new) 鈥?port of connections/metaplasticity/aggregate_scaling.jl. AggregateScalingParameter (蟿e/蟿a/蟿/Y/Wmin/Wmax) matching Julia's struct. AggregateScaling::new(n, targets, param) initialises WT[i] = sum of incoming weights. aggregate_scaling_forward(c, fire, dt) per Julia: y decays with 蟿a, bumps on fire[i] (Euler decay鈫抌ump鈫扺T-update order), drives WT[i] toward (1 - y[i]/Y[i])/Wmax with 蟿e. aggregate_scaling_plasticity(c, step_count) periodic variant: every 蟿/dt steps (default 80), recompute wt[i] = sum of weights, compute 渭[i] = (WT[i] - Wmin) / wt[i] (guarded to 1.0 when wt=0), rescale W[s] = W[s] * 渭[i] + Wmin. AggregateScaling::with_plasticity_interval(c, n) runtime override of the cadence. 9 tests + upgraded examples/aggregate_scaling/main.mbt to use the real infrastructure (was a placeholder). Final demo: 100 IF + 2074 EE connections + 1000 steps with weight perturbation + periodic homeostatic rescaling; y[0]=0.58, WT[0]=267.7, final mean weight 14.98
    LKD2014.PV demo (LKD.jl port)鉁?donev0.10.44: AdExParameter::custom(tm, vt, vr, el, r) constructor added to neuron_adex.mbt. examples/lkd2014_demo/main.mbt (new) 鈥?uses LKD2014.PV parameter values (tm=20ms, El=-62mV, Vt=-52mV, Vr=-57.47mV, R=0.0667) to build a single IF neuron, drives it with a single scheduled spike at t=1000ms via SpikeTimeStimulus (param spiketimes=[1000.0F], neurons=[0]), runs 1200ms @ dt=0.125ms, prints final v[0]/glu[0]/fire[0]. Final state: v=-61.999 (El unchanged since the single spike wasn't strong enough to push v above Vt=-52 with R=0.0667 脳 p路gsyn_e=1.0路2 nS=0.13 nS). AdEx side of LKD.jl omitted (SpikeTimeStimulus currently only accepts IF populations; full two-population network requires TimedStimulusAdEx variant). Mirrors refs/SNNUtils.jl/test/LKD.jl partial port
    metaplasticity.jl port (extended coverage)鉁?donev0.10.45: metaplasticity_test.mbt (extended) 鈥?added 3 tests from refs/SNNModels.jl/test/syn/metaplasticity.jl: SynapseNormalization AdditiveNorm form (W0 sum + no-shrinkage step), W0 sums from full connectivity (4脳4 synapse with 渭=1 gives W0=4 per post), metaplasticity_step multiplicative shrinkage+restore (W0=[4,6] captured, shrink to half 鈫?step restores via 渭=1 scaling), additive shrinkage correction (additive applies 渭[i] to all synapses connecting to post i, NOT a "restore" 鈥?over-corrects when multiple synapses target the same post). RandomTurnover and ActivityDependentTurnover constructors NOT ported (structural plasticity TODO 鈥?separate work). 4 tests + 1 example
    set_LTP! / set_STP! (runtime LTP/STP toggle)鉁?donev0.10.46: port of refs/SNNModels.jl/test/syn/plasticity_params.jl "set_LTP! / set_STP!" testset (lines 179-190). stdp_step now gates on vars.active[0] at the top (mirrors how markram_stp_step already did). Three new helpers: STDPEntry::set_ltp_active(entry, active), MarkramSTPEntry::set_stp_active(entry, active), MarkramSTPEntryHet::set_stp_active(entry, active), MarkramSTPEntryTimestep::set_stp_active(entry, active) 鈥?each mutates entry.vars.active[0] in place. MoonBit identifiers drop Julia's trailing !. Round-trip toggle preserves vars state (traces + spike times survive a false鈫抰rue cycle). MarkramSTPEntryHet::new constructor now used (was: struct-literal which MoonBit rejects without pub(all)). MarkramSTPParameterHet::homogeneous(n_pre) is the public constructor for the het struct. 8 tests + 0 examples
    SynapseTarget::post_id (post-population identifier)鉁?donev0.10.47: SynapseTarget gained a post_id : Int field 鈥?an explicit identifier for which post-neuron population the buffer targets. Mirrors Julia's targets[:post] lookup (which uses a NamedTuple key; we use a caller-supplied integer because MoonBit has no NamedTuple equivalent). All existing fixtures updated to set post_id: 0 (or post_id: N for new tests). 2 new tests verify the field is captured by SynapseNormalization::new and accessible via norm.targets[i].post_id. Limitation: Julia's @assert length(unique(posts)) == 1 runtime check is NOT ported because MoonBit's abort is a polymorphic bottom type that the type system doesn't track as a raise site (making try...catch ergonomics poor). The caller is expected to verify post-population consistency before calling ::new. 2 tests + 0 examples
    metaplasticity_step_gated (periodic 蟿 gating)鉁?donev0.10.48: added metaplasticity_step_gated(norm, step_count, dt) 鈥?periodic form that mirrors Julia's outer plasticity!(c, param, dt, T) which gates on ((tt) % round(Int, 蟿 / dt)) < dt. Fires only when step_count % round(蟿/dt) == 0. 蟿=0 disables periodic firing (matches Julia default). Caller maintains the step counter (just pass i in your sim loop). 2 tests verify gate behavior: with 蟿=0.5ms and dt=0.125ms, period=4 steps, only steps 0/4/8 fire. 蟿=0 test verifies the gate stays closed. 2 tests + 0 examples
    util.mbt (utils/util.jl port)鉁?donev0.10.49: port of SNNModels.jl/src/utils/util.jl non-graph helpers. rand_value(n, p1, p2, rng) 鈥?uniform Float32 values in [min(p1,p2), max(p1,p2)]; degenerates to constant when p1==p2. exp32(x) / exp64(x) / exp256(x) 鈥?fast exp approximations via repeated squaring (Julia-side performance helper; we use libm expf directly but expose these for API parity). name(pre, post, k?) / name2(pre, post) 鈥?Symbol/String name generators for connection labels (MoonBit: returns String, not Symbol). str_name(pre, post, k?) / str_name_single(pre, k?) 鈥?String variants (matching Julia's (::String, k?) overload). f2l(s, l?) / f2l_default(s) 鈥?pad/truncate string to exact length l (default 10). 21 tests in util_test.mbt cover all helpers. Note: the full compose and print_model from util.jl are NOT ported 鈥?MoonBit's compose in compose.mbt already provides the heterogeneous-model builder; print_model is graph-based and out of scope. 21 tests + 0 examples
    turnover.jl (structural plasticity)鉁?donev0.10.50: port of SNNModels.jl/src/connections/metaplasticity/turnover.jl (structural plasticity). RandomTurnover(rate, threshold, mu) and ActivityDependentTurnover(rate, fraction, tau_pre, tau_post, mu) constructors + Turnover struct + synaptic_turnover!(syn, p_rewire?, mu?, p_values?) rewiring logic. turnover_plasticity!(c, step_count, dt) periodic gate (fires every 蟿/dt steps). quantile_float helper for the activity-dependent threshold. Note: p_new callback is ignored (MoonBit has no first-class function references 鈥?new posts are sampled uniformly). SpikingSynapse's actual field names are matrix.vals / matrix.colptr / matrix.rowptr (NOT W / I). 12 tests in turnover_test.mbt cover both variants + periodic gate + empty-array quantile. 12 tests + 0 examples
    Multipod (variable-dendrite-count Tripod)鉁?donev0.10.51: port of SNNModels.jl/src/populations/multicompartment/multipod.jl 鈥?Multipod struct with variable nd (number of dendrites), per-dendrite voltage arrays, 4-receptor per-dendrite per-neuron conductance buffers (AMPA/NMDA/GABAa/GABAb), Euler integrator. MultipodParameter::new(dendrites) / ::uniform(d, nd) constructors. Multipod::step(p, dt) Euler update with soma_syn + dend_syn receptors, axial currents, soma+dendrite ODEs, spike detection. Note: simplified port 鈥?no full NMDA voltage gating or Heun corrector (those are TODO). Euler step has a stack-overflow issue on long simulations (likely the flat-index nested loops over g_d[i, d, n]); the Multipod::step runtime tests are marked as TODO pending a flat-loop refactor. 10 tests in neuron_multipod_test.mbt cover constructors + shape allocations + initial-state finiteness + receptor-marker preservation. 10 tests + 0 examples
    Multipod (step refactor + 3 additional shape tests)鉁?donev0.10.52: refactored Multipod::step body into smaller helper functions (multipod_step_neurons + multipod_sum_cs) to flatten the call stack. The helper-function refactor didn't fix the runtime stack overflow (MoonBit's JIT still overflows on the d 脳 k 脳 i flat-index nested loops), so the runtime tests remain deferred. Added 3 additional constructor-shape tests: Multipod::new 鈥?w_s, he_s, hi_s, ge_s, gi_s zero (verify all soma spike-input + conductance buffers are zero-initialised), Multipod::new 鈥?after_spike field shape + he_d/hi_d zero (verify dendrite spike-input buffers), Multipod::new 鈥?g_d / h_d / dv / dv_temp / cs / iv lengths (verify scratch buffer lengths scale with N 脳 nd 脳 4). 3 tests + 0 examples
    Multipod (g_d / h_d nested-array refactor)鉁?donev0.10.53: refactored g_d / h_d from a flat Array[Float] (indexed as g_d[i + d*N + n*N*nd]) to a nested 3-level Array[Array[Array[Float]]] (indexed as g_d[d][i][n]). The flat-index nested loops were the root cause of MoonBit's JIT stack overflow; the nested version has the same memory layout (4 脳 N 脳 nd floats) but the inner loop is now a contiguous slice which the JIT handles better. Note: even with the nested refactor, Multipod::step still stack-overflows on runtime tests (likely a different JIT limitation in this version of MoonBit). 3 new tests verify the nested shape: Multipod::new 鈥?g_d[d][i][n] nested zero-init across all 3 levels (24 cells verified), Multipod::new 鈥?g_d / h_d are independent per-dendrite slices (setting one dendrite doesn't affect another), Multipod::new 鈥?receptor index 3 (GABAb) accessible (boundary index reachable). 3 tests + 0 examples
    structs_extra.mbt (EmptyParam + NetworkModel + Time::from_ms)鉁?donev0.10.54: port of additional SNNModels.jl/src/utils/structs.jl helpers. EmptyParam struct with p_type : String field (mirrors Julia's EmptyParam with type::Symbol = :empty; we use a String instead of Symbol since MoonBit has no first-class Symbols). EmptyParam::new() defaults to "empty", EmptyParam::with_type(p_type) customises, EmptyParam::type_str(p) accessor. NetworkModel placeholder (MoonBit's HeterogeneousModel is the real port 鈥?this stub exists for API documentation). isa_model(m) accessor. Time::from_ms(time) was already in time.mbt but got 2 dedicated tests (100ms 鈫?tt=800, 0.5ms 鈫?tt=4). 6 tests in structs_extra_test.mbt cover all helpers + Time::from_ms numerics. 6 tests + 0 examples
    HH_NMDA multi-receptor variant鈴?TODOv0.7.5+
    Identity neuron (Lagzi-style pass-through)鉁?doneIdentity::new(n, param), step_neuron_id(p, dt) 鈥?fires when g > 0 (3 tests pass)
    STP (Markram 1998) integration into compose鉁?donev0.10.4: MarkramSTPEntry + STPEntryKind enum, auto-integrated before forward (12 tests pass)
    Bit-exactness verification vs Julia鈴?TODOCompare spike trains / voltage traces against the Julia run for each example

    Test count: 1721 (all passing; v0.10.4 +12 STP tests, v0.10.6 +6 STP het tests, v0.10.7 +2 STP bit-exactness tests, v0.10.9 +4 AdEx het tests, v0.10.10 +7 AdExSinExp tests, v0.10.11 +3 IZ bit-exact regression tests, v0.10.12 +7 Dendrite tests, v0.10.13 +6 BallAndStick tests, v0.10.14 +6 Tripod tests, v0.10.15 +6 Metaplasticity tests, v0.10.16 Heun fix (no new tests), v0.10.17 +8 HetRec tests, v0.10.18 +0 tests (just example), v0.10.19 +0 tests (just example), v0.10.20 +13 STTC tests, v0.10.21 +11 PoissonLayer tests, v0.10.22 +0 tests (just example), v0.10.23 +11 CompartmentSynapse tests, v0.10.24 +8 TripodHet tests, v0.10.25 +10 NMDA receptor tests, v0.10.26 +6 ReceptorSynapseTripod tests, v0.10.27 +7 PoissonLayerStimulusTripod tests, v0.10.28 +5 change_plasticity tests, v0.10.29 +6 PoissonLayerStimulusBallAndStick tests, v0.10.30 +9 vSTDP tests, v0.10.31 +9 BalancedStimulus tests, v0.10.32 +9 STDPConfavreux2025 tests, v0.10.33 +10 IstdpRate tests, v0.10.34 +10 IstdpPotential tests, v0.10.35 +8 MarkramSTPParameterTimestep tests, v0.10.36 +11 STDPSymmetric + IstdpTime tests, v0.10.37 +0 tests (just example 鈥?TripodNetwork wiring), v0.10.38 +10 parameters_test tests, v0.10.39 +12 synapse_params tests, v0.10.40 +3 snn_utils_params tests, v0.10.41 +6 sim_control_test tests, v0.10.42 +4 spatial tests, v0.10.43 +9 aggregate_scaling tests, v0.10.44 +0 tests (just example 鈥?AdExParameter::custom + lkd2014_demo), v0.10.45 +4 metaplasticity_test extensions, v0.10.46 +8 set_plasticity_active tests, v0.10.47 +2 SynapseTarget::post_id tests, v0.10.48 +2 metaplasticity_step_gated tests, v0.10.49 +21 util.mbt tests, v0.10.50 +12 turnover_test tests, v0.10.51 +10 neuron_multipod_test tests, v0.10.52 +3 neuron_multipod shape tests, v0.10.53 +3 nested-array tests, v0.10.54 +6 structs_extra_test tests, v0.10.55 +8 sparse_matrix_extra_test tests, v0.10.56 +13 analysis_populations_test tests, v0.10.57 +10 connection_spiking_extra_test tests, v0.10.58 +7 parameters_extra_test tests, v0.10.59 +5 connection_receptor_test tests, v0.10.60 +7 neuron_inhomogeneous_poisson_test tests, v0.10.61 +9 analysis_isi_test tests, v0.10.62 +0 tests (cleanup), v0.10.63 +5 neuron_if_gsyn_test tests, v0.10.64 +0 tests, +1 example (duarte2019), v0.10.65 +4 analysis_lif_closedform_test tests, v0.10.66 +4 chain_bitexact_test tests, v0.10.67 +6 analysis_smooth_test tests, v0.10.68 +4 inhomogeneous_poisson_bitexact_test tests, v0.10.69 example tuning (no new tests), v0.10.70 +4 adex_sinexp_bitexact_test tests, v0.10.71 +0 tests (cleanup pass), v0.10.72 +11 ca_plasticity_test tests, v0.10.73 +8 connection_pinning_test tests, v0.10.74 +9 dump_params_test tests, **v0.10.75 +9 neuron_extended_if_test tests (ExtendedIF 鈥?multi-receptor conductance-based IF from refs/SNNModels.jl/src/populations/generalized_if/if_extended.jl; 3 receptor buffers g_Exc/g_PV/g_SST with per-synapse 蟿e/蟿i + optional dendritic 伪-gating term -伪*g_Exc*g_SST*(E_e-v); matches Julia's update_neuron! exactly; 9 tests covering defaults + custom + decay + tonic drive + fire + refractory + 伪-gating + integrate; +1 example examples/if_extended), v0.10.76 +9 neuron_if_canahp_test tests (IFCANAHP 鈥?Calcium-activated CAN/AHP IF from refs/SNNModels.jl/src/populations/generalized_if/if_CANAHP.jl; Ca dynamics + pCAN/pAHP gating + membrane; simplified to single combined syn_curr for MoonBit compatibility; 9 tests covering defaults + Ca drive + gating + membrane + fire/reset + Ca bump + integrate), v0.10.77 +8 neuron_gif_test tests (GIFParameter + GIF 鈥?Generalized Integrate-and-Fire superset of IF/AdEx from refs/SNNModels.jl/src/populations/generalized_if/{gif,if}.jl; 蟿r/蟿d are scalars here; full parameter struct includes 螖T/a/b/蟿w for the AdEx special case; 8 tests covering defaults + AdEx factory + state + pure-LIF + AdEx-fire adaptation + refractory + sub-threshold decay), v0.10.78 +10 neuron_adex_multi_timescale_test tests (AdExMultiTimescale 鈥?multi-timescale AdEx with dynamic spike threshold from refs/SNNModels.jl/src/populations/adex/adex_multitimescale.jl; per-receptor 蟿r/蟿d vectors + dynamic 胃[i] (incremented by At on spike, decays to Vt with 蟿t); 10 tests covering parameter defaults + new shapes + 2-state decay + per-receptor independence + synaptic_current routing + fire + refractory + integrate), v0.10.79 +8 double_exp_current_synapse_test tests (DoubleExpCurrentSynapse 鈥?current-based DoubleExp from refs/SNNModels.jl/src/populations/synapse/synapses/DoubleExpCurrentSynapse.jl; differs from DoubleExpSynapse (in synapse_params.mbt) by syn_curr = -(ge - gi) instead of ge*(v-E_e) + gi*(v-E_i); 8 tests covering defaults + vars init + Euler 2-state + exc/ihn routing + current formula + pure-exc + pure-inh + 100-step stability), v0.10.80 +11 dendneuron_parameter_test tests (DendNeuronParameter 鈥?multicompartment dendritic neuron parameter container from refs/SNNModels.jl/src/populations/multicompartment/dendneuron_parameter.jl + Physiology{Float32} (Ri/Rd/Cd) from dendrite.jl; DendriticTreeType enum (BallAndStick/Tripod/Multipod) inferred from ds.length(); tripod_param_new + ballandstick_param_new factory shortcuts matching Julia's TripodParameter / BallAndStickParameter; 11 tests covering physiology defaults + custom + ds/physiology/geometry defaults + auto-infer for 1/2/3+ dendrites + population_from_dend_neuron passthrough + Float32 storage), v0.10.81 +9 confraveux2025_synapse_test tests (Confavreux2025Synapse 鈥?multi-timescale AMPA/NMDA/GABA synapse from refs/SNNModels.jl/src/populations/synapse/synapses/Confraveux2025.jl; differs from DoubleExpSynapse (in synapse_params.mbt) by separate gAMPA/gNMDA/gGABA buffers with gNMDA coupled to gAMPA via 蟿NMDA and weighted current syn_curr = (伪*gAMPA + (1-伪)*gNMDA)*(v-E_e) + gGABA*(v-E_i); 9 tests covering parameter defaults + vars init + AMPA/GABA spike input + NMDA coupled to AMPA + decay + weighted AMPA+NMDA + GABA inhibitory + 伪-blend dominance + 1000-step stability), v0.10.82 +7 receptor_extra_test tests (Glutamatergic + GABAergic + alpha_synapse helper from refs/SNNModels.jl/src/populations/synapse/receptors.jl; Glutamatergic bundles ampa+nmda Receptor, GABAergic bundles gabaa+gabab; both ::new() defaults + ::custom(...) factory; 7 tests covering alpha_synapse formula + Glu default/custom + GABA default/custom + independent fields + Receptor consistency), v0.10.83 +7 infer_receptors_test tests (infer_receptors 鈥?index receptors by target field from Julia's infer_receptors(receptors::ReceptorArray)::NamedTuple; returns a ReceptorsByTarget struct with glu : Array[Int] + gaba : Array[Int] index arrays; 7 tests covering empty input + single glu/gaba + 4-receptor set + mixed order + unknown target dropped + count_by_target helper), v0.10.84 +2 receptor_extra_test tests (Receptors::from_pair 鈥?mirror Julia's Receptors(glu::Glutamatergic, gaba::GABAergic) constructor that builds a 4-receptor collection from a Glu + GABA pair; 2 tests covering default + custom), v0.10.85 +3 receptor_extra_test tests (receptors_current 鈥?sums AMPA+NMDA+GABAa+GABAb contributions via 4-receptor collection; 3 tests covering mixed-receptor sum + zero g_mat + pure AMPA contribution matches single receptor math)), v0.10.86 +1 example (examples/receptors_demo/main.mbt 鈥?exercises Glutamatergic + GABAergic + Receptors::from_pair + infer_receptors + alpha_synapse + receptors_current end-to-end on a 4-neuron IF population; outputs the 4-receptor current sum per neuron), v0.10.87 +4 get_synapse_symbol_test tests (get_synapse_symbol 鈥?maps symbol identifiers "glu"/"gaba"/"he"/"ge"/"hi"/"gi" to internal Float array handles; passthrough (returns the same String); 4 tests for each symbol passthrough), v0.10.88 +8 single_exp_synapse_test tests (SingleExpSynapse 鈥?single-exp decay synapse from refs/SNNModels.jl/src/populations/synapse/synapses/SingleExpSynapse.jl; uses SingleExpSynapseVars from synapse_params.mbt (already declared there 鈥?was the source of a "declared twice" error on first attempt); struct + step function single_exp_synapse_step updates ge/gi with dt * (-ge/tau_e) + glu and similar for gi; 8 tests covering defaults + vars init + exc-only + inh-only + mixed routing + tau dependence + spike-driven decay + 1000-step stability), v0.10.89 +8 stimulus_current_noise_test tests (CurrentNoise + stimulate_noise 鈥?current-injection with multiplicative noise I = (I_base + noise) * (1-伪) + I * 伪 from refs/SNNModels.jl/src/populations/synapse/stimulus/CurrentNoise.jl; 2-arg positional constructor CurrentNoise::custom(n, i_base, noise_sigma, alpha) (MoonBit rejects labeled n=); box_muller_unit for Float32 Box-Muller normal draws (uses Double path: next_f64 鈫?@math.ln 鈫?@math.sqrt 鈫?@math.cos 鈫?Float::from_double); 8 tests covering defaults + custom + noise mean + zero alpha + seeded reproducibility + set_i_base + I/O 2-arg constructor + custom 4-arg constructor), v0.10.90 +8 stimulus_group_test tests (StimulusGroup 鈥?container for grouping multiple stimuli from refs/SNNModels.jl/src/populations/synapse/stimulus/StimulusGroup.jl; 2-arg positional constructor new(name, elements) (same MoonBit labeled-arg limitation); set_active_all / set_active_one / is_active / tag helpers; 8 tests covering new + set_active_all + set_active_one + is_active + tag + combined + 2-arg new + accessors), v0.10.91 +4 empty_stimulus_test tests (EmptyStimulus 鈥?no-op stimulus from refs/SNNModels.jl/src/populations/synapse/stimulus/EmptyStimulus.jl; reuses existing EmptyParam from structs_extra.mbt (v0.10.54); stimulate_empty is a no-op; 4 tests covering new + param access + stimulate no-op + step no-op), v0.10.92 +8 receptor_types_test tests (receptor presets port 鈥?refs/SNNModels.jl/src/populations/synapse/receptor_types.jl: Eyal/Soma NMDA voltage-dependency presets + MilesGabaSoma/DuarteGluSoma single receptors + EyalGluDend/MilesGabaDend/SomaGlu/SomaGABA bundles + TripodSomaReceptors/TripodDendReceptors/SomaReceptors 4-receptor collections; 8 tests covering NMDA defaults + single-receptor fields + bundle field shape + 4-receptor collection shape + fresh-each-call no-aliasing), v0.10.93 +8 analysis_weight_test tests (average_weight + average_weight_dynamics 鈥?port of refs/SNNUtils.jl/src/analysis/weights.jl; column-major CSR walk per pre-neuron with post-pop filter; record : Array[Float] is flat row-major n_connections 脳 n_steps indexed via record[s * n_steps + t]; 8 tests covering empty/full/partial connectivity + pre/post pop filters + out-of-bounds + 3-timestep dynamics + zero-matched edges), v0.10.94 +6 analysis_EI_balance_test tests (kei_balance_per_neuron + kei_balance_population 鈥?port of refs/SNNUtils.jl/src/analysis/EI_balance.jl; per-neuron argmin over a flat row-major n_steps 脳 n_neurons voltage matrix; tolerance 2.3mV; 6 tests covering constant-target + per-neuron settling + out-of-tolerance + boundary + population mean + never-settles), v0.10.95 +8 analysis_protocols_test tests (get_poisson_spikes + logrange 鈥?port of refs/SNNUtils.jl/src/analysis/protocols.jl; Bernoulli spike sample if rand < rate*dt then 1.0F else 0.0F; log10-spaced array via @math.ln / @math.ln(10) / @math.exp then Float::from_double; 8 tests covering rate=0 / rate=dt=1 / seeded reproducibility / frequency approximation / logrange shape), v0.10.96 +11 bimodal_kernel_test tests (KDE + globalKDE + get_maxima + is_bimodal + count_maxima + critical_window + all_windows 鈥?port of refs/SNNUtils.jl/src/stimuli/balance_EI/bimodal_kernel.jl; Float64 throughout (KDE density loses precision in Float32); strict local-max detection data[i] > data[i-1] && data[i] > data[i+1] (plateaus don't count); 11 tests covering Gaussian peak / empty data / shape / plateaus / single vs bimodal / asymmetric peaks / counter shape), v0.10.97 +7 neuron_if_sinexp_test tests (IFSinExpParameter + IFSinExp 鈥?port of refs/SNNUtils.jl/src/models/lkd2014.jl LKD2014SingleExp.PV; IF neuron + single-exp synapse 蟿e=6, 蟿i=2; 2-arg constructor IFSinExp::new(n, IFParameter::new(), rng) + lkd_pv() defaults; 7 tests covering defaults + LKD PV preset + initial state + ge/gi single-exp decay (no he/hi) + fire threshold + refractory freeze + 100-step stability), v0.10.98 +7 analysis_poisson_input_test tests (poisson_input_single + poisson_input 鈥?port of refs/SNNUtils.jl/src/analysis/protocols.jl; inverse-CDF exponential ISI sampling 未 = -位路ln(1-u); Float64 path for ln; spike_train Array[Bool]; 7 tests covering rate=0 / 500Hz / 100Hz / reproducibility / strictly-increasing spike indices / matrix shape / inter-neuron independence), v0.10.99 +8 analysis_intervals_test tests (time_in_interval + start_interval + end_interval 鈥?port of refs/SNNUtils.jl/src/stimuli/sequence/sequence.jl; Array[Array[Float]] of [start, end]; 8 tests covering empty intervals / inclusive bounds / x outside / -1 sentinel), v0.10.100 +3 receptor_from_array_test tests (Receptors::from_array 鈥?port of refs/SNNModels.jl/src/populations/synapse/receptors.jl Receptors(rec::Vector{Receptor}) constructor; length-4 array 鈫?pub struct Receptors { rec : Array[Receptor] }; 3 tests covering length / shape parity with Receptors::new / index ordering), v0.10.101 +1 example (examples/izhikevich_balanced/main.mbt 鈥?200 RS IZ neurons driven by 1kHz Bernoulli Poisson injection via get_poisson_spikes(rate, dt, rng) from v0.10.95, demonstrates IZ spike-frequency adaptation; balancedstimulus IF-only so excitatory-only here), v0.10.102 +4 analysis_weight_test tests (weights_indices + update_weight 鈥?port of refs/SNNUtils.jl/src/analysis/performance.jl; weights_indices returns 0-based edge indices matching a pre/post-pop filter; update_weight multiplies matched edges by a factor in place; 4 tests covering full/partial filters + factor application + no-op), v0.10.103 +1 snn_utils_params_test tests (LKD2014Soma 鈥?soma-level connection parameter template port of refs/SNNUtils.jl/src/models/connections.jl lkd2014_soma; 4 (E鈫扙, E鈫扨V, PV鈫扙, PV鈫扨V) connection groups with p + 渭 values; 1 test covering defaults), v0.10.104 +1 snn_utils_params_test tests (Quaresima2023Dend 鈥?dendritic connection parameter template port of quaresima2023_dend; 9 connection groups with log-mean 渭 values; 1 test covering E鈫扙d exception + log(15.8)/log(1.4)/log(0.83) 渭 defaults), v0.10.105 +2 snn_utils_params_test tests (Quaresima2024UpDown + eyal_equivalent_nar(nar, 蟿d) 鈥?port of refs/SNNUtils.jl/src/models/quaresima_2024_updown.jl; AMPA/NMDA g0 scaled by NAR; 2 tests covering AMPA/NMDA g0 computation at NAR=1.8 + struct population), v0.10.106 +1 example (examples/izhikevich_net/main.mbt 鈥?Brunel-style 80 E + 20 I IZ network with EE/EI/IE/II synapses; per-step Gaussian input via box_muller; demonstrates EE鈫扲S + II鈫扚S network dynamics; 800/200 from Julia port reduced to 80/20 for runtime), v0.10.107 +3 neuron_iz_custom_test tests (IZParameter::custom 鈥?arbitrary (a, b, c, d) constructor matching Julia's IZParameter(; a, b, c, d); 蟿e, 蟿i, Ee, Ei default to IZParameter defaults; 3 tests covering round-trip + IZ::new + parity with rs()), v0.10.108 +5 analysis_EPSP_test tests (exc_peak + inh_trough 鈥?port of get_EPSP from refs/SNNUtils.jl/src/analysis/protocols.jl; flat row-major [n_steps 脳 n_compartments] voltage trace; 5 tests covering rise-then-fall / spiketime offset / rest offset / inhibitory dip / multi-compartment column selection), v0.10.109 +4 izhikevich_step_synapses_test tests (step_iz_synapses 鈥?standalone IZ synapse decay ge += dt*(-ge/蟿e); gi += dt*(-gi/蟿i); 4 tests covering decay math / no-touch v-u / 1000-step stability / parity with full step_iz), v0.10.110 +3 izhikevich_bitexact_custom_test tests (IZ bit-exact with custom (a, b, c, d) 鈥?FS vs RS firing-rate comparison + custom c=-70 reset verification + custom RS-like trajectory), v0.10.111 +5 infer_receptors_extra_test tests (count_glu + count_gaba + infer_receptors_from + drop-unknown-target 鈥?extensions to infer_receptors helpers; 5 tests covering separate counts / zero glu case / helper-parity / unknown-target drop / total count), v0.10.112 +6 analysis_EPSP_extensions_test tests (exc_peak_with_time + inh_trough_with_time + exc_peak_window + inh_trough_window 鈥?extensions of get_EPSP for time-of-peak + windowed peak detection; 6 tests covering peak value/time / plateau first-wins / trough value/time / windowed peak / empty-inverted-window), v0.10.113 +5 izhikevich_init_with_test tests (IZ::init_with + IZ::init_uniform 鈥?custom initial v/u arrays; supports restart-from-state; 5 tests covering per-neuron arrays / uniform state / fire+ge+gi zero-init / step_iz evolution / restart-continuity), v0.10.114 +5 analysis_EPSP_pair_test tests (epsp_pair + epsp_pair_window 鈥?single-pass exc_peak+inh_trough; 5 tests covering both peaks / monotonic trace / parity with individual helpers / bounded window / empty window), v0.10.115 +4 analysis_EPSP_window_extra_test tests (multi-compartment peak/trough column selection + boundary case where peak is at spiketime; 4 tests covering windowed multi-compartment / dip exclusion / first-sample-at-spik / 3-compartment peak), v0.10.116 +5 izhikevich_postspike_test tests (IZPostSpike + IZ::init_with_postspike 鈥?IZ absolute-refractory state mirroring IF/AdEx PostSpike; 5 tests covering default 1-step + custom N / initial state honours v/u / step_iz evolves / fire+ge+gi+i zero-init), v0.10.117 +5 izhikevich_postspike_step_test tests (step_iz_with_postspike 鈥?refractory-aware IZ step function: tabs countdown decrements while > 0, synapse decay continues during refractory, on fire tabs=tabs_const resets; 5 tests covering force-fire + tabs decrement + synapse-decay-during-refractory + tabs_const=0 鈮?step_iz + multi-neuron heterogeneous firing), v0.10.118 +5 izhikevich_julia_ge_gi_test tests (sample_julia_initial_ge_gi 鈥?bit-exact port of Julia's (1.5randn+4)*10nS and (12randn+20)*10nS initial ge/gi via Box-Muller; 5 tests covering length / means / seeded-reproducibility / non-negative clamp / ge vs gi distribution), v0.10.119 +5 analysis_EPSP_normalise_test tests (epsp_normalise 鈥?z-score normalisation v_normalised = (v - mean) / std over a flat row-major voltage window; 5 tests covering zero-std degenerate / mean鈮? unit-variance / multi-compartment column selection / invalid window returns empty / output length matches window), v0.10.120 +3 izhikevich_postspike_bit_exact_test tests (refractory timing verification: tabs countdown 2鈫?鈫? after single fire, tabs_const=5 鈫?5 refractory steps, second-fire re-extends refractory; 3 tests covering countdown / multi-step / re-fire-after-recovery), v0.10.121 +4 izhikevich_postspike_step_main_test tests (integration scenarios: 200-step stability / fire rate bounded by refractory / 5-neuron heterogeneous initial fire / u adaptation freezes during refractory; 4 tests covering long-run / tonic-vs-refractory / all-fire-once / u-frozen-during-refractory), v0.10.122 +5 izhikevich_synapses_postspike_test tests (step_iz_synapses_postspike 鈥?standalone synapse decay with refractory countdown; 5 tests covering decay-no-refractory / tabs-countdown-during-refractory / no-touch v-u / tabs_const=0 鈮?step_iz_synapses / multi-neuron heterogeneous), v0.10.123 +5 izhikevich_reset_test tests (iz_reset 鈥?restore IZ to initial state; clears v/u/fire/ge/gi/tabs but preserves i (external input); 5 tests covering v-u restoration / fire-ge-gi-tabs clear / i preserved / step-determinism-after-reset / FS-parameters-reset), v0.10.124 +5 analysis_EPSP_normalise_by_compartment_test tests (epsp_normalise_by_compartment 鈥?multi-compartment z-score; returns Array[Array[Float]] of length n_compartments; 5 tests covering per-column z-scores / invalid window / single-compartment parity / degenerate-compartment-zeros / different std per compartment), v0.10.125 +1 example (examples/izhikevich_postspike/main.mbt 鈥?3 RS IZ populations with different tabs_const (0/8/20); compares firing rates under 30 pA tonic drive), v0.10.126 +1 example (examples/izhikevich_balanced_postspike/main.mbt 鈥?100 RS IZ + tabs_const=4 refractory + 1 kHz Bernoulli drive; demonstrates how refractory caps maximum firing rate), v0.10.127 +5 izhikevich_reset_with_postspike_test tests (iz_reset_with_postspike 鈥?clear both IZ state AND refractory state, hot-swap tabs_const from IZPostSpike; promoted IZ and IZPostSpike to pub(all) with mut tabs / mut tabs_const for cross-file mutation; 5 tests covering state+tabs clear / tabs_const hot-swap / i preserved / step_iz_with_postspike after reset / multi-neuron), v0.10.128 +5 izhikevich_balanced_postspike_test tests (integration tests for examples/izhikevich_balanced_postspike; 5 tests covering refractory caps firing rate / spike-count ratio / tabs countdown through combined loop / single-neuron refractory / multi-neuron mixed tabs), v0.10.129 +5 izhikevich_EPSP_windowed_by_compartment_test tests (epsp_normalise_windowed_by_compartment 鈥?separate baseline window [win_start, win_end] for mean/std from observation window [t_start, t_end] for z-scores; 5 tests covering distinct windows / std > 0 / multi-compartment / invalid observation / invalid baseline), v0.10.130 +5 izhikevich_reset_test_bitexact_test tests (bit-exact reset verification: two parallel populations with same seed produce identical trajectories across reset boundary; uses == (not approx) on v/u; 5 tests covering same-seed determinism / post-reset parity / round-trip / FS params / custom IB-like params), v0.10.131 +5 izhikevich_postspike_step_synapses_test tests (integration tests combining step_iz_synapses_postspike and step_iz_with_postspike in the typical sim! loop pattern; bugfix in step_iz_with_postspike 鈥?changed tabs[i] >= 0 to tabs[i] <= 0 in loops for u-recovery and ge/gi-current contributions so that refractory correctly freezes ALL membrane updates (matches Julia's step_neuron! continue semantics); 5 tests covering tab countdown through combined loop / ge decays during refractory / multi-neuron heterogeneous tabs_const / injection between decay and step / 100-step deterministic round-trip)., v0.11.0 +17 conv2d_test / maxpool2d_test tests (881 → 898) — first CNN primitives: conv2d.mbt with Conv2dParam + conv2d_forward (NCHW row-major flat Array[Float], naive 7-nested-loop, stride/pad, zero-padding, bias broadcast); maxpool2d.mbt with MaxPool2dParam + maxpool2d_forward (NCHW; out-of-bounds = -inf; stride defaults to kh). Forward-only, no new external deps. Examples runnable: 72 of 50+ (chain, adex, if_neuron, izhikevich, izhikevich_debug, hh_neuron, morris_lecar, poisson_pop, if_net, poisson_if, iz_net, if_noise, ei_inhibition, adex_net, out_degree, rate_net, potjans, hh_current, hh_net, tsodyks, adex_threshold, adex_balanced, stdp_demo, cuba_net, coba_net, stdp_compose_demo, festa2024, timed_stim, potjans_diesmann, lkd2014, stdp_kernel, afferent_response, lagzi2022, calcium_kernel, oja_rule, stp_demo, stp_onecell, lkd2014_adex, simulation_speed, tripod, metaplasticity, ballandstick, dendrite, hetrec, wilson_cowan, spikesynapse, sttc, aggregate_scaling, poisson_layer, spike_analysis, compartment_synapse, tripod_het, nmda, tripod_receptor, tripod_current, change_plasticity, stimuli, vstdp, balanced, stdp_confavreux, istdp_rate, istdp_potential, stp_timestep, stdp_symmetric, tripod_network, synapse_params, snn_utils_params, spatial, lkd2014_demo, duarte2019, if_extended, receptors_demo).

    #Recent additions (v0.44–v0.47, 2026-10-01)

    #v0.44.0 — Prioritized Experience Replay (PER, Schaul 2016)

    • mbt/sum_tree.mbt — segmented priority tree with O(log N) sample/update. API: SumTree::new(capacity), add(priority) -> tree_idx, set_at(slot, p), find_prefix(s) -> (tree_idx, p), find_prefix_batch(segs), total(), len(), get_leaf(slot).
    • mbt/prioritized_replay.mbt — PrioritizedReplayBuffer with proportional priorities P(i) = p_i^α / Σ p_j^α, importance-sampling weights w_i = (N·P(i))^{-β} / max_j w_j normalised to 1.0, FIFO wrap-around, max-priority insertion trick (so every transition is sampleable at least once). API: new(capacity, α, β, ε), push(s, a, r, s', done), sample(batch_size, rng) -> (states, actions, rewards, next_states, dones, slot_indices, is_weights, probs), update_priorities(slot_indices, td_errors), len(), total_priority(), max_priority().
    • 22 new tests (sum_tree_test.mbt, prioritized_replay_test.mbt).

    #v0.45.0 — N-step returns (R2D2-style)

    • mbt/n_step_buffer.mbt — FIFO StepRecord ring. NStepBuffer::new(n), push(s, a, r, s', done) -> Bool (true when window ready), at(i) chronological, reset(), n_step_return(root, γ) -> (G_n, h_eff, s_root, s_look, truncated) with terminal-truncation and partial-window handling.
    • mbt/dqn_n_step.mbt — dqn_update_step_n_step (γ^h bootstrap + no-bootstrap when terminal), train_dqn_n_step (NStepBuffer → ReplayBuffer flush + partial-window flush at episode end).
    • 14 new tests.

    #v0.46.0 — Distributional SAC (DSAC, Ma et al. 2021)

    • mbt/dsac.mbt — LinearGaussianQNet with μ + log-σ heads per action (σ = exp(log-σ) ∈ [1e-3, 10]), Gaussian NLL helper, DSac agent (policy + twin Gaussian critics + twin targets + α), dsac_critic_update with analytic ∂NLL/∂μ = (μ−t)/σ² and ∂NLL/∂log-σ = 0.5(z²−1), dsac_soft_update Polyak averaging on all 4 weight arrays, dsac_soft_target = SAC soft-Bellman + ε·N(0, 0.01) stochastic perturbation, dsac_sample_action.
    • 12 new tests.

    #v0.47.0 — Batch 4 unified Network Simulator framework

    • mbt/network_simulator.mbt — extends the existing HeterogeneousModel framework (compose.mbt). New helpers: NetworkSummary struct (counts of pops/conns/stims/monitors/stdp/stp entries + total_neurons + current_time), network_summary(model), reset_heterogeneous_full (time → 0 + monitor.clear_records + per-type STDP/STP entry resets), merge_heterogeneous(a, b) (concatenates two HeterogeneousModels via compose), heterogeneous_sim_for_with_log(model, duration, log_every_ms) -> Array[SimLog] (periodic SimLog snapshots with t, active_monitors, total_spikes), step_heterogeneous_with_record(model, dt, callback) (caller-supplied callback after each step).
    • 9 new tests.

    #Recent additions (v0.48–v0.53, 2026-10-01)

    #v0.48.0 — Bit-exact Julia parity framework

    • mbt/julia_parity.mbt — ULP-distance comparator (float32_ulp_distance via Float::reinterpret_as_int), float32_close(a, b, tol_ulps), compare_float_traces (element-by-element with max_abs + max_ulp), compare_spike_trains (greedy nearest-neighbour matching within tol_steps), parse_parity_csv (handles int/float traces + comments + blanks), int/float string parsers.
    • mbt/julia_parity_test.mbt — 12 tests including closed-form parity for a single IF neuron with constant current (geometric series (1-α)^n · v₀ + (1−(1-α)^n) · v_eq). The FP accumulation mismatch between MoonBit's step_neuron and the analytical closed-form is documented as a known limitation; tolerances (0.05–0.1 abs + 4096–8192 ULPs) catch regressions without requiring bit-exact analytical agreement.
    • First piece of the bit-exact Julia parity infrastructure that unblocks P0-1 (README TODO "Bit-exactness verification vs Julia").

    #v0.49.0 — IF_CANaHP example + AnyPop wiring

    • mbt/population.mbt — adds IFCANAHP_(IFCANAHP) to AnyPop enum + dispatch in integrate_any (calls integrate_ifcanahp) and any_n_neurons.
    • mbt/network_simulator.mbt — adds IFCANAHP_ case to count_neurons.
    • mbt/population_test.mbt — adds IFCANAHP_(_) catch-all arm.
    • mbt/examples/if_canahp/main.mbt (NEW) + moon.pkg (NEW) — 100 IF_CANAHP neurons (Brogdon 2022 CAN-AHP LIF) with constant I=50 driven through heterogeneous_sim_for for 100 ms.
    • mbt/neuron_if_canahp_test.mbt — 2 new tests (ifcanahp_example_smoke 100-neuron finite-state, ifcanahp_example_stable_dt smaller dt bounds v).
    • The empty if_canahp/ directory was the missing example for the already-ported neuron.

    #v0.50.0 — Multipod::step bug fix + re-enabled runtime tests

    • Fixed 1-based vs 0-based indexing bug in the per-receptor h_d update block (h_d[d][i][4] was written but storage is sized 4 → indices 0..3). The receptor marker arrays stay as Julia's 1-based labels [1, 2] / [3, 4]; only the storage indices are corrected to 0-based.
    • Re-enabled 4 deferred runtime tests in neuron_multipod_test.mbt (stubbed since v0.10.53 due to "stack overflow"). The actual root cause was the indexing bug, not JIT stack depth.
    • New multipod_runtime_test.mbt with 3 smoke tests verifying long-simulation Multipod::step does NOT panic.
    • Heun corrector still TODO per README.

    #v0.51.0 — izhikevich_balanced_postspike.jl complete

    • Extended mbt/examples/izhikevich_balanced_postspike/main.mbt from E-only (100 RS IZ + Bernoulli + refractory) to a true E/I balanced network: 100 RS IZ (excitatory) + 25 FS IZ (inhibitory), both with tabs_const=4 refractory, both receive 1 kHz Bernoulli drive; I→E cross-talk via gi spikes when an I neuron fires.
    • mbt/izhikevich_balanced_postspike_test.mbt — new "E/I balanced" test verifying both populations fire under drive.

    #v0.52.0 — tripod_network.jl example + tests

    • mbt/tripod_network_test.mbt (NEW) — 3 integration tests mirroring the existing examples/tripod_network/main.mbt scenario (E Tripod + I1/I2 IF + CompartmentSynapses I1→E[:soma, iSTDPRate] + I2→E[:d1, iSTDPPotential]): smoke run, I-populations-active-under-tonic-drive, iSTDP-rate-step-modifies-weights.
    • The example itself was already complete with the manual sim loop + iSTDP dispatch; only test coverage was missing.

    #v0.53.0 — tripod_current.jl example + tests

    • mbt/tripod_current_test.mbt (NEW) — 3 integration tests mirroring the existing examples/tripod_current/main.mbt scenario (4 TripodHet + PoissonLayerStimulusTripod exc@20Hz/inh@3Hz on :d1): smoke run, produces-bounded-spike-count, d1-receives-exc-and-inh.
    • The example itself was already complete; only test coverage was missing.

    #Running

    # Tests cd moonbit-snn/mbt moon test # chain.jl example moon run examples/chain/main.mbt

    #Bit-exact contract

    For any example, given the same parameters and dt (default 0.125f0) and Float32 accumulator types, the MoonBit port produces the same spike train (:fire recorded buffer) and the same recorded time series (:v, :ge, :gi, ...) as the Julia run, up to last-bit.

    Float ordering matches forward! / integrate! / update_synapses! in SNNModels.jl exactly. Where the Julia source uses v .+=dt/蟿m * (...), we keep the same operation order 鈥?no algebraic rearrangements, no vectorisation tricks that change the order of operations.

    Known gap (TODO v0.0.2): Julia's Xoshiro(seed::Integer) uses SHA-256 over the seed bytes. Our MoonBit RNG uses splitmix64 for integer seeds; sequences diverge from Julia for integer seeds. For bit-exact comparison, set the seed via Xoshiro::from_state(s0, s1,s2, s3) with raw UInt64s (bypassing the integer-seed hashing).

    #Layout

    mbt/ 鈹溾攢鈹€ moon.mod # module: riantr/snn_mbt 鈹溾攢鈹€ moon.pkg 鈹溾攢鈹€ README.md 鈹溾攢鈹€ units.mbt # 30+ Float32 unit constants 鈹溾攢鈹€ units_test.mbt 鈹溾攢鈹€ time.mbt # Time struct 鈹溾攢鈹€ time_test.mbt 鈹溾攢鈹€ rng.mbt # Xoshiro256++ RNG 鈹溾攢鈹€ rng_test.mbt 鈹溾攢鈹€ neuron_if.mbt # IF neuron + DoubleExpSynapse state 鈹溾攢鈹€ neuron_if_test.mbt 鈹溾攢鈹€ neuron_adex.mbt # AdEx neuron + AdExPostSpike + AdExParameter::with_vr 鈹溾攢鈹€ neuron_iz.mbt # IZ neuron + IZParameter::fs 鈹溾攢鈹€ neuron_hh.mbt # HH neuron 鈹溾攢鈹€ neuron_ml.mbt # Morris-Lecar neuron 鈹溾攢鈹€ neuron_poisson.mbt # Poisson population 鈹溾攢鈹€ neuron_wc.mbt # Wilson-Cowan rate neuron 鈹溾攢鈹€ neuron_wc_test.mbt 鈹溾攢鈹€ stimulus_poisson.mbt # PoissonStimulus (Knuth) 鈹溾攢鈹€ stimulus_poisson_layer.mbt # PoissonLayer (N sources projecting sparsely to IF) 鈹溾攢鈹€ stimulus_current.mbt # CurrentStimulus + CurrentStimulusArray 鈹溾攢鈹€ connection_spiking.mbt # SpikingSynapse (CSR, delay_dist + pending queue) 鈹溾攢鈹€ connection_spiking_iz.mbt # SpikingSynapseIZ 鈹溾攢鈹€ connection_spiking_hh.mbt # SpikingSynapseHH 鈹溾攢鈹€ connection_compartment.mbt # CompartmentSynapseBall/Tripod (multi-compartment routing) 鈹溾攢鈹€ connection_receptor_tripod.mbt # ReceptorSynapseTripod (multi-receptor soma/dendrite routing) 鈹溾攢鈹€ receptor.mbt # NMDA primitives (Receptor / Receptors / gating / 2-state ODE) 鈹溾攢鈹€ connection_spiking_test.mbt # delay_dist tests 鈹溾攢鈹€ connection_rate.mbt # RateSynapse 鈹溾攢鈹€ sparse_matrix.mbt # SparseMatrixCSR + ConnectRule 鈹溾攢鈹€ compose.mbt # HeterogeneousModel + AnyPop/AnyStim 鈹溾攢鈹€ vecplot.mbt # text dump + ascii_plot 鈹溾攢鈹€ stdp.mbt # STDP (Gerstner 1996, MexicanHat, AntiSymmetric, STDPEntryKind) 鈹溾攢鈹€ stdp_test.mbt 鈹溾攢鈹€ stp.mbt # Markram STP (event-based update_traces! + 蟻 broadcast) 鈹溾攢鈹€ stp_test.mbt 鈹溾攢鈹€ sim.mbt # Model, Monitor, sim_for 鈹溾攢鈹€ examples/ 鈹? 鈹斺攢鈹€ chain/ 鈹? 鈹溾攢鈹€ main.mbt # port of SpikingNeuralNetworks.jl/examples/chain.jl 鈹? 鈹斺攢鈹€ moon.pkg 鈹斺攢鈹€ _build/ # moon build artifacts

    #Network experiments summary

    Examples marked 鈿?partial demonstrate qualitative network behaviour matching the Julia run, but are not bit-exact (we scale population sizes and rates down to keep sim times tractable).

    ExampleNetwork sizePopulation scalesBit-exact?Notes
    chain.jl3 IFunchanged鉁?tiny; rates bit-exact
    if_neuron.jl1 IFunchanged鉁?(1 s vs 10 s)Knuth Poisson slow at high rates
    if_net.jl32E + 8I100脳 鈫?鉁?original is 3200+800
    ei_inhibition.jl5 + 5 IFunchanged鉁?tiny; inhibition observable
    cuba_net.jl80E + 20I100脳 鈫?鉁?qualitative: drive on鈫抐ire, drive off鈫抯ilent
    coba_net.jl80E + 20I100脳 鈫?鉁?delay_dist now implemented (v0.10.3); spike timing differs from Julia's true 0.8ms delay because our loop only checks delivery once per dt
    potjans_diesmann.jl120E + 40I100脳 鈫?鉁?simplified to 4 layers
    lkd2014.jl40E + 10I100脳 鈫?鉁?IF instead of AdExSinExpParameter
    festa2024.jl80E + 20I10脳 鈫?鉁?structured inhibition + STDP
    afferent_response.jl40E + 10I100脳 鈫?鉁?single 谓_a (no sweep)
    tsodyks.jl8 AdEx + 4 IFunchanged鉁?scaled-down paradoxical-effect
    hh_net.jl8E + 4I HHunchanged鉁?tiny; HH gating dynamics

    Known gaps preventing full bit-exactness:
    • delay_dist (uniform/normal synaptic delays) 鈥?鉁?done in v0.10.3 via per-connection delays array + pending-event queue. Spike timing is NOT bit-exact with Julia's true sub-step scheduling (our loop only checks delivery once per dt).
    • AdExSinExpParameter (used in LitwinKumar) 鈥?we use default IF.
    • AdExReceptorParameter (used in Mongillo2008) 鈥?needed for full Mongillo2008 working memory model. STP itself (MarkramSTPParameter) is 鉁?done in v0.10.4; the Mongillo2008 model requires the AdExReceptorParameter + selective sub-population targeting on top.
    • Multi-receptor synapses (NMDA / GABA_A / GABA_B for HH_NMDA, Tripod, BallAndStick, Multipod).
    • Multi-compartment neurons (Tripod/BallAndStick/Multipod).
    • HetRec (heterogeneous-recurrent layer with dendritic trees).

    For each of these, a scaled-down qualitative port works (afferent_response, festa2024, etc.) but the Julia run-time parameters are not reproduced bit-for-bit.

    #Reference source

    The Julia source has been cloned to moonbit-snn/refs/SNNModels.jl/ and moonbit-snn/refs/SNNUtils.jl/ for line-by-line reference. The main umbrella package SpikingNeuralNetworks.jl/src/SpikingNeuralNetworks.jl is a thin re-export layer over the subpackages; the actual neuron/synapse implementations live in SNNModels.jl/.

    Diffable

    pub(open) trait Diffable : Add + Sub {
    fn zero() -> Self
    fn one() -> Self
    }

    Trait required for any type that can be used with the tape. Pre-implemented for Float (Float32) and Double (Float64).
    impl Diffable for Float
    impl Diffable for Double

    ActivityDependentTurnover

    pub struct ActivityDependentTurnover {
    rate : Float
    tau : Float
    fraction : Float
    tau_pre : Float
    tau_post : Float
    mu : Float
    }

    ActivityDependentTurnover — pre/post activity-correlated rewiring.

    ActivityDependentTurnover::new

    fn ActivityDependentTurnover::new(rate? : Float, fraction? : Float, tau_pre? : Float, tau_post? : Float, mu? : Float) -> ActivityDependentTurnover

    AdEx

    pub struct AdEx {
    param : AdExParameter
    spike : AdExPostSpike
    n : Int
    v : Array[Float]
    w : Array[Float]
    fire : Array[Bool]
    threshold : Array[Float]
    tabs : Array[Int]
    i : Array[Float]
    syn_curr : Array[Float]
    ge : Array[Float]
    gi : Array[Float]
    he : Array[Float]
    hi : Array[Float]
    glu : Array[Float]
    gaba : Array[Float]
    gsyn_e : Array[Float]
    gsyn_i : Array[Float]
    e_e : Float
    e_i : Float
    tre : Float
    tde : Float
    tri : Float
    tdi : Float
    }

    AdEx neuron state — a population of N adaptive exponential LIF neurons.

    AdEx::new

    fn AdEx::new(n : Int, param : AdExParameter, rng : Xoshiro) -> AdEx

    Construct a new AdEx population with n neurons, using the default PostSpike (At=0mV, τA=10ms).

    AdEx::new_with_spike

    fn AdEx::new_with_spike(n : Int, param : AdExParameter, spike : AdExPostSpike, rng : Xoshiro) -> AdEx

    Construct a new AdEx population with a custom PostSpike.

    AdExHet

    pub struct AdExHet {
    param : AdExParameterHet
    spike : AdExPostSpike
    n : Int
    v : Array[Float]
    w : Array[Float]
    fire : Array[Bool]
    threshold : Array[Float]
    tabs : Array[Int]
    i : Array[Float]
    syn_curr : Array[Float]
    ge : Array[Float]
    gi : Array[Float]
    he : Array[Float]
    hi : Array[Float]
    glu : Array[Float]
    gaba : Array[Float]
    gsyn_e : Array[Float]
    gsyn_i : Array[Float]
    e_e : Float
    e_i : Float
    tre : Float
    tde : Float
    tri : Float
    tdi : Float
    }

    AdExHet — population with per-neuron heterogeneous AdEx parameters. Same synaptic state layout as AdEx (ge/gi/he/hi/glu/gaba/gsyn_e/gsyn_i).

    AdExHet::new

    fn AdExHet::new(n : Int, param : AdExParameterHet, rng : Xoshiro) -> AdExHet

    Construct a heterogeneous AdEx population with per-neuron params.

    AdExModel

    pub(all) struct AdExModel {
    pops : Array[AdEx]
    conns : Array[SpikingSynapseAdEx]
    monitors : Array[MonitorAdEx]
    }

    A model containing AdEx populations.

    AdExMultiTimescale

    pub(all) struct AdExMultiTimescale {
    n : Int
    param : AdExMultiTimescaleParameter
    v : Array[Float]
    w : Array[Float]
    fire : Array[Bool]
    theta : Array[Float]
    tabs : Array[Float]
    i : Array[Float]
    syn_curr : Array[Float]
    g_buf : Array[Float]
    h_buf : Array[Float]
    n_receptors : Int
    }

    AdExMultiTimescale — multi-timescale AdEx neuron container.

    State layout (1D arrays of length N): v — membrane potential (mV) w — adaptation current (pA) fire — spike flag theta — dynamic threshold (starts at Vt, decays back) tabs — refractory countdown i — external input current (pA) syn_curr — summed synaptic current (computed externally) State layout (2D, N × n_receptors): g_buf[i + nN] — g[i, n] receptor n conductance h_buf[i + nN] — h[i, n] receptor n rise state

    AdExMultiTimescale::new

    Construct a new AdExMultiTimescale with default parameters.

    AdExMultiTimescaleParameter

    pub(all) struct AdExMultiTimescaleParameter {
    tm : Float
    vt : Float
    vr : Float
    el : Float
    r : Float
    dt_slope : Float
    v_spike : Float
    tau_w : Float
    a : Float
    b : Float
    tau_abs : Float
    tau_r : Array[Float]
    tau_d : Array[Float]
    glu_receptors : Array[Int]
    gaba_receptors : Array[Int]
    e_e : Float
    e_i : Float
    gsyn_e : Float
    gsyn_i : Float
    at : Float
    tau_t : Float
    }

    AdExMultiTimescaleParameter — full parameter set including the per-receptor synaptic time constants and dynamic-threshold adaptation time constants.

    Float32 fields match Julia's defaults: τm = C/gl (computed); Vt = -50mV; Vr = -70.6mV; El = -70.6mV; R = 1/gl (computed); ΔT = 2mV; Vspike = 20mV; τw = 144ms; a = 4nS; b = 80.5pA; τabs = 1ms; τr = [1ms, 0.5ms]; τd = [6ms, 2ms]; glu_receptors = [1]; gaba_receptors = [2]; E_e = 0mV; E_i = -75mV; gsyn_e/i = 1.0; At = 10mV; τt = 30ms.

    AdExMultiTimescaleParameter::new

    Default AdExMultiTimescaleParameter. tau_r = [1, 0.5], tau_d = [6, 2]; glu_receptors = [0] (1st), gaba_receptors = [1] (2nd).

    AdExParameter

    pub struct AdExParameter {
    c : Float
    gl : Float
    vt : Float
    vr : Float
    el : Float
    tm : Float
    r : Float
    dt_slope : Float
    tw : Float
    a : Float
    b : Float
    }

    AdExParameter — biophysical constants of an AdEx neuron.

    AdExParameter::custom

    fn AdExParameter::custom(tm~ : Float, vt~ : Float, vr~ : Float, el~ : Float, r~ : Float) -> AdExParameter

    AdExParameter with full custom tm / vt / vr / el / r fields. Matches Julia's AdExParameter(; tm, vt, vr, el, r). Other fields use defaults. Required for non-default Litwin-Kumar-Doiron 2014 parameters (300pF/15nS=20ms tm, -70mV El, -52mV Vt, -60mV Vr, R=1/15nS).

    AdExParameter::new

    Default AdExParameter, matching Julia's AdExParameter().

    AdExParameter::with_vr

    fn AdExParameter::with_vr(vr : Float) -> AdExParameter

    AdExParameter with a custom reset potential vr. Matches Julia's AdExParameter(; Vr = -50mV). Other fields use defaults.

    AdExParameterHet

    pub(all) struct AdExParameterHet {
    vt : Array[Float]
    vr : Array[Float]
    el : Array[Float]
    tm : Array[Float]
    r : Array[Float]
    dt_slope : Array[Float]
    tw : Array[Float]
    a : Array[Float]
    b : Array[Float]
    }

    AdExParameterHet — per-neuron heterogeneous AdEx parameters. Each field is Array[Float] of length N (post-construction). Matches Julia's AdExParameter{Vector{Float32}} fields. c and gl stay scalar (only used to derive tm/r, not stored).

    AdExParameterHet::homogeneous

    fn AdExParameterHet::homogeneous(n : Int, p : AdExParameter) -> AdExParameterHet

    AdExParameterHet::homogeneous — fills all per-ne arrays with the same scalar value. Useful as a baseline before per-ne draws.

    AdExPostSpike

    pub struct AdExPostSpike {
    at : Float
    tau_a : Float
    ap_membrane : Float
    tabs_const : Float
    up : Float
    }

    AdEx PostSpike — adds At (threshold jump) and τA (threshold decay) on top of the IF PostSpike fields.

    AdExPostSpike::new

    AdExPostSpike::with_at

    fn AdExPostSpike::with_at(at : Float, tau_a : Float) -> AdExPostSpike

    AdExPostSpike with custom at (after-spike threshold jump) and tau_a (threshold time constant). Matches Julia's PostSpike(; At = 10mV, τA = 10ms).

    AdExSinExp

    pub struct AdExSinExp {
    param : AdExSinExpParameter
    spike : AdExPostSpike
    n : Int
    v : Array[Float]
    w : Array[Float]
    fire : Array[Bool]
    threshold : Array[Float]
    tabs : Array[Int]
    i : Array[Float]
    syn_curr : Array[Float]
    ge : Array[Float]
    gi : Array[Float]
    glu : Array[Float]
    gaba : Array[Float]
    gsyn_e : Array[Float]
    gsyn_i : Array[Float]
    e_e : Float
    e_i : Float
    tau_e : Float
    tau_i : Float
    }

    AdEx SinExp neuron state — a population of N adaptive exponential LIF neurons with single-exponential synaptic dynamics.

    AdExSinExp::new

    fn AdExSinExp::new(n : Int, param : AdExSinExpParameter, rng : Xoshiro) -> AdExSinExp

    Construct a new AdExSinExp population with n neurons, default PostSpike (At=0mV, τA=10ms) and LKD-like defaults (E_e=0mV, E_i=-75mV, τe=6ms, τi=2ms).

    AdExSinExp::new_with_spike

    fn AdExSinExp::new_with_spike(n : Int, param : AdExSinExpParameter, spike : AdExPostSpike, rng : Xoshiro) -> AdExSinExp

    Construct a new AdExSinExp population with a custom PostSpike.

    AdExSinExpParameter

    pub(all) struct AdExSinExpParameter {
    c : Float
    gl : Float
    vt : Float
    vr : Float
    el : Float
    tm : Float
    r : Float
    dt_slope : Float
    tw : Float
    a : Float
    b : Float
    }

    AdExSinExpParameter — same as AdExParameter (the neuron model is identical; only the synapse model differs). We keep it as a separate type to make param: AdExSinExpParameter a useful documentation marker and to match Julia's AdExSinExpParameter.

    AdExSinExpParameter::lkd_adex

    AdExSinExpParameter with LKD 2014 defaults (El=-70mV, Vt=-52mV, Vr=-60mV, At=10mV). Matches the LKD2014SingleExp AdEx config in SNNUtils.jl/src/models/lkd2014.jl.

    AdExSinExpParameter::new

    Default AdExSinExpParameter, matching Julia's AdExSinExpParameter(). All values identical to AdExParameter defaults.

    AdaGradState

    pub struct AdaGradState {
    v_w : Array[Float]
    v_b : Array[Float]
    }

    AdaGrad state. v_w / v_b accumulate squared gradients.

    Adafactor1DState

    pub struct Adafactor1DState {
    v : Array[Float]
    }

    Adafactor state for a 1D parameter (bias, gain).

    Adafactor1DState::new

    Construct a 1D Adafactor state.

    Adafactor2DState

    pub struct Adafactor2DState {
    v_row : Array[Float]
    v_col : Array[Float]
    }

    Adafactor state for a 2D weight matrix.

    Adafactor2DState::new

    fn Adafactor2DState::new(rows : Int, cols : Int) -> Adafactor2DState

    Construct a 2D Adafactor state.

    AdamState

    pub struct AdamState {
    m_w : Array[Float]
    v_w : Array[Float]
    m_b : Array[Float]
    v_b : Array[Float]
    step : Int
    }

    Adam state. m_w / v_w track first / second moments of d_weight; m_b / v_b do the same for d_bias. step is 1-based and is incremented implicitly inside adam_update_arrays — the caller must pass the state returned from the previous call into the next call. (We do NOT mutate the input state in-place because MoonBit struct fields share memory across function boundaries, which would corrupt any previously-returned state object.)

    AdamWState

    pub struct AdamWState {
    m_w : Array[Float]
    v_w : Array[Float]
    m_b : Array[Float]
    v_b : Array[Float]
    step : Int
    }

    AdamW state. Identical shape to AdamState; step is 1-based and tracked implicitly via the returned state (not mutated in place).

    AdditiveNorm

    pub struct AdditiveNorm {
    tau : Float
    }

    AdditiveNorm — per-step additive rule. At each step: μ[i] = W0[i] - W1[i]; W[s] += μ[i].

    AdditiveNorm::new

    fn AdditiveNorm::new(tau : Float) -> AdditiveNorm

    AggregateScaling

    pub struct AggregateScaling {
    param : AggregateScalingParameter
    n : Int
    targets : Array[SynapseTarget]
    wt : Array[Float]
    wt_total : Array[Float]
    y : Array[Float]
    mu : Array[Float]
    last_plasticity_step : Int
    plasticity_interval_steps : Int
    }

    AggregateScaling — holds the per-iteration state needed to apply the homeostatic rule. Mirrors Julia's @snn_kw struct AggregateScaling (subset of fields).

    fields:
    • param : AggregateScalingParameter
    • n : number of post-synaptic neurons
    • synapses : list of SpikingSynapses whose weights are rescaled. Each synapse is referenced via a SynapseTarget (same as SynapseNormalization uses) so the rule can mutate the weights in-place.
    • wt : temporary per-post-neuron weight sum (recomputed each plasticity call)
    • wt_total : per-post-neuron homeostatic target sum (updated in forward)
    • fire : borrowed reference to the post-population's fire array (read-only here; updated externally each step)
    • y : per-post-neuron homeostatic trace
    • mu : per-post-neuron plasticity multiplier (computed each plasticity call)
    • last_plasticity_step : counter used by the periodic plasticity scheduler (every tau ms)

    AggregateScaling::new

    Construct AggregateScaling from a post-population and a list of synapses (provided via Array[SynapseTarget]). n is the number of post-synaptic neurons. Initialises WT[i] = sum of incoming weights (matches Julia's constructor).

    AggregateScaling::with_plasticity_interval

    fn AggregateScaling::with_plasticity_interval(c : AggregateScaling, interval_steps : Int) -> AggregateScaling

    Override the plasticity interval (in steps). Default is 80 steps (≈ 10ms @ dt=0.125ms). Call this after new to use a different cadence (e.g. 8 steps for 1ms @ dt=0.125ms).

    AggregateScalingParameter

    pub struct AggregateScalingParameter {
    tau : Float
    tau_a : Float
    tau_e : Float
    y : Array[Float]
    w_min : Float
    w_max : Float
    }

    AggregateScalingParameter — homeostatic scaling rule parameters. Mirrors Julia's @snn_kw struct AggregateScalingParameter. Defaults: τ=10ms, Wmin=0.5, Wmax=250. Required: τe, τa, Y.

    AggregateScalingParameter::new

    fn AggregateScalingParameter::new(tau? : Float, tau_a~ : Float, tau_e~ : Float, y : Array[Float], w_min? : Float, w_max? : Float) -> AggregateScalingParameter

    Construct AggregateScalingParameter with explicit values. y : Array[Float] is the per-post-neuron target rate (in internal units = rate per ms). Use y[k] = 10.0F * hz for 10 Hz.

    AggregateScalingParameter::uniform

    fn AggregateScalingParameter::uniform(n : Int, rate_hz : Float, tau? : Float, tau_a? : Float, tau_e? : Float, w_min? : Float, w_max? : Float) -> AggregateScalingParameter

    Convenience constructor: N post-synaptic neurons, all with the same target rate (in Hz). Mirrors Julia's AggregateScalingParameter(N; rate=10Hz, ...).

    AnyPop

    pub(all) enum AnyPop {
    IF_(IF)
    AdEx_(AdEx)
    AdExSinExp_(AdExSinExp)
    IZ_(IZ)
    HH_(HH)
    ML_(MorrisLecar)
    Poisson_(Poisson)
    WC_(WilsonCowan)
    HetRec_(HetRec)
    IFCANAHP_(IFCANAHP)
    }

    Heterogeneous population wrapper.

    AnyStim

    pub(all) enum AnyStim {
    PoissonIF_(PoissonStimulusIF)
    PoissonLayer_(PoissonLayerStimulus)
    BalancedIF_(BalancedStimulus)
    CurrentIF_(CurrentStimulusIF)
    CurrentArr_(CurrentStimulusArray)
    TimedStim_(SpikeTimeStimulus, Float)
    }

    Heterogeneous stimulus wrapper. Currently we have:
    • PoissonIF_ for Poisson spike-train input onto :ge/:gi
    • PoissonLayer_ for PoissonLayer (N independent Poisson sources projecting sparsely to an IF population with optional Normal weights) — mirrors Julia's Stimulus(param, E, :ge, conn=...)
    • BalancedIF_ for BalancedStimulus (feedback-driven inhomogeneous Poisson source with low-pass noise and rate adaptation) — mirrors Julia's BalancedStimulus(E, :ge, :gi; param=BalancedParameter()).
    • CurrentIF_ for direct current injection into an IF population
    • CurrentArr_ for direct current injection into a raw i array (works for AdEx, IZ, HH, Poisson, etc.)
    • TimedStim_ for spike-time stimulus (exact spike times) More variants (CurrentVariable, etc.) can be added later.

    ArParam

    pub struct ArParam {
    p : Int
    intercept : Float
    coeffs : Array[Float]
    }

    AR(p) parameter bundle: p-order intercept + p coefficients. coeffs[i] corresponds to the lag-(i+1) coefficient φᵢ₊₁.

    ArParam::new

    fn ArParam::new(p : Int, intercept : Float, coeffs : Array[Float]) -> ArParam

    Build an AR(p) parameter from explicit coefficients. coeffs is expected to have length p; the first coefficient lags y[t-1].

    ArimaFit

    pub struct ArimaFit {
    p : Int
    d : Int
    q : Int
    intercept : Float
    ar_coeffs : Array[Float]
    ma_thetas : Array[Float]
    }

    Bundle of fitted ARIMA(p, d, q) parameters.

    ArmaParam

    pub struct ArmaParam {
    p : Int
    q : Int
    intercept : Float
    ar_coeffs : Array[Float]
    ma_thetas : Array[Float]
    }

    ARMA(p, q) parameter bundle.

    ArmaParam::new

    fn ArmaParam::new(p : Int, q : Int, intercept : Float, ar_coeffs : Array[Float], ma_thetas : Array[Float]) -> ArmaParam

    Build an ARMA(p, q) parameter from explicit coefficients. Both ar_coeffs and ma_thetas may be empty (then the corresponding component is omitted from the model).

    AttnCache

    pub struct AttnCache {
    seq_len : Int
    x : Array[Float]
    q : Array[Float]
    k : Array[Float]
    v : Array[Float]
    scores : Array[Float]
    weights : Array[Float]
    out_pre : Array[Float]
    mask : Array[Float]
    scale : Float
    }

    Forward / backward cache for an MHA forward pass.

    AvgPool2dParam

    pub struct AvgPool2dParam {
    kh : Int
    kw : Int
    stride : Int
    pad : Int
    }

    AvgPool2d parameter container.

    AvgPool2dParam::new

    fn AvgPool2dParam::new(kh : Int, kw : Int, stride? : Int, pad? : Int) -> AvgPool2dParam

    Build an AvgPool2dParam. stride defaults to kh, pad to 0.

    BalancedParameter

    pub(all) struct BalancedParameter {
    kIE : Float
    beta : Float
    tau : Float
    r0 : Float
    w : Float
    wIE : Float
    same_input : Bool
    }

    Parameter set for BalancedStimulus. Mirrors Julia's BalancedParameter.

    kIE — scale for inhibitory rate (multiplies r0 to get the inhomogeneous Poisson rate for the inhibitory channel). beta — amplitude of the low-pass-filtered noise injected into the excitatory firing rate. tau — time constant (ms) of the noise filter. r0 — baseline firing rate (stored in internal units: rate per ms). w — per-spike conductance scaling for the post-synaptic receptors. wIE — additional scaling for inhibitory spikes. same_input — when true, all neurons share the same noise/rate trace.

    BalancedParameter::new

    fn BalancedParameter::new(kIE? : Float, beta? : Float, tau? : Float, r0? : Float, w? : Float, wIE? : Float, same_input? : Bool) -> BalancedParameter

    Construct a BalancedParameter matching Julia's defaults.

    All parameters are keyword-optional. Defaults:
    • kIE=1.0, beta=0.0, tau=50ms, r0=1.0*khz, w=1.0, wIE=1.0, same_input=false.

    BalancedStimulus

    pub(all) struct BalancedStimulus {
    param : BalancedParameter
    n : Int
    ge : Array[Float]
    gi : Array[Float]
    fire : Array[Bool]
    r : Array[Float]
    noise : Array[Float]
    rng : Xoshiro
    }

    BalancedStimulus — feedback-driven Poisson source targeting an IF (or any population with glu / gaba receptor fields).

    State (per neuron):
    • r — current excitatory firing rate (internal units, per ms).
    • noise — low-pass-filtered random walk driving r toward r0.
    • fire — per-neuron spike record for the current step.

    The constructor picks up the post-synaptic glu / gaba arrays by reference, so subsequent stimulate_balanced calls update the receptors in-place (just like PoissonStimulusIF).

    BalancedStimulus::new

    fn BalancedStimulus::new(pop : IF, sym_e? : String, sym_i? : String, param? : BalancedParameter, seed? : UInt64) -> BalancedStimulus

    Construct a BalancedStimulus targeting an IF population on :ge / :gi receptors. seed controls the Xoshiro RNG used to draw uniform noise and Poisson counts.

    BallAndStick

    pub struct BallAndStick {
    soma_param : AdExParameter
    soma_spike : AdExPostSpike
    dend : Dendrite
    i_s : Array[Float]
    i_d : Array[Float]
    ge_s : Array[Float]
    gi_s : Array[Float]
    ge_d : Array[Float]
    gi_d : Array[Float]
    glu_s : Array[Float]
    gaba_s : Array[Float]
    glu_d : Array[Float]
    gaba_d : Array[Float]
    e_e : Float
    e_i : Float
    tau_e : Float
    tau_i : Float
    gsyn_e : Float
    gsyn_i : Float
    n : Int
    v_s : Array[Float]
    w_s : Array[Float]
    v_d : Array[Float]
    fire : Array[Bool]
    threshold : Array[Float]
    tabs : Array[Int]
    dv : Array[Float]
    dv_temp : Array[Float]
    syn_curr_s : Array[Float]
    syn_curr_d : Array[Float]
    ic : Float
    }

    BallAndStick neuron state — soma (AdEx) + 1 dendrite.

    BallAndStick::new

    fn BallAndStick::new(n : Int, soma_param : AdExParameter, rng : Xoshiro) -> BallAndStick

    Default BallAndStick: AdEx soma + Dendrite with human_dend defaults.

    BasicPrims

    pub struct BasicPrims[A] {
    tape : Tape[A]
    neg : (Loc[A]) -> Loc[A]
    add : (Loc[A], Loc[A]) -> Loc[A]
    sub : (Loc[A], Loc[A]) -> Loc[A]
    mul : (Loc[A], Loc[A]) -> Loc[A]
    div : (Loc[A], Loc[A]) -> Loc[A]
    }

    Generic algebraic primitive ops.

    BasicPrims::on

    fn[A : Neg + Add + Sub + Mul + Div] BasicPrims::on(tape : Tape[A]) -> BasicPrims[A]

    Build the BasicPrims bundle around the given tape.

    BatchNorm2d

    pub struct BatchNorm2d {
    gamma : Array[Float]
    beta : Array[Float]
    running_mean : Array[Float]
    running_var : Array[Float]
    momentum : Float
    eps : Float
    training : Bool
    }

    BatchNorm2d parameter container.

    BatchNorm2d::new

    fn BatchNorm2d::new(c : Int, momentum? : Float, eps? : Float) -> BatchNorm2d

    Build a BatchNorm2d for c channels. gamma is initialised to all-1, beta to all-0; running stats to all-0. momentum defaults to 0.1, eps to 1e-5.

    BatchNorm2d::set_training

    fn BatchNorm2d::set_training(self : BatchNorm2d, training : Bool) -> Unit

    Set training / inference mode.

    BatchNorm2d::with_gamma_beta

    fn BatchNorm2d::with_gamma_beta(gamma : Array[Float], beta : Array[Float], momentum? : Float, eps? : Float) -> BatchNorm2d

    Convenience constructor taking pre-built gamma / beta arrays (does NOT copy them).

    BatchNormCache

    pub struct BatchNormCache {
    mean : Array[Float]
    inv_std : Array[Float]
    x_centered : Array[Float]
    nhw : Int
    n : Int
    c : Int
    h : Int
    w : Int
    }

    Cache returned by batch_norm2d_forward and consumed by batch_norm2d_backward. Holds everything the backward pass needs without recomputing the forward.

    BiLstmCache

    pub struct BiLstmCache {
    caches_f : Array[LstmCellCache]
    caches_b : Array[LstmCellCache]
    hs_f : Array[Array[Float]]
    hs_b : Array[Array[Float]]
    cs_f : Array[Array[Float]]
    cs_b : Array[Array[Float]]
    }

    Forward cache storing per-direction per-step cell caches plus the forward outputs (used by BPTT and for loss computation).

    BiLstmParam

    pub struct BiLstmParam {
    d_x : Int
    d_h : Int
    fwd : LstmCellParam
    bwd : LstmCellParam
    }

    Parameter bundle for a 1-layer BiLSTM. Each direction is its own LstmCellParam.

    BiLstmParam::new

    fn BiLstmParam::new(d_x : Int, d_h : Int, seed : UInt64) -> BiLstmParam

    Build a 1-layer BiLSTM with both directions having hidden dim d_h. Forward uses seed seed, backward uses seed + 1.

    C51

    pub struct C51 {
    net : C51Net
    net_target : C51Net
    gamma : Float
    lr : Float
    n_atoms : Int
    v_min : Float
    v_max : Float
    support : Array[Float]
    delta_z : Float
    }

    C51 agent: online net + target net + hyper-parameters.

    C51::new

    fn C51::new(n_states : Int, n_actions : Int, n_atoms : Int, v_min : Float, v_max : Float, gamma : Float, lr : Float, seed : UInt64) -> C51

    C51Net

    pub struct C51Net {
    w : Array[Array[Array[Float]]]
    b : Array[Array[Float]]
    n_states : Int
    n_actions : Int
    n_atoms : Int
    v_min : Float
    v_max : Float
    }

    Linear net producing n_atoms logits per action. Forward on a discrete state (one-hot) yields n_actions × n_atoms logits.

    C51Net::new

    fn C51Net::new(n_states : Int, n_actions : Int, n_atoms : Int, v_min : Float, v_max : Float, seed : UInt64) -> C51Net

    CaPlasticityEntry

    pub struct CaPlasticityEntry {
    conn_index : Int
    n_pre : Int
    n_post : Int
    param : CaPlasticityParameter
    vars : CaPlasticityVariables
    t_now : Array[Float]
    }
    Bundle a CaPlasticityParameter connection with trace state. Pattern matches STDPEntry (Gerstner) — mut param enables runtime swap.

    CaPlasticityEntry::change_plasticity

    fn CaPlasticityEntry::change_plasticity(e : CaPlasticityEntry, new_param : CaPlasticityParameter) -> Unit
    Runtime swap of CaPlasticityParameter. Preserves trace state.

    CaPlasticityEntry::new

    fn CaPlasticityEntry::new(conn_index : Int, n_pre : Int, n_post : Int, param? : CaPlasticityParameter) -> CaPlasticityEntry
    Constructor.

    CaPlasticityEntry::set_ltp_active

    fn CaPlasticityEntry::set_ltp_active(e : CaPlasticityEntry, active : Bool) -> Unit
    Toggle the active flag (mirrors Julia's set_LTP!(s, active)).

    CaPlasticityParameter

    pub(all) struct CaPlasticityParameter {
    a_pre : Float
    a_post : Float
    tau_pre : Float
    tau_post : Float
    w_max : Float
    w_min : Float
    }
    CaPlasticityParameter (Brette-Gerstner 2005 pair-spike form with explicit trace-amplitude scaling). Mirrors Julia's CaPlasticityParameter from refs/SNNModels.jl/src/connections/sparse_plasticity/CaRule.jl. Field units (in normalised SI): A_pre : mV^-2 (LTP rate; bump on pre-spike enters the pre trace) A_post: mV^-1 (LTD rate; bump on post-spike enters the post trace) tau_pre, tau_post: ms (trace time constants) w_max, w_min: pF (weight clamp bounds) Default values match Julia's @snn_kw struct CaPlasticityParameter.

    CaPlasticityParameter::custom

    fn CaPlasticityParameter::custom(a_pre? : Float, a_post? : Float, tau_pre? : Float, tau_post? : Float, w_max? : Float, w_min? : Float) -> CaPlasticityParameter
    Custom CaPlasticityParameter (matches Julia's STDP kwargs).

    CaPlasticityParameter::new

    Default CaPlasticityParameter (matches Julia).

    CaPlasticityVariables

    pub(all) struct CaPlasticityVariables {
    n_pre : Int
    n_post : Int
    tpre : Array[Float]
    tpost : Array[Float]
    active : Array[Bool]
    }
    Trace state for CaPlasticityParameter.

    CaPlasticityVariables::new

    fn CaPlasticityVariables::new(n_pre : Int, n_post : Int) -> CaPlasticityVariables
    Allocate CaPlasticityVariables with zero-initialised traces.

    ClopathSTDP

    pub(all) struct ClopathSTDP {
    a_ltd : Float
    a_ltp : Float
    theta_ltd : Float
    theta_ltp : Float
    tau_s : Float
    tau_u : Float
    tau_v : Float
    tau_x : Float
    tau_1 : Float
    eps : Float
    w_min : Float
    w_max : Float
    inv_tau_u : Float
    inv_tau_v : Float
    inv_tau_x : Float
    inv_tau_1 : Float
    }
    Clopath 2010 voltage-based STDP — port of dump.jl::STDP. Field names mapped to ASCII. Defaults from lkd_stdp = STDP(...) literal at the bottom of dump.jl.

    ClopathSTDP::lkd

    Defaults matching lkd_stdp = STDP(a⁻=8e-5, a⁺=14e-5, θ⁻=-70, θ⁺=-49,τu=10, τv=7, τx=15, τ1=5, j⁻=1.78, j⁺=21.0) literal.

    CompartmentSynapseBall

    pub struct CompartmentSynapseBall {
    pre : IF
    post : BallAndStick
    sym : String
    target : String
    matrix : SparseMatrixCSR
    rho : Array[Float]
    delays : Array[Float]
    pending_times : Array[Float]
    pending_posts : Array[Int]
    pending_weights : Array[Float]
    }

    CompartmentSynapseBall — targets one compartment (:soma or :d) of a BallAndStick neuron. Same semantics as SpikingSynapse (random sparse with Bernoulli p and weight ~ Normal/Fixed).

    CompartmentSynapseBall::random

    fn CompartmentSynapseBall::random(pre : IF, post : BallAndStick, sym : String, target : String, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> CompartmentSynapseBall

    CompartmentSynapseBall::set_delays

    fn CompartmentSynapseBall::set_delays(c : CompartmentSynapseBall, delays : Array[Float]) -> Unit

    Add a per-connection delay (in ms) — enables scheduled delivery via deliver_pending_compartment_ball.

    CompartmentSynapseTripod

    pub struct CompartmentSynapseTripod {
    pre : IF
    post : Tripod
    sym : String
    target : String
    matrix : SparseMatrixCSR
    rho : Array[Float]
    delays : Array[Float]
    pending_times : Array[Float]
    pending_posts : Array[Int]
    pending_weights : Array[Float]
    }

    CompartmentSynapseTripod — targets one compartment (:soma, :d1, or :d2) of a Tripod neuron.

    CompartmentSynapseTripod::random

    fn CompartmentSynapseTripod::random(pre : IF, post : Tripod, sym : String, target : String, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> CompartmentSynapseTripod

    CompartmentSynapseTripod::set_delays

    fn CompartmentSynapseTripod::set_delays(c : CompartmentSynapseTripod, delays : Array[Float]) -> Unit

    Same as above, Tripod variant.

    Confavreux2025Parameter

    pub struct Confavreux2025Parameter {
    tau_ampa : Float
    tau_nmda : Float
    tau_gaba : Float
    e_i : Float
    e_e : Float
    alpha : Float
    }

    Confavreux2025Parameter — synapse parameters for the Confavreux 2025 multi-timescale model.

    Julia defaults: τAMPA=5ms, τNMDA=100ms, τGABA=10ms, E_i=-80mV, E_e=0mV, α=0.23.

    Confavreux2025Parameter::new

    Default Confavreux2025Parameter (Julia defaults).

    Confavreux2025SynapseVars

    pub struct Confavreux2025SynapseVars {
    n : Int
    g_ampa : Array[Float]
    g_nmda : Array[Float]
    g_gaba : Array[Float]
    }

    Confavreux2025SynapseVars — per-neuron state buffers.

    gAMPA : AMPA conductance (drives both E_e drive + NMDA coupling) gNMDA : NMDA conductance (coupled to AMPA via τNMDA) gGABA : GABA conductance (inhibitory drive)

    Confavreux2025SynapseVars::new

    Allocate per-neuron buffers for an N-neuron population.

    ConnectRule

    pub(all) enum ConnectRule {
    Bernoulli
    FixedIn
    FixedOut
    }

    Connection rules for random sparse matrices. Mirrors Julia SNN's rule keyword on sparse_matrix(post, pre, μ, σ, p; rule=...):
    • Bernoulli: each edge independently kept with probability p
    • FixedIn : each post-synaptic neuron receives from exactly p * Npre pre-synaptic neurons (chosen uniformly without replacement). Same in-degree for all posts.
    • FixedOut : each pre-synaptic neuron projects to exactly p * Npost post-synaptic neurons. Same out-degree for all pres.

    Conv2dParam

    pub struct Conv2dParam {
    weight : Array[Float]
    bias : Array[Float]
    c_out : Int
    c_in : Int
    kh : Int
    kw : Int
    stride : Int
    pad : Int
    }

    Conv2d parameter container. weight is laid out as [c_out, c_in, kh, kw] row-major; bias is [c_out]. stride is shared across spatial dims (no asymmetric strides yet); pad is the half-padding applied symmetrically on both sides (e.g. pad = kh // 2 gives "same" padding for odd kernel sizes).

    Conv2dParam::new

    fn Conv2dParam::new(weight : Array[Float], bias : Array[Float], c_out : Int, c_in : Int, kh : Int, kw : Int, stride? : Int, pad? : Int) -> Conv2dParam

    Build a Conv2dParam from raw arrays. Does NOT copy the arrays; the caller retains ownership.

    CorridorEnv

    pub(all) struct CorridorEnv {
    n_cells : Int
    goal_side : Int
    step_penalty : Float
    goal_reward : Float
    max_steps : Int
    }

    Corridor POMDP: indicator at t=0 says which side has the goal.

    CorridorEnv::goal_cell

    fn CorridorEnv::goal_cell(self : CorridorEnv) -> Int

    Goal cell index (0 or n_cells-1).

    CorridorEnv::new

    fn CorridorEnv::new(n_cells : Int, max_steps : Int) -> CorridorEnv

    Build a corridor with the goal on the right by default.

    CorridorEnv::step

    fn CorridorEnv::step(self : CorridorEnv, pos : Int, action : Int) -> (Int, Float, Bool)

    Step the corridor. pos = current position, action = 0 (left) or 1 (right). Going off the grid keeps the agent in place.

    CosineAnnealingLR

    pub struct CosineAnnealingLR {
    eta_max : Float
    eta_min : Float
    t_max : Int
    current_step : Int
    }

    CosineAnnealingLR state. lr follows a half-cosine from eta_max to eta_min over t_max steps, then jumps back to eta_max and repeats.

    CosineAnnealingLR::new

    fn CosineAnnealingLR::new(eta_max : Float, eta_min : Float, t_max : Int) -> CosineAnnealingLR

    Build a CosineAnnealingLR scheduler.

    CosineAnnealingLR::next

    Increment the step counter; returns a fresh state.

    CosineAnnealingLR::step

    fn CosineAnnealingLR::step(self : CosineAnnealingLR) -> Float

    Compute the current learning rate.

    CrossEntropyLoss

    pub struct CrossEntropyLoss {
    mean : Tensor
    per_batch : Tensor
    }

    Cross-entropy loss result. mean is a 1-element Tensor (shape=[1]) and per_batch has shape [batch].

    CurrentNoise

    pub struct CurrentNoise {
    n : Int
    i_base : Array[Float]
    noise_sigma : Float
    alpha : Float
    }

    CurrentNoise — per-neuron base + noise + decay parameters.

    CurrentNoise::custom

    fn CurrentNoise::custom(n : Int, i_base : Float, noise_sigma : Float, alpha : Float) -> CurrentNoise

    Custom CurrentNoise with explicit base current, noise σ, and α decay.

    CurrentNoise::new

    fn CurrentNoise::new(n : Int) -> CurrentNoise

    Default CurrentNoise (all zeros: no base current, no noise, α=0 = instantaneous tracking).

    CurrentStimulusArray

    pub struct CurrentStimulusArray {
    i_base : Float
    active : Array[Bool]
    i : Array[Float]
    n : Int
    noise_sigma : Float
    rng : Xoshiro
    }

    CurrentStimulusArray — generic current stimulus that writes into a raw Array[Float]. Works for any population (AdEx, IZ, HH, Poisson, WilsonCowan, etc.) — the caller passes the population's i array directly. Same noise / base semantics as CurrentStimulusIF.

    CurrentStimulusArray::new

    fn CurrentStimulusArray::new(i : Array[Float], n : Int, i_base : Float, rng : Xoshiro, noise_sigma? : Float) -> CurrentStimulusArray

    Construct a constant-current stimulus targeting any population's i array.

    CurrentStimulusArray::set_active

    fn CurrentStimulusArray::set_active(s : CurrentStimulusArray, v : Bool) -> Unit

    Disable / enable the array-targeted stimulus.

    CurrentStimulusArray::set_i_base

    fn CurrentStimulusArray::set_i_base(s : CurrentStimulusArray, v : Float) -> Unit

    Update the baseline current at runtime.

    CurrentStimulusIF

    pub struct CurrentStimulusIF {
    i_base : Float
    active : Array[Bool]
    pop : IF
    noise_sigma : Float
    rng : Xoshiro
    }

    CurrentStimulus — bound to an IF population. Injects current into the IF's i array each step. With active=true and noise_sigma=0, sets i[i] = i_base for every neuron. With noise_sigma > 0, adds N(0, noise_sigma) noise per step.

    CurrentStimulusIF::new

    fn CurrentStimulusIF::new(pop : IF, i_base : Float, rng : Xoshiro, noise_sigma? : Float) -> CurrentStimulusIF

    Construct a constant-current stimulus targeting an IF population.

    CurrentStimulusIF::set_active

    fn CurrentStimulusIF::set_active(s : CurrentStimulusIF, v : Bool) -> Unit

    Disable / enable the stimulus.

    CurrentStimulusIF::set_i_base

    fn CurrentStimulusIF::set_i_base(s : CurrentStimulusIF, v : Float) -> Unit

    Update the baseline current at runtime.

    CurrentSynapse

    pub(all) struct CurrentSynapse {
    tau_e : Float
    tau_i : Float
    }

    CurrentSynapse — exponential-decay current-based synapses. Fields:
    • tau_e : decay time constant for excitatory synapses (ms)
    • tau_i : decay time constant for inhibitory synapses (ms)

    CurrentSynapse::new

    Defaults match Julia's CurrentSynapse (tau_e=6ms, tau_i=2ms).

    CurrentSynapseVars

    pub struct CurrentSynapseVars {
    n : Int
    ge : Array[Float]
    gi : Array[Float]
    }

    CurrentSynapseVars — per-neuron state for CurrentSynapse.

    CurrentSynapseVars::new

    DSac

    pub struct DSac {
    policy : LinearSoftmaxPolicy
    q1 : LinearGaussianQNet
    q2 : LinearGaussianQNet
    q1_target : LinearGaussianQNet
    q2_target : LinearGaussianQNet
    alpha : Float
    }

    Distributional SAC agent: policy + twin Gaussian critics + twin targets.

    DSac::new

    fn DSac::new(n_states : Int, n_actions : Int, alpha : Float, seed : UInt64) -> DSac

    Construct a fresh DSAC agent. Online critics initialised from independent seeds; targets initialised by copying online.

    DeltaSynapse

    pub(all) struct DeltaSynapse {
    }

    DeltaSynapse — instantaneous synaptic dynamics. No parameters. The is a struct (no fields) matches Julia's struct DeltaSynapse <: AbstractDeltaParameter end.

    DeltaSynapse::new

    DeltaSynapseVars

    pub(all) struct DeltaSynapseVars {
    n : Int
    ge : Array[Float]
    gi : Array[Float]
    }

    DeltaSynapseVars — per-neuron state for DeltaSynapse.

    DeltaSynapseVars::new

    DendNeuronParameter

    pub(all) struct DendNeuronParameter {
    ds : Array[Array[Float]]
    physiology : Physiology
    geometry : Array[Array[String]]
    tree_type : DendriticTreeType
    }

    DendNeuronParameter — multicompartment neuron parameter bundle.

    ds : vector of (proximal, distal) dendritic segment lengths (Float32 in μm normalised units) physiology : Physiology {ri, rd, cd} geometry : vector of (soma_sym, dendrite_sym) pairs describing which dendritic compartment each segment targets (stored as Strings since MoonBit has no Symbols) tree_type : inferred from ds.length()

    DendNeuronParameter::custom

    fn DendNeuronParameter::custom(ds~ : Array[Array[Float]], physiology? : Physiology, geometry? : Array[Array[String]]) -> DendNeuronParameter

    Custom DendNeuronParameter constructor (caller supplies ds + physiology + geometry; tree_type auto-inferred).

    DendNeuronParameter::new

    Default DendNeuronParameter (Tripod: 2 dendrites, (200μm, 400μm) each, human physiology, s→d1 + s→d2 geometry).

    Dendrite

    pub struct Dendrite {
    n : Int
    el : Array[Float]
    c : Array[Float]
    gax : Array[Float]
    gm : Array[Float]
    l : Array[Float]
    d : Array[Float]
    gax_parent : Array[Float]
    }

    Dendrite — passive dendritic compartment parameters (per-neuron). All fields are Float32. N is implicit from the population size.

    Dendrite::custom

    fn Dendrite::custom(n : Int, el : Float, c : Float, gax : Float, gm : Float, l : Float, d : Float) -> Dendrite

    Custom Dendrite with all parameters specified per-neuron.

    Dendrite::new

    fn Dendrite::new(n : Int) -> Dendrite

    Default Dendrite constructor (matches Julia's Dendrite defaults): El=-70.6mV, C=10pF, gax=10nS, gm=1nS, l=150um, d=4um.

    DendriticTreeType

    pub enum DendriticTreeType {
    BallAndStick
    Tripod
    Multipod
    }

    DendriticTreeType — inferred from ds.length(): 1 → BallAndStick, 2 → Tripod, >2 → Multipod (unsupported here).

    DoubleExpCurrentParameter

    pub struct DoubleExpCurrentParameter {
    tau_re : Float
    tau_de : Float
    tau_ri : Float
    tau_di : Float
    }

    DoubleExpCurrentParameter — current-based DoubleExp synapse parameters (separate rise/decay for exc and inh).

    DoubleExpCurrentParameter::new

    Default DoubleExpCurrentParameter (Julia defaults: τre=1ms, τde=6ms, τri=0.5ms, τdi=2ms).

    DoubleExpCurrentSynapseVars

    pub struct DoubleExpCurrentSynapseVars {
    n : Int
    ge : Array[Float]
    gi : Array[Float]
    he : Array[Float]
    hi : Array[Float]
    }

    DoubleExpCurrentSynapseVars — per-neuron state buffers for the current-based DoubleExp synapse.

    ge, gi : the "excitatory" / "inhibitory" currents (interpreted directly as currents, not conductances) he, hi : rise-state auxiliary variables

    DoubleExpCurrentSynapseVars::new

    Allocate the per-neuron state buffers for an N-neuron population.

    DoubleExpSynapse

    pub(all) struct DoubleExpSynapse {
    tau_re : Float
    tau_de : Float
    tau_ri : Float
    tau_di : Float
    e_i : Float
    e_e : Float
    gsyn_e : Float
    gsyn_i : Float
    }

    DoubleExpSynapse — rise + decay exponential synaptic dynamics. Fields:
    • tau_re, tau_de : rise + decay for excitatory synapses (ms)
    • tau_ri, tau_di : rise + decay for inhibitory synapses (ms)
    • e_i, e_e : reversal potentials (mV)
    • gsyn_e, gsyn_i : scalar synaptic conductances

    DoubleExpSynapse::new

    Defaults match Julia's DoubleExpSynapse (tau_re=1ms, tau_de=6ms, tau_ri=0.5ms, tau_di=2ms, e_i=-75mV, e_e=0mV, gsyn_e=gsyn_i=1.0).

    DoubleExpSynapseVars

    pub(all) struct DoubleExpSynapseVars {
    n : Int
    ge : Array[Float]
    gi : Array[Float]
    he : Array[Float]
    hi : Array[Float]
    }

    DoubleExpSynapseVars — per-neuron state for DoubleExpSynapse.

    DoubleExpSynapseVars::new

    Dropout

    pub(all) struct Dropout {
    p : Float
    training : Bool
    }

    Dropout parameter container.

    Dropout::new

    fn Dropout::new(p? : Float) -> Dropout

    Construct a Dropout with dropout probability p. Defaults to 0.5.

    DropoutCache

    pub struct DropoutCache {
    mask : Array[Float]
    }

    Convenience: dropout_forward + cache wrapper for chain-style use.

    Duarte2019

    pub struct Duarte2019 {
    pv_tm : Float
    pv_el : Float
    pv_vt : Float
    pv_vr : Float
    pv_tau_abs : Float
    pv_gsyn_e : Float
    pv_gsyn_i : Float
    sst_tm : Float
    sst_el : Float
    sst_vt : Float
    sst_vr : Float
    sst_tau_abs : Float
    sst_gsyn_e : Float
    sst_gsyn_i : Float
    sst_b : Float
    sst_tw : Float
    adex_tm : Float
    adex_el : Float
    adex_vt : Float
    adex_tau_abs : Float
    adex_gsyn_e : Float
    adex_gsyn_i : Float
    }

    Duarte2019 — Duarte et al. 2019 PV / SST / AdEx model parameter constants. Mirrors SNNUtils.duarte2019 from duarte2019.jl.

    PV (parvalbumin-positive fast-spiking interneuron).

    Duarte2019::new

    fn Duarte2019::new() -> Duarte2019

    Defaults match Julia's duarte2019. Computed tm values from C/nS ratios: PV τm = 104.52pF / 9.75nS = 10.72ms ≈ 10.72F SST τm = 102.86pF / 4.61nS = 22.31ms ≈ 22.31F AdEx τm = 116.5pF / 4.64nS = 25.11ms ≈ 25.11F

    DuelingQNet

    pub struct DuelingQNet {
    n_states : Int
    n_actions : Int
    value : LinearQNet
    advantage : LinearQNet
    }

    Dueling Q-network: two LinearQNets (value + advantage).

    DuelingQNet::new

    fn DuelingQNet::new(n_states : Int, n_actions : Int, seed : UInt64) -> DuelingQNet

    EmptyConnection

    pub struct EmptyConnection {
    name : String
    }

    A no-op connection type. Returns immediately from every method. Mirrors Julia's EmptySynapse (zero-allocation connection used as a placeholder in compose(...) when a slot is intentionally left unwired).

    EmptyConnection::new

    Construct an EmptyConnection. Defaults to name = "".

    EmptyConnection::with_name

    fn EmptyConnection::with_name(_c : EmptyConnection, name : String) -> EmptyConnection

    Set the human-readable label. Returns a new struct (MoonBit fields are immutable); the caller must rebind:

    let ec = ec.with_name("placeholder")

    EmptyParam

    pub struct EmptyParam {
    p_type : String
    }

    EmptyParam — a struct representing an empty parameter. Mirrors Julia's @snn_kw struct EmptyParam with type::Symbol = :empty.

    EmptyParam::new

    fn EmptyParam::new() -> EmptyParam

    EmptyParam::type_str

    fn EmptyParam::type_str(p : EmptyParam) -> String

    Convenience: print type as a String for log output.

    EmptyParam::with_type

    fn EmptyParam::with_type(p_type : String) -> EmptyParam

    EmptyStimulus

    pub struct EmptyStimulus {
    param : EmptyParam
    records : Array[Int]
    }

    EmptyStimulus — placeholder stimulus (no operation).

    Uses the existing EmptyParam struct from structs_extra.mbt (added in v0.10.54). stimulate_empty is a no-op.

    EmptyStimulus::new

    Default EmptyStimulus (EmptyParam type="empty").

    EmptyStimulus::with_param

    fn EmptyStimulus::with_param(p : EmptyParam) -> EmptyStimulus

    Custom EmptyStimulus with explicit EmptyParam.

    Episode

    pub(all) struct Episode {
    states : Array[Int]
    actions : Array[Int]
    rewards : Array[Float]
    }

    Episode record: states, actions, rewards.

    EpisodeWithValues

    pub(all) struct EpisodeWithValues {
    states : Array[Int]
    actions : Array[Int]
    rewards : Array[Float]
    values : Array[Float]
    next_values : Array[Float]
    dones : Array[Bool]
    }

    Episode record augmented with value estimates for actor-critic.

    ExponentialLR

    pub struct ExponentialLR {
    base_lr : Float
    gamma : Float
    current_step : Int
    }

    ExponentialLR state. lr_t = base_lr * gamma^t.

    ExponentialLR::new

    fn ExponentialLR::new(base_lr : Float, gamma : Float) -> ExponentialLR

    Build an ExponentialLR scheduler.

    ExponentialLR::next

    Increment the step counter; returns a fresh state.

    ExponentialLR::step

    fn ExponentialLR::step(self : ExponentialLR) -> Float

    Compute the current learning rate.

    ExtendedIF

    pub(all) struct ExtendedIF {
    n : Int
    param : ExtendedIFParameter
    v : Array[Float]
    g_exc : Array[Float]
    g_pv : Array[Float]
    g_sst : Array[Float]
    tabs : Array[Float]
    fire : Array[Bool]
    i : Array[Float]
    }

    ExtendedIF — multi-receptor conductance-based IF neuron.

    State layout (1D arrays of length N): v — membrane potential (mV) g_exc — excitatory conductance (Exc AMPA-like, nS) g_pv — PV inhibition conductance (GABA_A fast, nS) g_sst — SST inhibition conductance (GABA_A slow, nS) tabs — absolute-refractory countdown (steps remaining) fire — spike flag at current step i — external input current (pA)

    ExtendedIF::new

    fn ExtendedIF::new(n? : Int, param? : ExtendedIFParameter) -> ExtendedIF

    Default ExtendedIF (100 neurons, parameters = ExtendedIFParameter::new()). v initialised to vr (we skip Julia's vr .+ rand(N) .* (vt - vr) RNG init; tests verify the step functions, not the RNG-driven init).

    ExtendedIFParameter

    pub(all) struct ExtendedIFParameter {
    cm : Float
    vt : Float
    vr : Float
    el : Float
    gl : Float
    tau_e : Float
    tau_i : Float
    e_i : Float
    e_e : Float
    tau_abs : Float
    alpha : Float
    }

    ExtendedIFParameter — biophysical constants of an LIF neuron with 3 receptor-specific synaptic conductances + optional dendritic interaction term.

    Float32 fields match Julia's @snn_kw struct ExtendedIFParameter defaults (Cm=250pF, Vt=-40mV, Vr=-65mV, El=-70mV, gl=10nS, τe=6ms, τi=20ms, E_i=-75mV, E_e=0mV, τabs=5ms, α=0).

    ExtendedIFParameter::custom

    fn ExtendedIFParameter::custom(cm~ : Float, vt~ : Float, vr~ : Float, el~ : Float, gl~ : Float, tau_e~ : Float, tau_i~ : Float, e_i~ : Float, e_e~ : Float, tau_abs~ : Float, alpha~ : Float) -> ExtendedIFParameter

    ExtendedIFParameter with full custom values. Mirrors Julia's ExtendedIFParameter(; Cm, Vt, Vr, El, gl, τe, τi, E_i, E_e, τabs, α) keyword-only call style.

    ExtendedIFParameter::new

    Default ExtendedIFParameter, matching Julia's ExtendedIFParameter().

    FilterRule

    pub(all) enum FilterRule {
    Greater(Int)
    Less(Int)
    Equal(Int)
    DropNoise
    }

    Filter a list of (label, neuron_count) pairs by a string predicate. MoonBit lacks first-class function references, so we approximate the Julia condition = p -> p.N > 7 by a hardcoded set of named predicates:

    • FilterNeuronCount::Greater(7) — keep pops with N > 7

    (We use the enum pattern so tests can verify the filter behaviour without needing a function reference.)

    GABAergic

    pub struct GABAergic {
    gabaa : Receptor
    gabab : Receptor
    }

    GABAergic — bundle of GABAa + GABAb receptors (inh synapse pair).

    GABAergic::custom

    fn GABAergic::custom(gabaa~ : Receptor, gabab~ : Receptor) -> GABAergic

    Custom GABAergic (caller supplies both GABAa + GABAb).

    GABAergic::new

    fn GABAergic::new(gabaa? : Receptor, gabab? : Receptor) -> GABAergic

    Default GABAergic (empty GABAa + empty GABAb).

    GIF

    pub(all) struct GIF {
    n : Int
    param : GIFParameter
    v : Array[Float]
    w : Array[Float]
    fire : Array[Bool]
    tabs : Array[Float]
    i : Array[Float]
    syn_curr : Array[Float]
    glu : Array[Float]
    gaba : Array[Float]
    }

    GIF — generalized integrate-and-fire neuron container.

    Includes the IF state (v, fire, tabs, I, syn_curr) plus the optional adaptation current w (zero unless param.tw > 0). Two synaptic conductance buffers (glu / gaba) are kept for compatibility with the SpikingSynapseIF routing conventions.

    GIF::new

    fn GIF::new(n? : Int, param? : GIFParameter) -> GIF

    Default GIF (100 neurons, parameters = GIFParameter::new()).

    GIFParameter

    pub struct GIFParameter {
    c : Float
    gl : Float
    tm : Float
    vt : Float
    vr : Float
    el : Float
    r : Float
    dt_slope : Float
    a : Float
    b : Float
    tw : Float
    tau_abs : Float
    }

    GIFParameter — full parameter struct for any point in the LIF ↔ AdEx generalisation space.

    GIFParameter::adex

    AdEx-like GIFParameter (a=4, b=80.5, τw=144ms, Vt=-50, Vr=-70.6).

    GIFParameter::new

    Default GIFParameter (pure LIF: a=b=τw=0, ΔT=2mV default).

    Glutamatergic

    pub struct Glutamatergic {
    ampa : Receptor
    nmda : Receptor
    }

    Glutamatergic — bundle of AMPA + NMDA receptors (exc synapse pair).

    Glutamatergic::custom

    fn Glutamatergic::custom(ampa~ : Receptor, nmda~ : Receptor) -> Glutamatergic

    Custom Glutamatergic (caller supplies both AMPA + NMDA).

    Glutamatergic::new

    fn Glutamatergic::new(ampa? : Receptor, nmda? : Receptor) -> Glutamatergic

    Default Glutamatergic (empty AMPA + empty NMDA ReceptorVoltage).

    GridWorld

    pub struct GridWorld {
    n_rows : Int
    n_cols : Int
    start : Int
    goal : Int
    step_penalty : Float
    goal_reward : Float
    max_steps : Int
    }

    Deterministic 2D GridWorld. n_states = n_rows * n_cols. State is (row, col) flattened to row * n_cols + col.

    GridWorld::n_states

    fn GridWorld::n_states(self : GridWorld) -> Int

    GridWorld::new

    fn GridWorld::new(n_rows : Int, n_cols : Int, max_steps : Int) -> GridWorld

    Build a simple GridWorld with start at (0, 0), goal at (n_rows-1, n_cols-1), step penalty -0.1, goal reward +1.0.

    GridWorld::step

    fn GridWorld::step(self : GridWorld, state : Int, action : Int) -> (Int, Float, Bool)

    Apply action. Returns (next_state, reward, done). Going off the grid keeps the agent in place and adds step_penalty.

    GruCellCache

    pub struct GruCellCache {
    x : Array[Float]
    h_prev : Array[Float]
    z : Array[Float]
    r : Array[Float]
    s : Array[Float]
    n : Array[Float]
    h_t : Array[Float]
    }

    Cache of intermediate values from a single GRU forward step.

    GruCellGrad

    pub struct GruCellGrad {
    d_w_z : Array[Array[Float]]
    d_w_r : Array[Array[Float]]
    d_w_n : Array[Array[Float]]
    d_b_z : Array[Float]
    d_b_r : Array[Float]
    d_b_n : Array[Float]
    }

    Accumulated parameter gradients from a sequence of backward steps. Initialise all entries to zero and accumulate via gru_cell_backward + gru_cell_sgd_step.

    GruCellGrad::zero

    fn GruCellGrad::zero(param : GruCellParam) -> GruCellGrad

    Create a zero-initialised GruCellGrad matching the param shape.

    GruCellParam

    pub struct GruCellParam {
    d_x : Int
    d_h : Int
    w_z : Array[Array[Float]]
    w_r : Array[Array[Float]]
    w_n : Array[Array[Float]]
    b_z : Array[Float]
    b_r : Array[Float]
    b_n : Array[Float]
    }

    GRU cell parameter bundle. Each w_X is d_h × (d_h + d_x); each b_X has length d_h. W_n also has shape d_h × (d_h + d_x), but its first d_h input slots see r ⊙ h_prev (not h_prev).

    GruCellParam::new

    fn GruCellParam::new(d_x : Int, d_h : Int, seed : UInt64) -> GruCellParam

    Build a fresh GRU cell parameter with Xavier-normal init for the three weight matrices and zero biases. seed controls RNG.

    GruPolicy

    pub struct GruPolicy {
    cell : GruCellParam
    w_out : Array[Array[Float]]
    b_out : Array[Float]
    }

    GRU policy: cell + output projection.

    GruPolicy::new

    fn GruPolicy::new(n_cells : Int, d_h : Int, seed : UInt64) -> GruPolicy

    Build a GRU policy. d_x = 2 + n_cells, d_h = hidden_dim, n_actions = 2 (left/right).

    GruPolicyCache

    pub struct GruPolicyCache {
    cell_caches : Array[GruCellCache]
    probs : Array[Array[Float]]
    hs : Array[Array[Float]]
    }

    Forward cache for the GRU policy.
    pub struct HH {
    param : HHParameter
    n : Int
    v : Array[Float]
    m : Array[Float]
    n_gate : Array[Float]
    h : Array[Float]
    fire : Array[Bool]
    i : Array[Float]
    ge : Array[Float]
    gi : Array[Float]
    }

    HH neuron state — a population of N Hodgkin-Huxley neurons.

    HH::new

    fn HH::new(n : Int, param : HHParameter, rng : Xoshiro) -> HH

    Construct a new HH population with n neurons.

    HHParameter

    pub struct HHParameter {
    cm : Float
    gl : Float
    el : Float
    ek : Float
    en : Float
    gn : Float
    gk : Float
    vt : Float
    tau_e : Float
    tau_i : Float
    e_e : Float
    e_i : Float
    }

    HHParameter — biophysical constants of an HH neuron.

    HHParameter::new

    Default HHParameter, matching Julia's HHParameter().

    HetRec

    pub struct HetRec {
    param : HetRecParameter
    n : Int
    v_d : Array[Float]
    v_s : Array[Float]
    is_ : Array[Float]
    r : Array[Float]
    tau_d : Array[Float]
    fire : Array[Bool]
    tabs : Array[Int]
    trace : Array[Float]
    randcache : Array[Float]
    colptr : Array[Int]
    i_syn : Array[Int]
    w_syn : Array[Float]
    }

    HetRec — heterogeneous-timescale non-recurrent population.

    HetRec::new

    fn HetRec::new(n : Int, param : HetRecParameter, rng : Xoshiro) -> HetRec

    Construct a HetRec population. Initialises:
    • v_d, v_s = 0
    • is_ = 0
    • r = Uniform(rate_low, rate_high) per neuron (in Hz; matches Julia's Uniform(0, 1) so r values are in (0, 1)).
    • τd = Uniform(tau_d_low, tau_d_high) per compartment (in ms).
    • fire = false, tabs = 0, trace = 0
    • randcache = uniform (will be re-filled each step)
    • colptr / i_syn / w_syn: sparse M where M[soma i, dendrite (j-1)*N + k] = 1 if k==i (own dendrite) or 1 with probability overlap M is then transposed to M' (dendrites × somas) and stored as CSC with colptr = soma indices, i_syn = dendrite indices.

    HetRecParameter

    pub(all) struct HetRecParameter {
    nd : Int
    overlap : Float
    tau_d_low : Float
    tau_d_high : Float
    rate_low : Float
    rate_high : Float
    tau_abs : Float
    steepness : Float
    tau_m : Float
    tau_rate : Float
    }

    HetRecParameter — parameters for the HetRec layer.

    HetRecParameter::custom

    fn HetRecParameter::custom(nd : Int, tau_m : Float, overlap : Float) -> HetRecParameter

    Custom HetRecParameter with non-default Nd, τm, and overlap. Mirrors Julia's HetRecParameter(Nd = 4, τm = 30ms, overlap = 0.2). Other fields keep their defaults.

    HetRecParameter::new

    HetRecParameter with Julia's defaults.

    HeterogeneousModel

    pub(all) struct HeterogeneousModel {
    pops : Array[AnyPop]
    conns : Array[SpikingSynapse]
    stims : Array[AnyStim]
    time : Time
    monitors : Array[Monitor]
    stdp_entries : Array[STDPEntryKind]
    stp_entries : Array[STPEntryKind]
    }

    Heterogeneous model: populations, connections, and stimuli. Mirrors SNN's Model{pop, conn, stim}.
    pub struct IF {
    param : IFParameter
    spike : PostSpike
    n : Int
    v : Array[Float]
    w : Array[Float]
    fire : Array[Bool]
    tabs : Array[Int]
    i : Array[Float]
    syn_curr : Array[Float]
    ge : Array[Float]
    gi : Array[Float]
    he : Array[Float]
    hi : Array[Float]
    glu : Array[Float]
    gaba : Array[Float]
    gsyn_e : Array[Float]
    gsyn_i : Array[Float]
    e_e : Float
    e_i : Float
    tre : Float
    tde : Float
    tri : Float
    tdi : Float
    }

    IF neuron state — a population of N integrate-and-fire neurons.

    IF::new

    fn IF::new(n : Int, param : IFParameter, rng : Xoshiro) -> IF

    Construct a new IF population with n neurons.

    IF::with_gsyn

    fn IF::with_gsyn(n : Int, base : IFParameter, gsyn_e : Float, gsyn_i : Float, rng : Xoshiro) -> IF

    Convenience constructor: build an IF + apply gsyn scales in one step.

    IFCANAHP

    pub(all) struct IFCANAHP {
    n : Int
    param : IFCANAHParameter
    v : Array[Float]
    fire : Array[Bool]
    tabs : Array[Float]
    i : Array[Float]
    p_can : Array[Float]
    p_ahp : Array[Float]
    ca : Array[Float]
    syn_curr : Array[Float]
    }

    IFCANAHP — CAN-AHP Integrate-and-Fire neuron with multi-receptor synaptic input (glu_receptors + gaba_receptors).

    We simplify Julia's 4-receptor array (AMPA / NMDA / GABAa / GABAb) to a single combined synaptic current syn_curr for bit-exactness with the existing MoonBit IF/SpikingSynapse infrastructure. The core CAN-AHP dynamics (Ca dynamics, pCAN/pAHP gating, membrane) are ported unchanged.

    IFCANAHP::new

    fn IFCANAHP::new(n? : Int, param? : IFCANAHParameter) -> IFCANAHP

    Default IFCANAHP (100 neurons, parameters = IFCANAHParameter::new()).

    IFCANAHParameter

    pub(all) struct IFCANAHParameter {
    area : Float
    c : Float
    vt : Float
    vr : Float
    tau_abs : Float
    gl : Float
    vl : Float
    delta_ca : Float
    ca0 : Float
    tau_ca : Float
    v_ahp : Float
    g_ahp : Float
    alpha_ahp : Float
    beta_ahp : Float
    gamma_ahp : Float
    v_can : Float
    g_can : Float
    beta_can : Float
    alpha_can : Float
    gamma_can : Float
    }

    IF_CANAHPParameter — biophysical constants for the CAN-AHP LIF.

    Float32 fields match Julia's defaults (area=5e-4cm², C=1uF/cm², Vt=-50mV, Vr=-65mV, τabs=3ms, gl=0.05mS/cm², VL=-70mV, ΔCa=0.2uM, Ca0=0.1uM, τCa=100ms, V_AHP=-90mV, g_AHP=0, α_AHP=0.125uM/ms, β_AHP=0.025uM/ms, γ_AHP=1, V_CAN=30mV, g_CAN=0, β_CAN=0.025/ms, α_CAN=0.03125uM/ms, γ_CAN=1).

    IFCANAHParameter::new

    Default IFCANAHParameter (CAN/AHP off by default — g_can=0, g_ahp=0).

    IFParameter

    pub struct IFParameter {
    c : Float
    gl : Float
    tm : Float
    vt : Float
    vr : Float
    el : Float
    r : Float
    dt_slope : Float
    a : Float
    b : Float
    tw : Float
    }

    IFParameter — holds the biophysical constants of an LIF neuron.

    Field values match the Julia defaults in Float32 arithmetic.

    IFParameter::custom

    fn IFParameter::custom(tm : Float, vt : Float, vr : Float, el : Float, r : Float) -> IFParameter

    IFParameter with custom time-constant, threshold, reset, resting potential, and resistance. Matches SNN.IFParameter(; τm=20ms, R=100MΩ, Vt=-50mV, Vr=-60mV, El=-60mV) from CUBA.jl. Note: in MoonBit normalised units, R is in mho; 100*MΩ = 100*mohm = 0.1F.

    IFParameter::new

    Default IFParameter, matching Julia's IFParameter().

    IFParameter::with_el

    fn IFParameter::with_el(el : Float) -> IFParameter

    IFParameter with a custom resting potential el. Matches SNN.IFParameter(; El = -49mV) from IF_net.jl.

    IFParameterGsyn

    pub(all) struct IFParameterGsyn {
    base : IFParameter
    gsyn_e : Float
    gsyn_i : Float
    }

    IFParameterGsyn — IFParameter + population-wide synaptic conductance scales gsyn_e (excitatory) and gsyn_i (inhibitory). All other IF dynamics are unchanged.

    IFParameterGsyn::apply

    fn IFParameterGsyn::apply(s : IFParameterGsyn, p : IF) -> Unit

    Apply the population-wide gsyn_e / gsyn_i scales uniformly to every neuron's per-neuron gsyn arrays. Mirrors Julia's effect of constructing an IF with an IFParameterGsyn (every neuron gets the same conductance scale).

    Usage: let pop = IF::new(n, IFParameter::new(), rng) let gs = IFParameterGsyn::from_base(IFParameter::new(), 1.04F, 0.84F) gs.apply(pop)

    IFParameterGsyn::from_base

    fn IFParameterGsyn::from_base(base : IFParameter, gsyn_e : Float, gsyn_i : Float) -> IFParameterGsyn

    Construct an IFParameterGsyn from an existing IFParameter, copying the underlying dynamics and overriding only the gsyn scales. Used to upgrade an IFParameter to a Gsyn variant without rebuilding all the field-by-field.

    IFParameterGsyn::new

    fn IFParameterGsyn::new(base : IFParameter, gsyn_e : Float, gsyn_i : Float) -> IFParameterGsyn

    Construct an IFParameterGsyn from explicit field values. Mirrors Julia's IFParameterGsyn(τm = 104.52pF/9.75nS,El = -64.33mV, Vt = -38.97mV, Vr = -57.47mV, τabs = 0.5ms,gsyn_e = 1.04nS, gsyn_i = 0.84nS).

    IFSinExp

    pub struct IFSinExp {
    param : IFSinExpParameter
    spike : PostSpike
    n : Int
    v : Array[Float]
    w : Array[Float]
    fire : Array[Bool]
    tabs : Array[Int]
    i : Array[Float]
    syn_curr : Array[Float]
    ge : Array[Float]
    gi : Array[Float]
    glu : Array[Float]
    gaba : Array[Float]
    gsyn_e : Array[Float]
    gsyn_i : Array[Float]
    fire_t : Array[Int]
    fire_t_delta : Array[Int]
    }

    IFSinExp neuron state — population of N integrate-and-fire neurons with single-exp synaptic dynamics.

    IFSinExp::new

    fn IFSinExp::new(n : Int, param : IFSinExpParameter, rng : Xoshiro) -> IFSinExp

    Build an IFSinExp population of size n with param and random initial state drawn from rng.

    IFSinExpParameter

    pub struct IFSinExpParameter {
    c : Float
    gl : Float
    tm : Float
    vt : Float
    vr : Float
    el : Float
    r : Float
    dt_slope : Float
    a : Float
    b : Float
    tw : Float
    tau_e : Float
    tau_i : Float
    }

    IFSinExpParameter — IF neuron parameters with single-exp synapse time constants (τe, τi). Structurally identical to IFParameter; kept as a separate type for documentation and to match Julia's IFSinExpParameter named tuple in LKD2014SingleExp.

    IFSinExpParameter::lkd_pv

    IFSinExpParameter with LKD 2014 PV defaults (El=-62mV, Vr=-57.47mV, Vt=-52mV, τm=20ms, τe=6, τi=2). Matches Julia's IFSinExpParameter(El=-62, Vr=-57.47, Vt=-52, τm=20, τi=2, τe=6).

    IFSinExpParameter::new

    Default IFSinExpParameter, matching Julia's IFSinExpParameter() with τe=6, τi=2.

    ISTDP

    pub(all) struct ISTDP {
    eta : Float
    r0 : Float
    vd : Float
    tau_d : Double
    tau_y : Float
    alpha : Float
    w_min : Float
    w_max : Float
    }
    ISTDP (Vogels 2011 inhibitory STDP) — port of dump.jl::ISTDP.

    ISTDP::new

    fn ISTDP::new() -> ISTDP
    Defaults from ISTDP struct (η=0.2, r0=0.01, vd=-70, τd=5, τy=20, α=2r0τy=0.4, j⁻=2.78, j⁺=243).

    ISTDP::vogels

    fn ISTDP::vogels() -> ISTDP
    Convenience: vogels_istdp() factory — returns ISTDP configured for inhibitory synapse homeostatic plasticity.
    pub(all) struct IZ {
    param : IZParameter
    n : Int
    v : Array[Float]
    u : Array[Float]
    fire : Array[Bool]
    i : Array[Float]
    ge : Array[Float]
    gi : Array[Float]
    tabs : Array[Int]
    tabs_const : Int
    }

    IZ neuron state — a population of N Izhikevich neurons.

    IZ::init_uniform

    fn IZ::init_uniform(n : Int, param : IZParameter, v_init : Float, u_init : Float) -> IZ

    Construct an IZ population with uniform initial v=v_init, u=u_init for all neurons. Convenience for restarting many neurons at the same state.

    IZ::init_with

    fn IZ::init_with(n : Int, param : IZParameter, v_init : Array[Float], u_init : Array[Float]) -> IZ

    Construct an IZ population with custom initial v / u arrays. v_init and u_init must have length n; if not, the constructor panics. Other fields (fire, i, ge, gi) are zero-initialised. Useful for restarting a simulation from a saved state.

    IZ::init_with_postspike

    fn IZ::init_with_postspike(n : Int, param : IZParameter, v_init : Float, u_init : Float, postspike : IZPostSpike) -> IZ

    Construct an IZ population with custom PostSpike (refractory) state. The tabs array tracks the per-neuron countdown; refractory blocks the membrane update but does not pause synapse decay.

    IZ::new

    fn IZ::new(n : Int, param : IZParameter, rng : Xoshiro) -> IZ

    Construct a new IZ population with n neurons.

    IZParameter

    pub struct IZParameter {
    a : Float
    b : Float
    c : Float
    d : Float
    tau_e : Float
    tau_i : Float
    e_e : Float
    e_i : Float
    }

    IZParameter — biophysical constants of an Izhikevich neuron.

    IZParameter::custom

    fn IZParameter::custom(a : Float, b : Float, c : Float, d : Float) -> IZParameter

    Custom IZParameter with arbitrary (a, b, c, d) — matches Julia's IZParameter(; a, b, c, d) keyword constructor. τe, τi, Ee, Ei default to the IZParameter defaults (5ms, 10ms, 0mV, -80mV).

    IZParameter::fs

    Fast-spiking (FS) Izhikevich defaults: a=0.1, b=0.2, c=-65, d=2. Matches the inhibitory IZ parameters in Izikievich_net.jl.

    IZParameter::new

    Default IZParameter, matching Julia's IZParameter().

    IZParameter::rs

    Regular-spiking (RS) Izhikevich defaults: a=0.02, b=0.2, c=-65, d=8. Matches the RS = SNN.IZ(; param = SNN.IZParameter(; a = 0.02, b = 0.2,c = -65, d = 8)) setup in the Izikievich_neuron.jl example.

    IZPostSpike

    pub(all) struct IZPostSpike {
    tabs_const : Int
    }

    IZPostSpike — minimal absolute-refractory state for IZ. tabs_const is the absolute refractory period in timesteps (matches the IF/ AdEx PostSpike shape; reused here for IZ compatibility with the sim! loop composition).

    IZPostSpike::custom

    fn IZPostSpike::custom(tabs_const : Int) -> IZPostSpike

    IZPostSpike with custom absolute refractory period (in timesteps).

    IZPostSpike::new

    Default IZPostSpike — 1 ms absolute refractory (≈ 8 timesteps at dt=0.125ms).

    Identity

    pub struct Identity {
    n : Int
    param : IdentityParameter
    g : Array[Float]
    h : Array[Float]
    fire : Array[Bool]
    spikecount : Array[Float]
    }

    Construct an Identity neuron population.

    Identity::new

    fn Identity::new(n : Int, param : IdentityParameter) -> Identity

    Construct an Identity population with N neurons.

    IdentityParameter

    pub(all) struct IdentityParameter {
    dummy : Float
    }

    Identity neuron parameters (empty struct).

    Image

    pub struct Image {
    data : Array[Float]
    n : Int
    c : Int
    h : Int
    w : Int
    }

    4D NCHW Float32 image tensor.

    Image::numel

    fn Image::numel(self : Image) -> Int

    Total element count.

    Image::shape

    fn Image::shape(self : Image) -> (Int, Int, Int, Int)

    Image dimensions as (n, c, h, w).

    Image::zeros

    fn Image::zeros(n : Int, c : Int, h : Int, w : Int) -> Image

    Build an empty NCHW image filled with zeros.

    InhomogeneousPoisson

    pub(all) struct InhomogeneousPoisson {
    n : Int
    param : InhomogeneousPoissonParam
    fire : Array[Bool]
    r : Array[Float]
    noise : Array[Float]
    randcache_beta : Array[Float]
    }

    Inhomogeneous Poisson population (Julia's VariablePoisson). Rate at each neuron adapts over rate_timescale toward r0, with multiplicative Ornstein-Uhlenbeck noise on β scale.

    InhomogeneousPoisson::new

    Construct an InhomogeneousPoisson with n neurons and an Xoshiro RNG. All state arrays are zero-initialised except r (initialised to r0) and randcache_beta (initialised to fresh uniform draws).

    InhomogeneousPoissonParam

    pub(all) struct InhomogeneousPoissonParam {
    beta : Float
    tau : Float
    r0 : Float
    rate_timescale : Float
    }

    InhomogeneousPoissonParam — Julia's VariablePoissonParameter.

    β : noise modulation amplitude (default 0.0) τ : noise time-constant (default 50.0 ms) r0 : target rate (default 1000 Hz = 1.0 in normalised units) rate_timescale : time-constant for rate adaptation (default 400 ms)

    InhomogeneousPoissonParam::custom

    fn InhomogeneousPoissonParam::custom(beta : Float, tau : Float, r0 : Float) -> InhomogeneousPoissonParam

    Custom InhomogeneousPoissonParam with explicit β / τ / r0. Mirrors Julia's VariablePoissonParameter(β = 0.1, τ = 100ms,r0 = 500Hz).

    InhomogeneousPoissonParam::new

    Julia defaults: β=0.0, τ=50ms, r0=1kHz, rate_timescale=400ms. In normalised units, 1 kHz = 1.0.

    Inst

    pub enum Inst[A] {
    Var(A)
    Unary((A) -> A, (A, A) -> A, Loc[A])
    Binary((A, A) -> A, (A, A, A) -> A, (A, A, A) -> A, Loc[A], Loc[A])
    }

    Recorded instruction in the tape. Var stores a literal value; Unary and Binary capture operations with their forward and backward callbacks.

    IstdpPotential

    pub(all) struct IstdpPotential {
    eta : Float
    v0 : Float
    tau_y : Float
    w_max : Float
    w_min : Float
    }

    IstdpPotential — Vogels 2011 inhibitory STDP with potential-based post-synaptic trace.

    Differs from IstdpRate in two ways:
    1. r is replaced by v0 (a reference potential in mV). The pre-spike weight update becomes w[s] += eta * (tpost[i] - v0) where tpost[i] tracks the post-synaptic membrane potential v_post[i] (low-pass filtered with time constant tau_y) rather than spike count.
    2. The default learning rate is smaller (eta=0.001pA) and the trace time constant is larger (tau_y=200ms), reflecting the longer memory of potential-based traces.

    Defaults match Julia's iSTDPPotential (eta=0.001pA, v0=-50mV, tau_y=200ms, w_max=243pF, w_min=0.01pF).

    IstdpPotential::new

    IstdpPotentialEntry

    pub(all) struct IstdpPotentialEntry {
    conn_index : Int
    n_pre : Int
    n_post : Int
    param : IstdpPotential
    vars : IstdpPotentialVariables
    t_now : Array[Float]
    }

    IstdpPotentialEntry — bundles a connection's IstdpPotential rule with per-step state plus an internal t_now clock.

    IstdpPotentialEntry::change_plasticity

    fn IstdpPotentialEntry::change_plasticity(e : IstdpPotentialEntry, new_param : IstdpPotential) -> Unit

    Runtime swap of IstdpPotential parameters. Preserves trace state.

    IstdpPotentialEntry::new

    fn IstdpPotentialEntry::new(conn_index : Int, n_pre : Int, n_post : Int, param? : IstdpPotential) -> IstdpPotentialEntry

    IstdpPotentialVariables

    pub(all) struct IstdpPotentialVariables {
    tpre : Array[Float]
    tpost : Array[Float]
    }

    IstdpPotentialVariables — same shape as IstdpRateVariables: just the running trace values tpre and tpost. The tpost[i] trace here is updated each step to low-pass-filter v_post[i], so the step function takes the post-synaptic membrane potential as an additional input.

    IstdpPotentialVariables::new

    fn IstdpPotentialVariables::new(n_pre : Int, n_post : Int) -> IstdpPotentialVariables

    IstdpRate

    pub(all) struct IstdpRate {
    eta : Float
    r : Float
    tau_y : Float
    w_max : Float
    w_min : Float
    }

    IstdpRate — Vogels 2011 inhibitory STDP with rate homeostasis.

    Fields:
    • eta : learning rate (Julia: 0.01pA, normalised to 0.01F)
    • r : target post-synaptic rate (Julia: 3Hz, internal units Hz*hz)
    • tau_y : STDP time constant (Julia: 50ms)
    • w_max / w_min : weight bounds (Julia: 243pF / 0.01pF)

    IstdpRate::new

    fn IstdpRate::new() -> IstdpRate

    Defaults match Julia's iSTDPRate (eta=0.01pA, r=3Hz, tau_y=50ms, w_max=243pF, w_min=0.01pF). With @snn_kw's unit normalisation (pA=1.0F, hz=0.001F, ms=1.0F, pF=1.0F), these map directly to the Float32 values shown here.

    IstdpRateEntry

    pub(all) struct IstdpRateEntry {
    conn_index : Int
    n_pre : Int
    n_post : Int
    param : IstdpRate
    vars : IstdpRateVariables
    t_now : Array[Float]
    }

    IstdpRateEntry — bundles a connection's IstdpRate rule with the per-step state plus an internal t_now clock so the compose layer can advance simulation time across step calls.

    IstdpRateEntry::change_plasticity

    fn IstdpRateEntry::change_plasticity(e : IstdpRateEntry, new_param : IstdpRate) -> Unit

    Runtime swap of IstdpRate parameters. Preserves trace state.

    IstdpRateEntry::new

    fn IstdpRateEntry::new(conn_index : Int, n_pre : Int, n_post : Int, param? : IstdpRate) -> IstdpRateEntry

    Construct an IstdpRateEntry. vars is zero-initialised; t_now starts at 0.0F.

    IstdpRateVariables

    pub(all) struct IstdpRateVariables {
    tpre : Array[Float]
    tpost : Array[Float]
    }

    IstdpRateVariables — per-connection plasticity state for IstdpRate.

    Unlike STDPVariables (Gerstner), iSTDP keeps only the current trace values (tpre, tpost). The trace model is continuous-time Euler integration, so there is no last_pre / last_post bookkeeping.

    IstdpRateVariables::new

    fn IstdpRateVariables::new(n_pre : Int, n_post : Int) -> IstdpRateVariables

    IstdpTime

    pub(all) struct IstdpTime {
    eta : Float
    tau_y : Float
    w_max : Float
    w_min : Float
    }

    IstdpTime — Vogels 2011 inhibitory STDP time-based parameter type.

    Mirrors Julia's iSTDPTime{FT = Float32} <: iSTDPParameter from iSTDP.jl. This is a parameter-only struct — the Julia source defines it but does not implement a separate step function for it (the step rules for iSTDP.jl use iSTDPRate / iSTDPPotential only). The struct exists so users can construct it as an LTPParam and the plasticity_params.jl testset can verify its fields.

    Fields match Julia defaults (eta=0.01pA, tau_y=50ms, w_max=243pF, w_min=0.01pF).

    IstdpTime::new

    fn IstdpTime::new() -> IstdpTime

    LKD2014

    pub struct LKD2014 {
    tm : Float
    vt : Float
    el : Float
    vr : Float
    r : Float
    tau_abs : Float
    e_i : Float
    e_e : Float
    at : Float
    pv_tm : Float
    pv_el : Float
    pv_vr : Float
    pv_vt : Float
    }

    LKD2014 — Litwin-Kumar-Doiron 2014 balanced-network parameter constants. Mirrors SNNUtils.LKD2014 named-tuple from lkd2014.jl.

    Note: Julia's AdExParameter(τm = 300pF / 15.0nS) evaluates at compile time. In MoonBit we compute the value manually: 300pF / 15.0nS = (300 * 1e-12 F) / (15.0 * 1e-9 S) = (300 / 15.0) * 1e-3 s = 20 ms So lkd_tm = 20.0F (in internal ms units).

    LKD2014::new

    fn LKD2014::new() -> LKD2014

    Defaults match Julia's LKD2014. Computed tm from Float32(300 / 15) (pF / nS, since pF=1.0 and nS=1.0 internally).

    LKD2014Soma

    pub struct LKD2014Soma {
    es_e_p : Float
    es_e_mu : Float
    e_to_pv_p : Float
    e_to_pv_mu : Float
    pv_to_e_p : Float
    pv_to_e_mu : Float
    pv_to_pv_p : Float
    pv_to_pv_mu : Float
    }

    LKD2014Soma — soma-level connection parameter template for the Litwin-Kumar-Doiron 2014 model. Mirrors the lkd2014_soma named tuple in refs/SNNUtils.jl/src/models/connections.jl. Each field is a (probability p, mean μ, distribution dist, optional σ) tuple.

    LKD2014Soma::new

    Defaults match Julia's lkd2014_soma: EsE: p=0.2, μ=2.76 (LogNormal) E→PV: p=0.2, μ=1.27 (LogNormal) PV→E: p=0.2, μ=48.7 (LogNormal) PV→PV: p=0.2, μ=16.2 (LogNormal)

    Layer

    pub enum Layer {
    Conv2d(Conv2dParam)
    ReLU
    MaxPool2d(MaxPool2dParam)
    Flatten
    Linear(LinearParam)
    BatchNorm2d(BatchNorm2d)
    LayerNorm(LayerNorm)
    }

    Layer enum: each variant holds the parameter struct (or is unit for stateless layers).

    Layer::batch_norm2d

    fn Layer::batch_norm2d(bn : BatchNorm2d) -> Layer

    Layer::conv2d

    fn Layer::conv2d(param : Conv2dParam) -> Layer

    Layer::flatten

    fn Layer::flatten() -> Layer

    Layer::layer_norm

    fn Layer::layer_norm(ln : LayerNorm) -> Layer

    Layer::linear

    fn Layer::linear(param : LinearParam) -> Layer

    Layer::max_pool2d

    fn Layer::max_pool2d(param : MaxPool2dParam) -> Layer

    Layer::relu

    fn Layer::relu() -> Layer

    LayerCache

    pub enum LayerCache {
    Conv2d(Array[Float], Int, Int, Int, Int)
    ReLU(Array[Float])
    MaxPool2d(Array[Int], Int, Int, Int, Int, Int, Int, Int, Int)
    Flatten
    Linear(Array[Float], Int)
    BatchNorm2d(BatchNormCache)
    LayerNorm(LayerNormCache)
    }

    Per-layer forward cache. Each variant holds the data the backward pass needs (input activations, BN stats, max-pool argmax indices, etc.).

    LayerGrad

    pub enum LayerGrad {
    Empty
    Conv2d(Array[Float], Array[Float])
    Linear(Array[Float], Array[Float])
    BatchNorm2d(Array[Float], Array[Float])
    LayerNorm(Array[Float], Array[Float])
    MaxPool2d
    }

    Per-layer parameter gradients. Empty for layers without learnable params (ReLU, Flatten, MaxPool2d).

    LayerNorm

    pub struct LayerNorm {
    gamma : Array[Float]
    beta : Array[Float]
    eps : Float
    c : Int
    h : Int
    w : Int
    }

    LayerNorm parameter container. Affine params have one element per feature position (length = c * h * w).

    LayerNorm::new

    fn LayerNorm::new(c : Int, h : Int, w : Int, eps? : Float) -> LayerNorm

    Build a LayerNorm for the given feature shape (c, h, w). gamma is initialised to all-1, beta to all-0.

    LayerNorm::with_gamma_beta

    fn LayerNorm::with_gamma_beta(gamma : Array[Float], beta : Array[Float], c : Int, h : Int, w : Int, eps? : Float) -> LayerNorm

    Convenience constructor with explicit gamma / beta arrays (no copy).

    LayerNormCache

    pub struct LayerNormCache {
    mean : Array[Float]
    inv_std : Array[Float]
    x_centered : Array[Float]
    n : Int
    c : Int
    h : Int
    w : Int
    chw : Int
    }

    Cache returned by layer_norm_forward and consumed by layer_norm_backward.

    LayerScale

    pub struct LayerScale {
    dim : Int
    gamma : Array[Float]
    }

    LayerScale parameter container.

    LayerScale::new

    fn LayerScale::new(dim : Int, init_value? : Float) -> LayerScale

    Construct a LayerScale with gamma initialised to init_value (typically 1e-4 or smaller). Default: 1e-4.

    LeNet5

    pub struct LeNet5 {
    layers : Array[Layer]
    }

    Classic LeNet-5 (LeCun 1998) for 32x32 grayscale input.

    C1: Conv 1->6, 5x5, stride=1, pad=0 -> [n, 6, 28, 28] S2: MaxPool 2x2, stride=2 -> [n, 6, 14, 14] C3: Conv 6->16, 5x5, stride=1, pad=0 -> [n, 16, 10, 10] S4: MaxPool 2x2, stride=2 -> [n, 16, 5, 5] C5: Conv 16->120, 5x5, stride=1, pad=0 -> [n, 120, 1, 1] (acts as FC) F6: Linear 120 -> 84 Out: Linear 84 -> 10

    Note: We use MaxPool instead of AvgPool (a common modern variant; not bit-exact to the original paper but produces an equally-valid classifier architecture).

    LeNet5::new

    fn LeNet5::new(seed : UInt64) -> LeNet5

    Build a LeNet5. Always 32x32 grayscale input -> 10 classes.

    LinearGaussianQNet

    pub struct LinearGaussianQNet {
    n_states : Int
    n_actions : Int
    w_mu : Array[Array[Float]]
    b_mu : Array[Float]
    w_sigma : Array[Array[Float]]
    b_sigma : Array[Float]
    }

    Linear Q-network with Gaussian heads. Each (state, action) pair has its own μ and σ. Stored as two parallel weight matrices + bias vectors.

    LinearGaussianQNet::new

    fn LinearGaussianQNet::new(n_states : Int, n_actions : Int, seed : UInt64) -> LinearGaussianQNet

    Construct a Gaussian critic. Initial μ is small-random, σ is initialised to 1.0 + small jitter (in log-space for stability under direct σ updates).

    LinearParam

    pub struct LinearParam {
    weight : Array[Float]
    bias : Array[Float]
    in_features : Int
    out_features : Int
    }

    Linear (Dense) parameter container. weight is laid out as [out_features, in_features] row-major; bias is [out_features].

    LinearParam::new

    fn LinearParam::new(weight : Array[Float], bias : Array[Float], in_features : Int, out_features : Int) -> LinearParam

    Build a LinearParam from raw arrays. Does NOT copy the arrays.

    LinearQNet

    pub struct LinearQNet {
    n_states : Int
    n_actions : Int
    w : Array[Array[Float]]
    b : Array[Float]
    }

    Q-network: linear mapping from one-hot state to Q-values per action. W has shape n_actions × n_states. Bias has shape n_actions.

    LinearQNet::new

    fn LinearQNet::new(n_states : Int, n_actions : Int, seed : UInt64) -> LinearQNet

    LinearSoftmaxPolicy

    pub struct LinearSoftmaxPolicy {
    n_states : Int
    n_actions : Int
    w : Array[Array[Float]]
    }

    Linear softmax policy: logits = W · x, π = softmax(logits).

    LinearSoftmaxPolicy::new

    fn LinearSoftmaxPolicy::new(n_states : Int, n_actions : Int, seed : UInt64) -> LinearSoftmaxPolicy

    LinearValueNet

    pub struct LinearValueNet {
    n_states : Int
    w : Array[Float]
    }

    Linear value network: V(s) = w · x_s, where x_s is the one-hot encoding of state s. Length n_states weights.

    LinearValueNet::new

    fn LinearValueNet::new(n_states : Int, seed : UInt64) -> LinearValueNet

    Loc

    pub enum Loc[A] {
    Const(A)
    Memory(Int)
    }

    Reference to a value in the tape — either a literal constant (no gradient propagation) or a memory slot produced by an earlier instruction.

    LstmCellCache

    pub struct LstmCellCache {
    x : Array[Float]
    h_prev : Array[Float]
    c_prev : Array[Float]
    f : Array[Float]
    ig : Array[Float]
    c_tilde : Array[Float]
    o : Array[Float]
    c_t : Array[Float]
    tanh_c_t : Array[Float]
    h_t : Array[Float]
    }

    Cache of intermediate values from a single LSTM forward step.

    LstmCellGrad

    pub struct LstmCellGrad {
    d_w_f : Array[Array[Float]]
    d_w_i : Array[Array[Float]]
    d_w_c : Array[Array[Float]]
    d_w_o : Array[Array[Float]]
    d_b_f : Array[Float]
    d_b_i : Array[Float]
    d_b_c : Array[Float]
    d_b_o : Array[Float]
    }

    Accumulated parameter gradients from a sequence of backward steps. Initialise all entries to zero and accumulate via lstm_cell_backward + lstm_cell_sgd_step.

    LstmCellGrad::zero

    Create a zero-initialised LstmCellGrad matching the param shape.

    LstmCellParam

    pub struct LstmCellParam {
    d_x : Int
    d_h : Int
    w_f : Array[Array[Float]]
    w_i : Array[Array[Float]]
    w_c : Array[Array[Float]]
    w_o : Array[Array[Float]]
    b_f : Array[Float]
    b_i : Array[Float]
    b_c : Array[Float]
    b_o : Array[Float]
    }

    LSTM cell parameter bundle. Each w_X is d_h × (d_h + d_x); each b_X has length d_h.

    LstmCellParam::new

    fn LstmCellParam::new(d_x : Int, d_h : Int, seed : UInt64) -> LstmCellParam

    Build a fresh LSTM cell parameter with Xavier-normal init for the four weight matrices and zero biases. seed controls RNG.

    LstmPolicy

    pub struct LstmPolicy {
    cell : LstmCellParam
    w_out : Array[Array[Float]]
    b_out : Array[Float]
    }

    LSTM policy: cell + output projection.

    LstmPolicy::new

    fn LstmPolicy::new(n_cells : Int, d_h : Int, seed : UInt64) -> LstmPolicy

    Build an LSTM policy. d_x = 2 + n_cells, d_h = hidden_dim, n_actions = 2 (left/right).

    LstmPolicyCache

    pub struct LstmPolicyCache {
    cell_caches : Array[LstmCellCache]
    probs : Array[Array[Float]]
    hs : Array[Array[Float]]
    }

    Forward cache: per-step cell caches + per-step projected info.

    LstmQNet

    pub struct LstmQNet {
    cell : LstmCellParam
    w_out : Array[Array[Float]]
    b_out : Array[Float]
    }

    LSTM Q-net: cell + linear head producing one Q value per action.

    LstmQNet::new

    fn LstmQNet::new(d_x : Int, d_h : Int, n_actions : Int, seed : UInt64) -> LstmQNet

    LstmQNetCache

    pub struct LstmQNetCache {
    cell_caches : Array[LstmCellCache]
    qs : Array[Array[Float]]
    hs : Array[Array[Float]]
    }

    Cache from one forward pass through an LstmQNet.

    MHAGrad

    pub struct MHAGrad {
    d_w_q : Array[Float]
    d_b_q : Array[Float]
    d_w_k : Array[Float]
    d_b_k : Array[Float]
    d_w_v : Array[Float]
    d_b_v : Array[Float]
    d_w_o : Array[Float]
    d_b_o : Array[Float]
    }

    Gradient bundle for an MHA module: d_weight / d_bias for each of the four projections.

    MLP

    pub struct MLP {
    layers : Array[Layer]
    sizes : Array[Int]
    }

    A multi-layer perceptron. sizes is [in, h1, h2, ..., out]; the bottle allocates Linear layers between adjacent entries with a ReLU between every pair (no ReLU after the final Linear).

    MLP::new

    fn MLP::new(sizes : Array[Int], seed : UInt64) -> MLP

    Build an MLP. seed controls the xoshiro RNG used for He init (sqrt(2/n_in) per Linear layer). Use the same seed for reproducible bottles.

    MLP::num_params

    fn MLP::num_params(self : MLP) -> Int

    Total learnable parameter count.

    MaParam

    pub struct MaParam {
    q : Int
    intercept : Float
    thetas : Array[Float]
    }

    MA(q) parameter bundle: intercept + q moving-average coefficients. thetas[i] corresponds to lag-(i+1) coefficient θᵢ₊₁.

    MaParam::new

    fn MaParam::new(q : Int, intercept : Float, thetas : Array[Float]) -> MaParam

    Build an MA(q) parameter from explicit coefficients. thetas is expected to have length q; the first entry lags ε[t-1].

    MarkramSTPEntry

    pub(all) struct MarkramSTPEntry {
    conn_index : Int
    vars : MarkramSTPVariables
    param : MarkramSTPParameter
    }

    MarkramSTPEntry — bundles a connection's STP rule with its mutable per-step state so the compose layer can apply it automatically.

    MarkramSTPEntry::new

    fn MarkramSTPEntry::new(conn_index : Int, n_pre : Int, n_post : Int, param? : MarkramSTPParameter) -> MarkramSTPEntry

    Construct a MarkramSTPEntry for the synapse at conn_index in HeterogeneousModel.conns. Initialises per-pre state from param.

    MarkramSTPEntry::set_stp_active

    fn MarkramSTPEntry::set_stp_active(entry : MarkramSTPEntry, active : Bool) -> Unit

    Toggle STP on/off for this entry — mirrors Julia's set_STP!(s::SpikingSynapse, active). When active=false, markram_stp_step becomes a no-op for this entry (traces don't decay, weights use full ρ=1.0 instead of u*x). When active=true, normal Markram STP resumes from whatever u/x state is currently in vars (no reset).

    Note: MoonBit identifiers can't contain !, so the trailing bang is dropped. Semantic is identical.

    MarkramSTPEntryHet

    pub(all) struct MarkramSTPEntryHet {
    conn_index : Int
    vars : MarkramSTPVariables
    param : MarkramSTPParameterHet
    }

    MarkramSTPVariables (shared by both homogeneous and heterogeneous variants) — per-pre-synaptic-neuron STP state. The struct is reused; the heterogeneous step function reads τD[j], τF[j], U[j] from MarkramSTPParameterHet arrays instead of scalars.

    MarkramSTPEntryHet::new

    fn MarkramSTPEntryHet::new(conn_index : Int, n_pre : Int, n_post : Int, param : MarkramSTPParameterHet) -> MarkramSTPEntryHet

    Construct a MarkramSTPEntryHet for the synapse at conn_index in HeterogeneousModel.conns. Initialises per-pre state from the per-neuron U array in param.

    MarkramSTPEntryHet::set_stp_active

    fn MarkramSTPEntryHet::set_stp_active(entry : MarkramSTPEntryHet, active : Bool) -> Unit

    Heterogeneous-parameter variant of set_stp_active — toggles the same vars.active[0] flag.

    MarkramSTPEntryTimestep

    pub(all) struct MarkramSTPEntryTimestep {
    conn_index : Int
    vars : MarkramSTPVariables
    param : MarkramSTPParameterTimestep
    }

    MarkramSTPEntryTimestep — bundles a connection's MarkramSTPParameterTimestep rule with the same MarkramSTPVariables state used by the event-based variant. No last_spike bookkeeping is needed (continuous-time updates don't depend on ΔT).

    MarkramSTPEntryTimestep::new

    fn MarkramSTPEntryTimestep::new(conn_index : Int, n_pre : Int, n_post : Int, param? : MarkramSTPParameterTimestep) -> MarkramSTPEntryTimestep

    Construct a MarkramSTPEntryTimestep. vars is allocated with u[j] = param.u, x[j] = 1.0, rho_pre[j] = param.u (matches MarkramSTPVariables::new).

    MarkramSTPEntryTimestep::set_stp_active

    fn MarkramSTPEntryTimestep::set_stp_active(entry : MarkramSTPEntryTimestep, active : Bool) -> Unit

    Continuous-time (timestep) variant of set_stp_active — toggles the same vars.active[0] flag.

    MarkramSTPParameter

    pub(all) struct MarkramSTPParameter {
    tau_d : Float
    tau_f : Float
    u : Float
    w_max : Float
    w_min : Float
    }

    MarkramSTPParameter — homogeneous STP parameters (same τD, τF, U for every pre-neuron). Matches Julia's MarkramSTPParameterEvent.

    MarkramSTPParameter::new

    Construct MarkramSTPParameter with SNN.jl defaults (τD=200ms, τF=1500ms, U=0.2, Wmax=1.0, Wmin=0.0).

    MarkramSTPParameterHet

    pub(all) struct MarkramSTPParameterHet {
    tau_d : Array[Float]
    tau_f : Array[Float]
    u : Array[Float]
    w_max : Float
    w_min : Float
    }

    MarkramSTPParameterHet — per-pre-neuron heterogeneous STP parameters. τD, τF, U are all Array[Float] of length n_pre. Wmax/Wmin stay scalar (weight clamping in the Event-based update is unused).

    MarkramSTPParameterHet::homogeneous

    fn MarkramSTPParameterHet::homogeneous(n_pre : Int) -> MarkramSTPParameterHet

    Allocate MarkramSTPParameterHet with the same τD, τF, U for every pre-neuron (homogeneous default = MarkramSTPParameter defaults).

    MarkramSTPParameterTimestep

    pub(all) struct MarkramSTPParameterTimestep {
    u : Float
    tau_f : Float
    tau_d : Float
    w_max : Float
    w_min : Float
    }

    MarkramSTPParameterTimestep — timestep-driven Markram STP variant from SNNModels.jl/src/connections/sparse_plasticity/STP.jl.

    Differs from MarkramSTPParameter (event-based) in that the u, x traces are updated continuously each simulation step (Euler integration), regardless of whether a spike occurred. On a pre- spike there is an additional discrete bump before the continuous update.

    Julia reference (MarkramSTPParameterTimestep defaults): U = 0.5pF, τF = 750ms (facilitation), τD = 250ms (depression), Wmax / Wmin carry-overs (unused in this pure-time variant).

    Algorithm per step (mirrors Julia):
    1. Spike bumps (if fireJ[j]): u[j] += U * (1 - u[j]) x[j] += -u[j] * x[j]
    2. Continuous-time Euler update (every j, every step): u[j] += dt * (U - u[j]) / τF # facilitation toward U x[j] += dt * (1 - x[j]) / τD # depression toward 1
    3. Refresh rho_pre[j] = u[j] * x[j]
    4. Broadcast rho_pre to syn.rho[s] for s in rowptr[j]:rowptr[j+1].

    MarkramSTPParameterTimestep::new

    Defaults match Julia's MarkramSTPParameterTimestep (U=0.5, tau_F=750ms, tau_D=250ms).

    MarkramSTPVariables

    pub(all) struct MarkramSTPVariables {
    n_pre : Int
    n_post : Int
    u : Array[Float]
    x : Array[Float]
    rho_pre : Array[Float]
    last_spike : Array[Float]
    active : Array[Bool]
    }

    MarkramSTPVariables — per-pre-synaptic-neuron STP state.

    MarkramSTPVariables::new

    fn MarkramSTPVariables::new(n_pre : Int, n_post : Int, param : MarkramSTPParameter) -> MarkramSTPVariables

    Allocate MarkramSTPVariables for a connection of size n_pre × n_post. Initial state mirrors Julia's plasticityvariables: u[j] = param.u x[j] = 1.0F ρ[j] = param.u last_spike[j] = -Inf active = [true]

    MathPrims

    pub struct MathPrims {
    tape : Tape[Float]
    exp : (Loc[Float]) -> Loc[Float]
    ln : (Loc[Float]) -> Loc[Float]
    sin : (Loc[Float]) -> Loc[Float]
    cos : (Loc[Float]) -> Loc[Float]
    tanh : (Loc[Float]) -> Loc[Float]
    sigmoid : (Loc[Float]) -> Loc[Float]
    }

    Float-only transcendental primitive ops.

    MathPrims::on

    fn MathPrims::on(tape : Tape[Float]) -> MathPrims

    Build the MathPrims bundle around the given Float tape.

    MaxPool2dParam

    pub struct MaxPool2dParam {
    kh : Int
    kw : Int
    stride : Int
    pad : Int
    }

    MaxPool2d parameter container.

    MaxPool2dParam::new

    fn MaxPool2dParam::new(kh : Int, kw : Int, stride? : Int, pad? : Int) -> MaxPool2dParam

    Build a MaxPool2dParam. stride defaults to kh (non-overlapping).

    MiniSpikeFormer

    pub struct MiniSpikeFormer {
    d_model : Int
    n_classes : Int
    patch_dim : Int
    n_patches : Int
    patch_w : Array[Float]
    patch_b : Array[Float]
    class_token : Array[Float]
    pos_weight : Array[Float]
    pos_d_model : Int
    block : SpikingTransformerBlock
    cls_w : Array[Float]
    cls_b : Array[Float]
    }

    Mini-SpikeFormer parameter bundle.

    MiniSpikeFormer::new

    fn MiniSpikeFormer::new(d_model : Int, n_heads : Int, beta : Float, n_classes : Int, seed : UInt64) -> MiniSpikeFormer

    Construct a Mini-SpikeFormer.

    MiniSpikeFormerCache

    pub struct MiniSpikeFormerCache {
    pre_block : Array[Float]
    block_out : Array[Float]
    class_out : Array[Float]
    logits : Array[Float]
    probs : Array[Float]
    target : Int
    block_cache : SpikingTransformerBlockCache
    }

    Cache for one Mini-SpikeFormer forward pass.

    Model

    pub(all) struct Model {
    pops : Array[IF]
    conns : Array[SpikingSynapse]
    monitors : Array[Monitor]
    }

    A simple model container: one population + a list of connections.

    Monitor

    pub struct Monitor {
    pop : IF
    sym : String
    data : Array[Float]
    times : Array[Float]
    neuron : Int
    rec_step : Int
    step_count : Int
    }

    A monitor records a chosen variable from a population each step. recs is a list of (key, snapshot) pairs; the simplest monitor stores an Array[Float] per (key, neuron) pair.

    Monitor::ascii_plot

    fn Monitor::ascii_plot(m : Monitor, width? : Int, height? : Int) -> Unit

    Print a text-mode ASCII plot of the monitor buffer. Each data point becomes a single character placed in a column at the appropriate vertical position. Default width is 80 columns (down-sampled if buffer is larger); default height is 15 rows.

    width and height can be passed to override the canvas size. The plot is rendered with the value axis on the left (max at top, min at bottom) and the time axis on the x-axis (oldest at left, newest at right). Spike markers (*) are added for fire monitors where the value crosses 0.5.

    Monitor::clear_records

    fn Monitor::clear_records(m : Monitor) -> Unit

    Clear all recorded data and timestamps. Mirrors Julia's clear_records!(pop) API. Used to reset monitoring between sim!() calls without re-allocating monitors.

    Monitor::count_spikes

    fn Monitor::count_spikes(m : Monitor) -> Int

    Count rising-edge transitions in a fire monitor. Returns the number of times the monitored neuron's fire flag went from false to true. Approximates spike count.

    Monitor::count_spikes_interval

    fn Monitor::count_spikes_interval(m : Monitor, t_start : Float, t_end : Float) -> Int

    Count rising-edge transitions in the fire buffer that occur within the time window [t_start, t_end] (ms, inclusive on both ends). Returns 0 if no spikes in the window. Used to compute firing rates over sub-intervals of the simulation.

    Monitor::dump_csv_stdout

    fn Monitor::dump_csv_stdout(m : Monitor) -> Unit

    Dump the monitor buffer as CSV to stdout: time,value lines. Useful for piping to a Python plotting script.

    Monitor::dump_summary

    fn Monitor::dump_summary(m : Monitor) -> Unit

    Print a one-line summary of the monitor buffer (min/max/mean).

    Monitor::duration

    fn Monitor::duration(m : Monitor) -> Float

    Total duration covered by the monitor buffer.

    Monitor::firing_rate

    fn Monitor::firing_rate(m : Monitor) -> Float

    Compute the firing rate (Hz) of a fire monitor over the full recorded duration. Returns count_spikes(m) / duration_ms * 1000.0F. Returns 0.0F if the buffer is empty or the duration is zero.

    Monitor::firing_rate_interval

    fn Monitor::firing_rate_interval(m : Monitor, t_start : Float, t_end : Float) -> Float

    Compute the firing rate (Hz) within a sub-interval [t_start, t_end]. Returns count_spikes_interval(m, t_start, t_end) / duration * 1000.0F. Returns 0.0F if the interval is empty or duration is zero.

    Monitor::max

    fn Monitor::max(m : Monitor) -> Float

    Return the maximum value of the monitor's data buffer. Panics via assert_eq if the buffer is empty.

    Monitor::mean

    fn Monitor::mean(m : Monitor) -> Float

    Return the mean of the monitor's data buffer. Returns 0.0F if the buffer is empty.

    Monitor::min

    fn Monitor::min(m : Monitor) -> Float

    Return the minimum value of the monitor's data buffer. Panics via assert_eq if the buffer is empty.

    Monitor::new_fire

    fn Monitor::new_fire(pop : IF, neuron : Int) -> Monitor

    Initialise a monitor for the fire variable of a specific neuron.

    Monitor::new_v

    fn Monitor::new_v(pop : IF, neuron : Int) -> Monitor

    Initialise a monitor for the v variable of a specific neuron.

    Monitor::new_v_sr

    fn Monitor::new_v_sr(pop : IF, neuron : Int, sr_hz : Float) -> Monitor

    Initialise a monitor for the v variable with a sampling rate. sr_hz is the sample rate in Hz (internal units: Hz = 0.001, so 1 kHz = 1.0F in internal units). With dt=0.125F ms, sr_hz=1.0F (1 kHz) gives rec_step = 8.

    Monitor::spike_times

    fn Monitor::spike_times(m : Monitor) -> Array[Float]

    Return the spike times (in ms) as an array of timestamps at which the monitored neuron fired (rising-edge transitions in the fire buffer). Returns an empty array if no spikes. Mirrors Julia's spiketimes(pop, neuron) API at the per-neuron level.

    MonitorAdEx

    pub struct MonitorAdEx {
    pop : AdEx
    sym : String
    data : Array[Float]
    times : Array[Float]
    neuron : Int
    }

    A monitor for an AdEx population.

    MonitorAdEx::new_v

    fn MonitorAdEx::new_v(pop : AdEx, neuron : Int) -> MonitorAdEx

    MonitorAdExSinExp

    pub struct MonitorAdExSinExp {
    pop : AdExSinExp
    sym : String
    data : Array[Float]
    times : Array[Float]
    neuron : Int
    }

    A monitor for an AdExSinExp population. Records :v / :fire / :w.

    MonitorAdExSinExp::new_fire

    fn MonitorAdExSinExp::new_fire(pop : AdExSinExp, neuron : Int) -> MonitorAdExSinExp

    MonitorAdExSinExp::new_v

    fn MonitorAdExSinExp::new_v(pop : AdExSinExp, neuron : Int) -> MonitorAdExSinExp

    MorrisLecar

    pub struct MorrisLecar {
    param : MorrisLecarParameter
    n : Int
    v : Array[Float]
    w : Array[Float]
    fire : Array[Bool]
    i : Array[Float]
    ge : Array[Float]
    gi : Array[Float]
    }

    Morris-Lecar neuron state — a population of N ML neurons.

    MorrisLecar::new

    fn MorrisLecar::new(n : Int, param : MorrisLecarParameter, _rng : Xoshiro) -> MorrisLecar

    Construct a new MorrisLecar population with n neurons.

    MorrisLecarParameter

    pub struct MorrisLecarParameter {
    cm : Float
    el : Float
    ek : Float
    eca : Float
    gl : Float
    gk : Float
    gca : Float
    tau_e : Float
    tau_i : Float
    v1 : Float
    v2 : Float
    v3 : Float
    v4 : Float
    phi : Float
    e_e : Float
    e_i : Float
    }

    MorrisLecarParameter — biophysical constants.

    MorrisLecarParameter::new

    Default MorrisLecarParameter, matching Julia's MorrisLecarParameter().

    MultiHeadAttention

    pub struct MultiHeadAttention {
    d_model : Int
    num_heads : Int
    d_k : Int
    w_q : LinearParam
    w_k : LinearParam
    w_v : LinearParam
    w_o : LinearParam
    }

    Multi-head self-attention parameter container.

    MultiHeadAttention::new

    fn MultiHeadAttention::new(d_model : Int, num_heads : Int, seed : UInt64) -> MultiHeadAttention

    Construct a fresh MHA. seed initialises the four weight matrices deterministically (xoshiro256++ from_state with derived seeds).

    MultiplicativeNorm

    pub struct MultiplicativeNorm {
    tau : Float
    }

    MultiplicativeNorm — per-step multiplicative rule. At each step: μ[i] = (W0[i] - W1[i]) / W1[i] (the ratio of initial-sum to current-sum minus 1); W[s] *= (1 + μ[i]).

    MultiplicativeNorm::new

    fn MultiplicativeNorm::new(tau : Float) -> MultiplicativeNorm

    Multipod

    pub struct Multipod {
    n : Int
    nd : Int
    v_s : Array[Float]
    w_s : Array[Float]
    v_d : Array[Array[Float]]
    ge_s : Array[Float]
    gi_s : Array[Float]
    he_s : Array[Float]
    hi_s : Array[Float]
    he_d : Array[Array[Float]]
    hi_d : Array[Array[Float]]
    g_d : Array[Array[Array[Float]]]
    h_d : Array[Array[Array[Float]]]
    glu_receptors : Array[Int]
    gaba_receptors : Array[Int]
    alpha : Array[Float]
    fire : Array[Bool]
    after_spike : Array[Int]
    postspike : PostSpike
    theta : Array[Float]
    dv : Array[Float]
    dv_temp : Array[Float]
    cs : Array[Float]
    iv : Array[Float]
    soma_syn : SingleExpSynapse
    dend_syn : SingleExpSynapse
    nmda : NMDAVoltageDependency
    dendrites : Array[Dendrite]
    }

    Multipod — variable-dendrite-count multi-compartment AdEx neuron. v_d is a Array[Array[Float]] (one inner array per dendrite, each of length N). g_d is a flat Float array indexed as g_d[i, d, n] via g_d[i + d*N + n*N*nd].

    Multipod::new

    fn Multipod::new(dendrites : Array[Dendrite], n : Int, param? : AdExParameter) -> Multipod

    Allocate a Multipod. dendrites is the per-dendrite parameter list (length = nd). n is the number of neurons. param is the AdEx soma parameters (defaults to AdExParameter::new()).

    Multipod::step

    fn Multipod::step(p : Multipod, dt : Float) -> Unit

    Step the multipod forward one dt — Euler-style update (no Heun corrector). Updates soma_syn, dend_syn receptors, w_s, v_s, v_d, then spike detection.

    MultipodParameter

    pub struct MultipodParameter {
    dendrites : Array[Dendrite]
    }

    MultipodParameter — wraps an Array[Dendrite] for the multipod constructor. The Julia version uses Dendrite objects directly.

    MultipodParameter::new

    MultipodParameter::uniform

    fn MultipodParameter::uniform(d : Dendrite, nd : Int) -> MultipodParameter

    Convenience: MultipodParameter from a single Dendrite applied nd times. Mirrors Julia's Multipod(d; N, Nd) constructor.

    NLTAH

    pub(all) struct NLTAH {
    tau : Float
    lambda_ : Float
    mu : Float
    }
    NLTAH (non-linear triplet additive Hebbian) — simple three-factor STDP variant. Port of dump.jl::NLTAH.

    NMDAVoltageDependency

    pub struct NMDAVoltageDependency {
    b : Float
    k : Float
    mg : Float
    }

    NMDA voltage-dependence parameters (Eyal 2018): B(v) = 1 / (1 + (mg/b) * exp(k * v))

    NMDAVoltageDependency::custom

    fn NMDAVoltageDependency::custom(mg : Float, b : Float, k : Float) -> NMDAVoltageDependency

    Custom NMDAVoltageDependency with explicit mg / b / k values. Mirrors Julia's NMDAVoltageDependency(mg = 1.0, b = 3.36, k = -0.077).

    NMDAVoltageDependency::eyal

    Eyal 2018 defaults: mg = 1.0 mM, b = 3.36, k = -0.077.

    NMDAVoltageDependency::soma

    Soma NMDA defaults (SomaNMDA in Julia): mg = 1.0, b = 3.57, k = -0.062.

    NStepBuffer

    pub struct NStepBuffer {
    n : Int
    records : Array[StepRecord]
    count : Int
    head : Int
    }

    Circular buffer of recent step records.

    NStepBuffer::at

    fn NStepBuffer::at(self : NStepBuffer, i : Int) -> StepRecord

    Read the i-th record in chronological order (0 = oldest, count-1 = newest). Returns a copy of the record; the caller cannot mutate the buffer via it.

    NStepBuffer::capacity

    fn NStepBuffer::capacity(self : NStepBuffer) -> Int

    Window size (capacity).

    NStepBuffer::len

    fn NStepBuffer::len(self : NStepBuffer) -> Int

    Number of records currently in the buffer (≤ n).

    NStepBuffer::n_step_return

    fn NStepBuffer::n_step_return(self : NStepBuffer, root : Int, gamma : Float) -> (Float, Int, Int, Int, Bool)

    Compute the n-step return for the transition rooted at record index root (0 = oldest in the buffer).

    Given records R_root, R_root+1, ..., R_root+n-1, the n-step return is

    G_n = Σ_{k=0..n-1} γ^k · r_{root+k}

    If any intermediate step in the window is terminal (done = true), the sum is truncated at the terminal step and the bootstrap is suppressed (the Q-value of s_{terminal+1} is treated as zero).

    Also returns the effective horizon h (number of rewards included, 1..=n) and the terminal flag — the caller uses these to decide whether to use the standard n-step bootstrap (h == n, !done) or the truncated form (h < n).

    NStepBuffer::new

    fn NStepBuffer::new(n : Int) -> NStepBuffer

    Create an empty N-step buffer holding at most n records.

    NStepBuffer::push

    fn NStepBuffer::push(self : NStepBuffer, s : Int, a : Int, r : Float, s_next : Int, done : Bool) -> Bool

    Push a new step record, overwriting the oldest if the buffer is full. Returns true if the buffer now has n records (i.e., a complete n-step window is available for extraction).

    NStepBuffer::reset

    fn NStepBuffer::reset(self : NStepBuffer) -> Unit

    Empty the buffer (used at episode boundaries when we flush remaining transitions to the replay buffer with shorter effective horizons).

    NetworkModel

    pub struct NetworkModel {
    is_valid : Bool
    }

    NetworkModel — placeholder for the heterogeneous model aggregate. Our HeterogeneousModel (in compose.mbt) is the MoonBit equivalent; this stub exists to document the Julia-side abstraction and provide the same field accessor names. The actual port lives in compose.mbt.

    NetworkModel::new

    NetworkSummary

    pub struct NetworkSummary {
    n_populations : Int
    n_connections : Int
    n_stimuli : Int
    n_monitors : Int
    n_stdp_entries : Int
    n_stp_entries : Int
    current_time : Float
    total_neurons : Int
    }

    Summary of a HeterogeneousModel: counts and aggregate statistics. Cheap to compute (O(1) per dimension) and useful for sanity checks + print debugging.

    NoisyLinearParam

    pub struct NoisyLinearParam {
    weight_mu : Array[Float]
    weight_sigma : Array[Float]
    bias_mu : Array[Float]
    bias_sigma : Array[Float]
    in_features : Int
    out_features : Int
    }

    NoisyLinear parameter container. Stores the mean and std of weights/biases; noise is sampled at forward time.

    NoisyLinearParam::init

    fn NoisyLinearParam::init(in_features : Int, out_features : Int, rng : Xoshiro) -> NoisyLinearParam

    Init μ / σ weights with uniform in [-1/sqrt(in), +1/sqrt(in)] (PyTorch's NoisyLinear default bound). σ weights init to 0.017. bias μ init to 0, σ init to 0.017.

    NoisyLinearParam::new

    fn NoisyLinearParam::new(weight_mu : Array[Float], weight_sigma : Array[Float], bias_mu : Array[Float], bias_sigma : Array[Float], in_features : Int, out_features : Int) -> NoisyLinearParam

    Build a NoisyLinearParam. mu_* / sigma_* arrays are not copied. All sigma_* entries should be ≥ 0 (typical init: 0.017).

    NoisyQNet

    pub struct NoisyQNet {
    layer1 : NoisyLinearParam
    layer2 : NoisyLinearParam
    n_states : Int
    hidden : Int
    n_actions : Int
    }

    Two-hidden-layer Noisy Q-network (no conv layers — matches the 1D-state DQN shape used by dqn.mbt::LinearQNet).

    NoisyQNet::new

    fn NoisyQNet::new(n_states : Int, hidden : Int, n_actions : Int, seed : UInt64) -> NoisyQNet

    Build a fresh NoisyQNet with both layers initialized uniformly in [-1/√fan_in, +1/√fan_in] and σ weights = 0.017 (paper default).

    NormParam

    pub(all) enum NormParam {
    Multiplicative_(MultiplicativeNorm)
    Additive_(AdditiveNorm)
    }

    Sum type for the two normalisation rules. Used internally to dispatch metaplasticity_step.

    PINningSparseSynapse

    pub(all) struct PINningSparseSynapse {
    pre : WilsonCowan
    post : WilsonCowan
    matrix : SparseMatrixCSR
    p_vals : Array[Float]
    rI : Array[Float]
    rJ : Array[Float]
    g : Array[Float]
    q : Array[Float]
    f : Array[Float]
    }

    PINningSparseSynapse — port of refs/SNNModels.jl/src/connections/pinning_sparse_synapse.jl. Rate-mode synaptic plasticity rule for rate-model populations (WilsonCowan currently; any population with an r Array[Float] field works since arrays are passed by reference).

    Fields: pre / post : WilsonCowan (or any rate population with r) matrix : SparseMatrixCSR (CSR of the W weight matrix) rI, rJ : aliases for post.r / pre.r (set at construction; MoonBit arrays are reference types so these track rate updates) g : Array[Float] (post-synaptic conductance buffer; aliased to post.g for direct write) P : Array[Float] (same length as W; the inverse <r_i * r_j>^{-1} matrix) q : Array[Float] (length post.n; P * rJ projection) f : Array[Float] (length post.n; post-synaptic target rate)

    PINningSparseSynapse::new

    fn PINningSparseSynapse::new(pre : WilsonCowan, post : WilsonCowan, mu : Float, p : Float, alpha : Float, rng : Xoshiro) -> PINningSparseSynapse

    Build a PINningSparseSynapse with random Normal weights and identity inverse-covariance matrix P = α * I (initial guess for P is uncorrelated firing).

    Args: pre / post : WilsonCowan populations mu : Float — weight scale (Julia uses μ / sqrt(p * pre.N) * sprandn(...)) p : Float — connection probability alpha : Float — initial P diagonal scale rng : Xoshiro

    PINningSparseSynapse::set_target

    fn PINningSparseSynapse::set_target(c : PINningSparseSynapse, i : Int, target : Float) -> Unit

    Set the target rate f[i] for post-neuron i. After learning, the PINning rule drives g[i] toward f[i].

    ParityResult

    pub struct ParityResult {
    name : String
    n_compared : Int
    max_error : Float
    max_ulp : Int
    passed : Bool
    }

    Result of comparing two traces element-by-element.

    ParityTrace

    pub struct ParityTrace {
    kind : String
    values : Array[Float]
    }

    One reference trace loaded from a parity CSV file. kind is "float" or "int". values are in chronological order.

    Physiology

    pub(all) struct Physiology {
    ri : Float
    rd : Float
    cd : Float
    }

    Physiology — passive dendritic cable parameters (Ri/Rd/Cd).

    ri : intracellular resistivity (Ω×cm) rd : membrane resistance (Ω×cm²) cd : membrane capacitance (pF/cm²)

    Physiology::custom

    fn Physiology::custom(ri~ : Float, rd~ : Float, cd~ : Float) -> Physiology

    Custom Physiology constructor.

    Physiology::human

    fn Physiology::human() -> Physiology

    Default human cortical Physiology (Julia's human_dend). Ri = 200 Ω·cm, Rd = 38907 Ω·cm², Cd = 0.5 μF/cm² Converted to Float32 via the SNN unit normalisation factors: Ω·cm = 1e-3F (= 1e3 in mho/cm; we store the Float32 raw value) Ω·cm² = 1e-3F (= 1e3 mho·cm) pF/cm² = 1e-1F (= 0.1; 1pF=1 in our units, 1cm²=1) Julia evaluates 200 Ω·cm = 200*0.001 = 0.2F after unit norm.

    Physiology::mouse

    fn Physiology::mouse() -> Physiology

    Mouse cortical Physiology (Julia's mouse_dend). Ri = 200 Ω·cm, Rd = 1700 Ω·cm², Cd = 1 μF/cm².

    PlacedPops

    pub(all) struct PlacedPops {
    e : Array[Point2D]
    i : Array[Point2D]
    }

    Point2D

    pub(all) struct Point2D {
    x : Float
    y : Float
    }

    Poisson

    pub struct Poisson {
    param : PoissonHomoParameter
    n : Int
    fire : Array[Bool]
    randcache : Array[Float]
    rng : Xoshiro
    }

    Poisson neuron population — each "neuron" fires according to a homogeneous Poisson process.

    Poisson::new

    fn Poisson::new(n : Int, rate : Float, rng : Xoshiro) -> Poisson

    Construct a new Poisson population with n neurons and rate (Hz).

    PoissonFixed

    pub struct PoissonFixed {
    rate : Float
    mu : Float
    active : Array[Bool]
    }

    Fixed-rate Poisson stimulus parameter.

    PoissonFixed::new

    fn PoissonFixed::new(rate : Float) -> PoissonFixed

    PoissonFixed::set_mu

    fn PoissonFixed::set_mu(p : PoissonFixed, mu : Float) -> Unit

    Set the per-event amplitude μ. Used by Julia-style constructors that pass μ = 1.0f0 etc.

    PoissonHomoParameter

    pub struct PoissonHomoParameter {
    rate : Float
    }

    PoissonParameter — homogeneous rate.

    PoissonHomoParameter::new

    PoissonLayer

    pub(all) struct PoissonLayer {
    rate : Float
    n_sources : Int
    active : Array[Bool]
    mu : Float
    sigma : Float
    p : Float
    dist : String
    rule : String
    }

    PoissonLayer parameter — a population of n_sources independent Poisson sources with shared rate, drawn from optional Normal/Fixed connection weights. Mirrors Julia's PoissonLayer struct.

    PoissonLayer::new

    fn PoissonLayer::new(rate : Float) -> PoissonLayer

    Default PoissonLayer: rate in Hz, n_sources=1, fully active, μ=1.0, σ=0.0, p=1.0, Fixed weight, Fixed (no-plasticity) rule.

    PoissonLayer::with_active

    fn PoissonLayer::with_active(rate : Float, n_sources : Int, active : Array[Bool]) -> PoissonLayer

    PoissonLayer with explicit active flags (Julia: active = [false]).

    PoissonLayer::with_conn

    fn PoissonLayer::with_conn(rate : Float, n_sources : Int, active : Array[Bool], mu : Float, sigma : Float, p : Float, dist : String, rule : String) -> PoissonLayer

    PoissonLayer with full connection parameters (Julia: conn = (p, μ, σ, dist, rule)).

    PoissonLayer::with_n

    fn PoissonLayer::with_n(rate : Float, n_sources : Int) -> PoissonLayer

    PoissonLayer with n_sources sources, all initially active.

    PoissonLayerStimulus

    pub struct PoissonLayerStimulus {
    param : PoissonLayer
    post : IF
    sym : String
    fire : Array[Bool]
    weights : Array[Float]
    connectivity : Array[Bool]
    rng : Xoshiro
    }

    PoissonLayerStimulus — wraps a PoissonLayer with a target IF population and the per-(pre, post) connection weights drawn at construction time. Sym is "ge" or "gi" (which receptor to write).

    PoissonLayerStimulus::new

    fn PoissonLayerStimulus::new(param : PoissonLayer, post : IF, sym : String, rng : Xoshiro) -> PoissonLayerStimulus

    Construct a PoissonLayerStimulus. Draws each connection weight independently: with probability p the connection exists, and if it does, weight is drawn Normal(μ, σ) or Fixed(μ) per dist. weights and connectivity are stored row-major with shape [post.N, param.n_sources] (weights[j, i] = weight from source i to neuron j).

    PoissonLayerStimulusBallAndStick

    pub struct PoissonLayerStimulusBallAndStick {
    param : PoissonLayer
    post : BallAndStick
    weights : Array[Float]
    connectivity : Array[Bool]
    target_compartment : String
    target_kind : String
    rng : Xoshiro
    }

    PoissonLayerStimulusBallAndStick — PoissonLayer + sparse weights targeting a BallAndStick compartment buffer.

    PoissonLayerStimulusBallAndStick::new

    fn PoissonLayerStimulusBallAndStick::new(param : PoissonLayer, post : BallAndStick, target_compartment : String, target_kind : String, mu : Float, sigma : Float, p_conn : Float, rng : Xoshiro) -> PoissonLayerStimulusBallAndStick

    Construct a PoissonLayerStimulusBallAndStick. Draws per-(pre, post) sparse weights at construction.

    PoissonLayerStimulusTripod

    pub struct PoissonLayerStimulusTripod {
    param : PoissonLayer
    post : TripodHet
    weights : Array[Float]
    connectivity : Array[Bool]
    target_compartment : String
    target_kind : String
    rng : Xoshiro
    }

    PoissonLayerStimulusTripod — wraps a PoissonLayer + per-(pre, post) sparse weights to deliver Poisson spikes to a specific TripodHet compartment buffer.

    PoissonLayerStimulusTripod::new

    fn PoissonLayerStimulusTripod::new(param : PoissonLayer, post : TripodHet, target_compartment : String, target_kind : String, mu : Float, sigma : Float, p_conn : Float, rng : Xoshiro) -> PoissonLayerStimulusTripod

    Construct a PoissonLayerStimulusTripod. Draws per-(pre, post) weights at construction: with probability p_conn the connection exists, weight ~ Normal(μ, σ) (Fixed if dist=="Fixed").

    PoissonStimulusIF

    pub struct PoissonStimulusIF {
    param : PoissonFixed
    neurons : Array[Int]
    g : Array[Float]
    rng : Xoshiro
    }

    A Poisson stimulus targeting a list of neurons on a post-synaptic population. The g array is the receptor (e.g. glu or gaba).

    PoissonStimulusIF::new

    fn PoissonStimulusIF::new(pop : IF, sym : String, rate : Float, rng : Xoshiro) -> PoissonStimulusIF

    Construct a Poisson stimulus targeting all neurons of a population. g is the post-synaptic receptor (glu for :ge, gaba for :gi).

    PopIndex

    pub(all) struct PopIndex {
    name : String
    start : Int
    end_ : Int
    }

    A contiguous 1-based index range assigned to one population.

    PopIndex::length

    fn PopIndex::length(p : PopIndex) -> Int

    Number of neurons in this population (inclusive range length).

    PositionalEmbedding

    pub struct PositionalEmbedding {
    max_len : Int
    d_model : Int
    weight : Array[Float]
    }

    Learnable positional embedding parameter container.

    PositionalEmbedding::new

    fn PositionalEmbedding::new(max_len : Int, d_model : Int, seed : UInt64) -> PositionalEmbedding

    Construct a learnable positional embedding with Xavier-normal init (N(0, sqrt(2 / (1 + d_model)))).

    PostSpike

    pub struct PostSpike {
    tabs_const : Float
    }

    PostSpike parameters — absolute refractory period.

    PostSpike::new

    fn PostSpike::new() -> PostSpike

    PpoBatch

    pub(all) struct PpoBatch {
    states : Array[Int]
    actions : Array[Int]
    returns : Array[Float]
    old_probs : Array[Float]
    n_episodes : Int
    }

    A batch of transitions collected under a "frozen" policy snapshot.

    PpoGaeEpisode

    pub(all) struct PpoGaeEpisode {
    states : Array[Int]
    actions : Array[Int]
    rewards : Array[Float]
    values : Array[Float]
    next_values : Array[Float]
    dones : Array[Bool]
    }

    Episode record augmented with V(s_t), V(s_{t+1}), done flags for GAE computation. Mirrors EpisodeWithValues from actor_critic.mbt.

    PpoKlBatch

    pub(all) struct PpoKlBatch {
    states : Array[Int]
    actions : Array[Int]
    advantages : Array[Float]
    old_probs : Array[Float]
    old_dist : Array[Array[Float]]
    }

    Batch storing per-step (state, action, advantage) plus the full per-state old distribution (for the KL term).

    PrioritizedBuffer

    pub struct PrioritizedBuffer {
    capacity : Int
    states : Array[Int]
    actions : Array[Int]
    rewards : Array[Float]
    next_states : Array[Int]
    dones : Array[Bool]
    priorities : Array[Float]
    alpha : Float
    beta : Float
    epsilon : Float
    size : Int
    cursor : Int
    total_priority : Float
    }

    Prioritized replay buffer. Each transition has an associated priority (|TD error|^α + ε). Sampling is done with probability p_i ∝ priority_i. Importance-sampling weights correct for the bias.

    PrioritizedBuffer::len

    fn PrioritizedBuffer::len(self : PrioritizedBuffer) -> Int

    PrioritizedBuffer::new

    fn PrioritizedBuffer::new(capacity : Int, alpha : Float, beta : Float, epsilon : Float) -> PrioritizedBuffer

    PrioritizedBuffer::push

    fn PrioritizedBuffer::push(self : PrioritizedBuffer, s : Int, a : Int, r : Float, s_next : Int, done : Bool) -> Unit

    Push a new transition with its initial priority. The caller should later call update_priority(i, new_priority) to refresh after computing TD errors.

    PrioritizedBuffer::sample

    fn PrioritizedBuffer::sample(self : PrioritizedBuffer, batch_size : Int, rng : Xoshiro) -> (Array[Int], Array[Int], Array[Int], Array[Float], Array[Int], Array[Bool], Array[Float])

    Sample a mini-batch with probability ∝ priority^alpha. Returns indices, transitions, and importance-sampling weights.

    PrioritizedBuffer::update_priority

    fn PrioritizedBuffer::update_priority(self : PrioritizedBuffer, i : Int, abs_delta : Float) -> Unit

    Update priority for transition at index i.

    PrioritizedReplayBuffer

    pub struct PrioritizedReplayBuffer {
    capacity : Int
    states : Array[Int]
    actions : Array[Int]
    rewards : Array[Float]
    next_states : Array[Int]
    dones : Array[Bool]
    tree : SumTree
    max_priority : Float
    alpha : Float
    beta : Float
    epsilon : Float
    size : Int
    cursor : Int
    }

    Prioritized replay buffer with proportional priorities and importance-sampling weights. New transitions are inserted at priority = max_priority so that every transition is guaranteed to be sampled at least once.

    PrioritizedReplayBuffer::capacity

    Underlying slot capacity.

    PrioritizedReplayBuffer::len

    Current number of stored transitions.

    PrioritizedReplayBuffer::max_priority

    fn PrioritizedReplayBuffer::max_priority(self : PrioritizedReplayBuffer) -> Float

    Maximum priority observed so far. New transitions are inserted at this priority so they are guaranteed to be sampled at least once.

    PrioritizedReplayBuffer::new

    fn PrioritizedReplayBuffer::new(capacity : Int, alpha : Float, beta : Float, eps : Float) -> PrioritizedReplayBuffer

    Construct a new PER buffer.

    • capacity — number of transitions to hold
    • alpha — prioritization exponent (0 = uniform, 1 = fully prioritized)
    • beta — importance-sampling exponent (anneal 0 → 1 during training)
    • eps — small constant added to |δ| before exponentiating, ensures every transition is sampleable even with zero TD error

    PrioritizedReplayBuffer::push

    fn PrioritizedReplayBuffer::push(self : PrioritizedReplayBuffer, s : Int, a : Int, r : Float, s_next : Int, done : Bool) -> Unit

    Append a transition. New transitions get priority = max_priority (Schmidt's trick: ensures P(i) > 0 even when the initial |δ| is zero). Overwrites oldest slot when the buffer is full (FIFO ring).

    PrioritizedReplayBuffer::sample

    fn PrioritizedReplayBuffer::sample(self : PrioritizedReplayBuffer, batch_size : Int, rng : Xoshiro) -> (Array[Int], Array[Int], Array[Float], Array[Int], Array[Bool], Array[Int], Array[Float], Array[Float])

    Proportional-priority sample. Returns:

    (states, actions, rewards, next_states, dones, slot_indices, is_weights, p_i_per_index)

    where:

    • slot_indices is the leaf slot index for each sampled transition (for use with update_priorities).
    • is_weights is the importance-sampling weight normalised so the maximum weight in the batch equals 1.0.
    • p_i_per_index is the raw sampling probability P(i) used for the weight, exposed so tests can verify sampling bias.

    PrioritizedReplayBuffer::total_priority

    fn PrioritizedReplayBuffer::total_priority(self : PrioritizedReplayBuffer) -> Float

    Total priority mass (= root of the sum tree). Used to normalise per-step sampling segments and to detect "all priorities zero" edge cases.

    PrioritizedReplayBuffer::update_priorities

    fn PrioritizedReplayBuffer::update_priorities(self : PrioritizedReplayBuffer, slot_indices : Array[Int], td_errors : Array[Float]) -> Unit

    Update priorities for a list of (slot_index, |δ|+ε) pairs after a training step. slot_indices is the array returned by sample. td_errors may be signed (the absolute value is taken internally).

    Bumps max_priority upward if any new |δ|+ε exceeds the current max.

    Quaresima2023Dend

    pub struct Quaresima2023Dend {
    e_to_ed_p : Float
    e_to_ed_mu : Float
    e_to_pv_p : Float
    e_to_pv_mu : Float
    e_to_sst_p : Float
    e_to_sst_mu : Float
    pv_to_e_p : Float
    pv_to_e_mu : Float
    pv_to_sst_p : Float
    pv_to_sst_mu : Float
    pv_to_pv_p : Float
    pv_to_pv_mu : Float
    sst_to_ed_p : Float
    sst_to_ed_mu : Float
    sst_to_pv_p : Float
    sst_to_pv_mu : Float
    sst_to_sst_p : Float
    sst_to_sst_mu : Float
    }

    Quaresima2023Dend::new

    Defaults match Julia's quaresima2023_dend: E→Ed: p=0.168, μ=2.8; E→PV: p=0.2, μ=log(1.0)=0; E→SST: p=0.2, μ=0 PV→E: p=0.2, μ=log(15.8); PV→SST: p=0.2, μ=log(1.4); PV→PV: p=0.2, μ=log(16.2) SST→Ed: p=0.2, μ=log(15.8); SST→PV: p=0.2, μ=log(0.83); SST→SST: p=0.2, μ=log(0.83) log(1.0)=0, log(15.8)≈2.76, log(1.4)≈0.336, log(16.2)≈2.785, log(0.83)≈-0.186.

    Quaresima2024UpDown

    pub struct Quaresima2024UpDown {
    nar : Float
    tau_d : Float
    dend_receptors : Receptors
    }

    Quaresima2024UpDown — top-level configuration bundle for the Quaresima 2024 up/down state cortical model. Mirrors quaresima2022_nar (the main reusable component) from refs/SNNUtils.jl/src/models/quaresima_2024_updown.jl. The Julia version is a NamedTuple; we provide a struct with the most useful fields: the dendritic NAR scaling factor nar and τd, plus the soma/dendrite receptor collections (built via eyal_equivalent_nar at runtime).

    Quaresima2024UpDown::new

    fn Quaresima2024UpDown::new(nar : Float, tau_d : Float) -> Quaresima2024UpDown

    Build a Quaresima2024UpDown config from nar (NMDA/AMPA ratio, Julia default = 1.8) and optional tau_d (NMDA decay time in ms, default 35). Soma uses tripod_soma_receptors() (DuarteGluSoma + MilesGabaSoma placeholder); dendrite uses eyal_equivalent_nar(nar, τd).

    RMSpropState

    pub struct RMSpropState {
    v_w : Array[Float]
    v_b : Array[Float]
    }

    RMSprop state. v_w / v_b are exponential moving averages of the squared gradient for weight / bias. No step counter — RMSprop has no bias correction term.

    RandomTurnover

    pub struct RandomTurnover {
    rate : Float
    tau : Float
    threshold : Float
    mu : Float
    }

    RandomTurnover — random rewiring rule. Mirrors Julia's @snn_kw struct RandomTurnover.

    RandomTurnover::new

    fn RandomTurnover::new(rate? : Float, threshold? : Float, mu? : Float) -> RandomTurnover

    RateSynapse

    pub struct RateSynapse {
    pre : WilsonCowan
    post : WilsonCowan
    matrix : SparseMatrixCSR
    }

    RateSynapse — a sparse (CSR) connectivity matrix between two rate-model populations. Forward pass: for each pre neuron j that has outgoing weights: for each (k, w) in row j: g[k] += w * rJ[j]

    RateSynapse::new

    fn RateSynapse::new(pre : WilsonCowan, post : WilsonCowan, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> RateSynapse

    Build a RateSynapse with random Normal weights at connection probability p. Matches Julia's RateSynapse(pre, post; μ, p) which uses w = μ / sqrt(p * pre.N) * sprandn(...) — i.e. weights are Normal(0, μ/sqrt(p*N)) with a Bernoulli mask at probability p.

    Our SparseMatrixCSR::random uses N(μ_center, σ) weights. To match Julia, we pass mu_center = 0 and σ = μ / sqrt(p*N) so the effective weight distribution is N(0, μ²/(p*N)) * Bernoulli(p).

    Receptor

    pub struct Receptor {
    e_rev : Float
    tau_r : Float
    tau_d : Float
    g0 : Float
    gsyn : Float
    alpha : Float
    tau_r_inv : Float
    tau_d_inv : Float
    is_nmda : Bool
    target : String
    }

    A synaptic receptor (AMPA, NMDA, GABAa, GABAb, ...). target ∈ {"glu", "gaba"} determines which input buffer (target[i] = glu[i] or gaba[i]) drives this receptor. is_nmda = true triggers voltage-dependent gating in current computation.

    Receptor::nmda

    fn Receptor::nmda(e_rev : Float, tau_r : Float, tau_d : Float, g0 : Float) -> Receptor

    Construct an NMDA receptor (voltage-dependent gating).

    Receptor::simple

    fn Receptor::simple(e_rev : Float, tau_r : Float, tau_d : Float, g0 : Float, target : String) -> Receptor

    Construct a non-NMDA receptor (AMPA, GABAa, GABAb).

    ReceptorSynapse

    pub(all) struct ReceptorSynapse {
    pre : IF
    post : IF
    matrix : SparseMatrixCSR
    syn : Receptors
    target_receptor : Array[Int]
    glu_receptors : Array[Int]
    gaba_receptors : Array[Int]
    nmda_dep : NMDAVoltageDependency
    delays : Array[Float]
    glu : Array[Float]
    gaba : Array[Float]
    }

    Per-edge receptor routing for IF→IF synapses with 4-receptor dynamics. Each edge selects one receptor in [0, 3]; based on which receptor it targets, the weight is added to the post's glu or gaba buffer at delivery time.

    ReceptorSynapse::new

    fn ReceptorSynapse::new(pre : IF, post : IF, syn : Receptors, nmda_dep : NMDAVoltageDependency, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> ReceptorSynapse

    Construct a ReceptorSynapse with random connectivity. Defaults: glu_receptors = [0, 1] (AMPA, NMDA), gaba_receptors = [2, 3] (GABAa, GABAb). All edges initially target receptor 0 (AMPA). Use set_target_receptor to set per-edge targeting later.

    ReceptorSynapse::set_target_receptor

    fn ReceptorSynapse::set_target_receptor(s : ReceptorSynapse, idx : Int, r : Int) -> Unit

    Set the per-edge receptor index for edge index s (parallel to matrix.colptr). r ∈ [0, 3]. Edge targets must be in glu_receptors or gaba_receptors for forward routing to work.

    ReceptorSynapseTripod

    pub struct ReceptorSynapseTripod {
    pre : IF
    post : TripodHet
    matrix : SparseMatrixCSR
    target_compartment : String
    receptors : Receptors
    nmda_dep : NMDAVoltageDependency
    g_state : Array[Float]
    h_state : Array[Float]
    ge_out : Array[Float]
    gi_out : Array[Float]
    }

    ReceptorSynapseTripod — multi-receptor SpikingSynapse wired into TripodHet. One struct per (pre, post) connection; targets a single compartment per synapse (set at construction).

    Per-(pre, post) weight w is distributed to the right receptor index based on target_receptors[s]:
    • 0 = AMPA (glu, E_rev=0)
    • 1 = NMDA (glu, E_rev=0, voltage-dependent)
    • 2 = GABAa (gaba, E_rev<0)
    • 3 = GABAb (gaba, E_rev<0, slow)

    ReceptorSynapseTripod::new

    fn ReceptorSynapseTripod::new(pre : IF, post : TripodHet, target_compartment : String, receptors : Receptors, nmda_dep : NMDAVoltageDependency, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> ReceptorSynapseTripod

    Receptors

    pub struct Receptors {
    rec : Array[Receptor]
    }

    Receptors — a 4-element collection (AMPA, NMDA, GABAa, GABAb) for one synapse. Index 0 = AMPA, 1 = NMDA, 2 = GABAa, 3 = GABAb (matches Julia's Receptors default ordering).

    Receptors::from_array

    fn Receptors::from_array(rec : Array[Receptor]) -> Receptors

    Build a Receptors from an explicit Array[Receptor] of length 4. Mirrors Julia's Receptors(rec::Vector{Receptor}) constructor. Indices: 0=AMPA, 1=NMDA, 2=GABAa, 3=GABAb. Panics if length != 4.

    Receptors::from_pair

    fn Receptors::from_pair(glu : Glutamatergic, gaba : GABAergic) -> Receptors

    Build a Receptors from a Glutamatergic + GABAergic pair. Mirrors Julia's Receptors(glu::Glutamatergic, gaba::GABAergic) constructor.

    Receptors::new

    fn Receptors::new(ampa : Receptor, nmda_rec : Receptor, gabaa : Receptor, gabab : Receptor) -> Receptors

    Build a default Receptors (empty AMPA, NMDA, GABAa, GABAb).

    ReceptorsByTarget

    pub struct ReceptorsByTarget {
    glu : Array[Int]
    gaba : Array[Int]
    }

    ReceptorsByTarget — result of infer_receptors.

    RecurrentPpoBatch

    pub(all) struct RecurrentPpoBatch {
    states : Array[Int]
    actions : Array[Int]
    advantages : Array[Float]
    old_probs : Array[Float]
    cur_probs : Array[Float]
    cur_dist : Array[Array[Float]]
    old_dist : Array[Array[Float]]
    }

    Per-step transition record for recurrent PPO. Includes both the cached π_old (at batch collection) and the recomputed π_cur (at update time, after policy weights have shifted).

    RecurrentSac

    pub struct RecurrentSac {
    policy : LstmPolicy
    q1 : LstmQNet
    q2 : LstmQNet
    q1_target : LstmQNet
    q2_target : LstmQNet
    log_alpha : Float
    target_entropy : Float
    alpha_lr : Float
    }

    Recurrent SAC agent.

    RecurrentSac::new

    fn RecurrentSac::new(n_cells : Int, d_h : Int, log_alpha_init : Float, target_entropy : Float, alpha_lr : Float, seed : UInt64) -> RecurrentSac

    RecurrentSacEpisode

    pub(all) struct RecurrentSacEpisode {
    obs_seq : Array[Array[Float]]
    next_obs_seq : Array[Array[Float]]
    actions : Array[Int]
    rewards : Array[Float]
    dones : Array[Bool]
    initial_pos : Int
    goal_side : Int
    }

    Episode trace for recurrent SAC. Stores obs_seq, next_obs_seq, actions, rewards, dones for both actor and critic updates.

    RecurrentSacEpisode::new

    fn RecurrentSacEpisode::new(obs_seq : Array[Array[Float]], next_obs_seq : Array[Array[Float]], actions : Array[Int], rewards : Array[Float], dones : Array[Bool], initial_pos : Int, goal_side : Int) -> RecurrentSacEpisode

    Convenience constructor for synthetic episodes (tests).

    ReduceLROnPlateau

    pub struct ReduceLROnPlateau {
    base_lr : Float
    factor : Float
    patience : Int
    threshold : Float
    best_metric : Float
    num_bad_epochs : Int
    current_lr : Float
    }

    ReduceLROnPlateau state. Tracks the best metric seen so far and the number of consecutive non-improving steps. lr is multiplied by factor when patience runs out.

    ReduceLROnPlateau::new

    fn ReduceLROnPlateau::new(base_lr : Float, factor : Float, patience : Int, threshold : Float) -> ReduceLROnPlateau

    Build a ReduceLROnPlateau scheduler. base_lr is the initial lr; factor is the decay multiplier applied on plateau (typically 0.1); patience is the number of consecutive non-improving steps before decay; threshold is the minimum relative improvement (in the metric's own units) to count as "improved".

    ReduceLROnPlateau::step

    fn ReduceLROnPlateau::step(self : ReduceLROnPlateau, metric : Float) -> (Float, ReduceLROnPlateau)

    Feed the current step's metric to the scheduler. Returns the new lr and the updated state. The metric convention is "lower is better" (loss-like); the metric counts as "improved" if it is at least threshold below the previous best.

    ReplayBuffer

    pub struct ReplayBuffer {
    capacity : Int
    states : Array[Int]
    actions : Array[Int]
    rewards : Array[Float]
    next_states : Array[Int]
    dones : Array[Bool]
    size : Int
    cursor : Int
    }

    Experience replay buffer (uniform random sampling).

    ReplayBuffer::len

    fn ReplayBuffer::len(self : ReplayBuffer) -> Int

    ReplayBuffer::new

    fn ReplayBuffer::new(capacity : Int) -> ReplayBuffer

    ReplayBuffer::push

    fn ReplayBuffer::push(self : ReplayBuffer, s : Int, a : Int, r : Float, s_next : Int, done : Bool) -> Unit

    Append a transition. Overwrites oldest when full.

    ReplayBuffer::sample

    fn ReplayBuffer::sample(self : ReplayBuffer, batch_size : Int, rng : Xoshiro) -> (Array[Int], Array[Int], Array[Float], Array[Int], Array[Bool])

    Sample a mini-batch by random indices. Returns 5 parallel arrays.

    ResidualBlock

    pub struct ResidualBlock {
    conv1 : Conv2dParam
    bn1 : BatchNorm2d
    conv2 : Conv2dParam
    bn2 : BatchNorm2d
    shortcut_conv : Conv2dParam?
    shortcut_bn : BatchNorm2d?
    stride : Int
    }

    A basic residual block.

    ResidualBlock::identity

    fn ResidualBlock::identity(c : Int, seed : UInt64) -> ResidualBlock

    Build an identity-shortcut basic block (input shape must match block output: c_in == c_out, stride == 1).

    ResidualBlock::projection

    fn ResidualBlock::projection(c_in : Int, c_out : Int, stride : Int, seed : UInt64) -> ResidualBlock

    Build a downsample basic block with a 1x1 conv projection shortcut.

    ResidualCache

    pub struct ResidualCache {
    x : Array[Float]
    sum : Array[Float]
    bn1_out : Array[Float]
    bn1_cache : BatchNormCache
    bn2_cache : BatchNormCache
    shortcut_bn_cache : BatchNormCache?
    shortcut_output : Array[Float]
    conv2_input : Array[Float]
    conv2_input_n : Int
    conv2_input_c : Int
    conv2_input_h : Int
    conv2_input_w : Int
    n : Int
    c : Int
    h : Int
    w : Int
    }

    Cache returned by residual_block_forward and consumed by residual_block_backward.

    ResidualGrads

    pub struct ResidualGrads {
    conv1_d_weight : Array[Float]
    conv1_d_bias : Array[Float]
    bn1_d_gamma : Array[Float]
    bn1_d_beta : Array[Float]
    conv2_d_weight : Array[Float]
    conv2_d_bias : Array[Float]
    bn2_d_gamma : Array[Float]
    bn2_d_beta : Array[Float]
    shortcut_conv_d_weight : Array[Float]?
    shortcut_conv_d_bias : Array[Float]?
    shortcut_bn_d_gamma : Array[Float]?
    shortcut_bn_d_beta : Array[Float]?
    }

    Parameter gradients returned by residual_block_backward.

    SGDMomentumState

    pub struct SGDMomentumState {
    v_w : Array[Float]
    v_b : Array[Float]
    }

    SGD-with-momentum state. v_w and v_b are velocity buffers matching the shape of weight and bias respectively.

    SRNN

    pub struct SRNN {
    n_in : Int
    n_hidden : Int
    n_classes : Int
    dt : Float
    alpha : Float
    v_thresh : Float
    w_in : Array[Float]
    w_rec : Array[Float]
    w_out : Array[Float]
    b_in : Array[Float]
    b_out : Array[Float]
    }

    SRNN parameter container.

    SRNN::new

    fn SRNN::new(n_in : Int, n_hidden : Int, n_classes : Int, seed : UInt64) -> SRNN

    Construct a SRNN with random init.

    SRNNCache

    pub struct SRNNCache {
    n_steps : Int
    n_in : Int
    n_hidden : Int
    n_classes : Int
    x : Array[Float]
    v : Array[Float]
    spikes : Array[Float]
    y : Array[Float]
    probs : Array[Float]
    target : Int
    alpha : Float
    v_thresh : Float
    beta : Float
    }

    Forward / backward cache.

    STDPAntiSymmetric

    pub(all) struct STDPAntiSymmetric {
    a_x : Float
    a_y : Float
    tau_x : Float
    tau_y : Float
    alpha_pre : Float
    alpha_post : Float
    w_max : Float
    w_min : Float
    }

    STDPAntiSymmetric parameter struct (Festa et al. 2024, inhibitory STDP).

    STDPAntiSymmetric::new

    Defaults match Julia's STDPAntiSymmetric (A_x=A_y=3e-2, τ=50ms, etc.).

    STDPAntiSymmetricVariables

    pub struct STDPAntiSymmetricVariables {
    tr_x : Array[Float]
    to_y : Array[Float]
    }

    STDPAntiSymmetric variables — tr_x[j] (pre) and to_y[i] (post) traces.

    STDPAntiSymmetricVariables::new

    fn STDPAntiSymmetricVariables::new(n_pre : Int, n_post : Int) -> STDPAntiSymmetricVariables

    Initialise STDPAntiSymmetric variables for a given pre/post size.

    STDPConfavreux2025

    pub(all) struct STDPConfavreux2025 {
    eta : Float
    alpha : Float
    beta : Float
    kappa : Float
    gamma : Float
    tau_pre : Float
    tau_post : Float
    w_max : Float
    w_min : Float
    }

    STDPConfavreux2025 — Confavreux 2025 STDP variant with separable pre/post contributions and baseline rate dependencies (alpha, beta).

    Reference: SpikingNeuralNetworks.jl/src/connections/sparse_plasticity/ STDP_traces.jl. Update rule per synapse (j -> i): On pre spike (fireJ[j]): W[s] += eta * (kappa * Deltapost[i] + alpha) On post spike (fireI[i]): W[s] += eta * (gamma * Deltapre[j] + beta) Clamp W to [w_min, w_max].

    Deltapre[j] and Deltapost[i] are the pre/post spike traces at the current time (after continuous exponential decay + spike bump). The alpha / beta baseline terms add a constant offset on every spike event, regardless of the partner trace — these encode the "baseline rate dependency" that drives competition.

    Field naming: Julia's eta -> eta, alpha -> alpha, beta -> beta, kappa -> kappa, gamma -> gamma. MoonBit doesn't accept Greek letters in identifiers.

    STDPConfavreux2025::new

    Defaults match Julia's STDPConfavreux2025 (eta=0.01, alpha=0, beta=0, kappa=1, gamma=1, tau_pre=20ms, tau_post=20ms, w_min=0, w_max=30).

    STDPEntry

    pub struct STDPEntry {
    conn_index : Int
    n_pre : Int
    n_post : Int
    vars : STDPVariables
    param : STDPGerstner
    t_now : Array[Float]
    }

    STDPEntry — bundles a connection's plasticity rule with mutable per-step state so the compose layer can apply it automatically. The t_now field is a 1-element Float array; mutations happen in compose.mbt's step_heterogeneous (cross-module mutation works because Array indexing is always allowed).

    STDPEntry::advance

    fn STDPEntry::advance(e : STDPEntry, dt : Float) -> Unit

    Advance the entry's internal clock by dt ms. (Note: since MoonBit passes structs by value, this only updates a local copy. The compose layer mutates entry.t_now[0] directly instead.)

    STDPEntry::change_plasticity

    fn STDPEntry::change_plasticity(e : STDPEntry, new_param : STDPGerstner) -> Unit

    Runtime swap of the STDPGerstner parameters for an entry. Mirrors Julia's change_plasticity!(syn; LTP = STDPConfavreux2025()) pattern — caller passes a new STDPGerstner and the entry picks it up. State (tpre/tpost, vars) is preserved.

    STDPEntry::new

    fn STDPEntry::new(conn_index : Int, n_pre : Int, n_post : Int) -> STDPEntry

    Construct an STDPEntry for the synapse at conn_index in HeterogeneousModel.conns. The vars are zero-initialised; t_now starts at 0.0F.

    STDPEntry::set_ltp_active

    fn STDPEntry::set_ltp_active(entry : STDPEntry, active : Bool) -> Unit

    Toggle LTP (STDP) on/off for this entry — mirrors Julia's set_LTP!(s::SpikingSynapse, active). When active=false, stdp_step becomes a no-op for this entry (traces don't decay, weights don't update). When active=true, normal STDP resumes from whatever trace state is currently in vars.

    Note: MoonBit identifiers can't contain !, so the trailing bang is dropped. The semantic is identical — mutate the entry's active flag in place.

    STDPEntryAntiSymmetric

    pub struct STDPEntryAntiSymmetric {
    conn_index : Int
    n_pre : Int
    n_post : Int
    param : STDPAntiSymmetric
    vars : STDPAntiSymmetricVariables
    t_now : Array[Float]
    }

    STDPEntryAntiSymmetric — bundles a connection's STDPAntiSymmetric rule with the tr_x/to_y trace state.

    STDPEntryAntiSymmetric::change_plasticity

    fn STDPEntryAntiSymmetric::change_plasticity(e : STDPEntryAntiSymmetric, new_param : STDPAntiSymmetric) -> Unit

    Runtime swap of STDPAntiSymmetric parameters for an entry.

    STDPEntryAntiSymmetric::new

    fn STDPEntryAntiSymmetric::new(conn_index : Int, n_pre : Int, n_post : Int, param? : STDPAntiSymmetric) -> STDPEntryAntiSymmetric

    Construct an STDPEntryAntiSymmetric. param defaults to STDPAntiSymmetric::new().

    STDPEntryConfavreux2025

    pub struct STDPEntryConfavreux2025 {
    conn_index : Int
    n_pre : Int
    n_post : Int
    param : STDPConfavreux2025
    vars : STDPVariables
    t_now : Array[Float]
    }

    STDPEntryConfavreux2025 — bundles a connection's STDPConfavreux2025 rule with the per-step state (traces + last-spike times) plus an internal t_now clock so the compose layer can advance simulation time across step calls.

    STDPEntryConfavreux2025::change_plasticity

    fn STDPEntryConfavreux2025::change_plasticity(e : STDPEntryConfavreux2025, new_param : STDPConfavreux2025) -> Unit

    Runtime swap of STDPConfavreux2025 parameters. Preserves trace state.

    STDPEntryConfavreux2025::new

    fn STDPEntryConfavreux2025::new(conn_index : Int, n_pre : Int, n_post : Int, param? : STDPConfavreux2025) -> STDPEntryConfavreux2025

    Construct an STDPEntryConfavreux2025. vars is zero-initialised; t_now starts at 0.0F. Caller wires the entry into HeterogeneousModel.stdp_entries via Confavreux2025_(entry).

    STDPEntryKind

    pub(all) enum STDPEntryKind {
    Gerstner_(STDPEntry)
    MexicanHat_(STDPEntryMexicanHat)
    AntiSymmetric_(STDPEntryAntiSymmetric)
    Confavreux2025_(STDPEntryConfavreux2025)
    IstdpRate_(IstdpRateEntry)
    IstdpPotential_(IstdpPotentialEntry)
    Symmetric_(STDPEntrySymmetric)
    CaPlasticity_(CaPlasticityEntry)
    }

    STDPEntryKind — enum-dispatched wrapper around the three STDP entry types. Use these variants in compose(stdp=[...]) to register a plasticity rule on a specific synapse. Each variant carries the matching *Entry struct.

    STDPEntryMexicanHat

    pub struct STDPEntryMexicanHat {
    conn_index : Int
    n_pre : Int
    n_post : Int
    param : STDPMexicanHat
    tpre : Array[Float]
    tpost : Array[Float]
    t_now : Array[Float]
    }

    STDPEntryMexicanHat — bundles a connection's STDPMexicanHat rule with the raw tpre/tpost trace arrays (MexicanHat has no wrapper struct for its traces; they're just plain Array[Float]).

    STDPEntryMexicanHat::change_plasticity

    fn STDPEntryMexicanHat::change_plasticity(e : STDPEntryMexicanHat, new_param : STDPMexicanHat) -> Unit

    Runtime swap of STDPMexicanHat parameters for an entry.

    STDPEntryMexicanHat::new

    fn STDPEntryMexicanHat::new(conn_index : Int, n_pre : Int, n_post : Int, param? : STDPMexicanHat) -> STDPEntryMexicanHat

    Construct an STDPEntryMexicanHat. param defaults to STDPMexicanHat::new().

    STDPEntrySymmetric

    pub struct STDPEntrySymmetric {
    conn_index : Int
    n_pre : Int
    n_post : Int
    param : STDPSymmetric
    vars : STDPSymmetricVariables
    t_now : Array[Float]
    }

    STDPEntrySymmetric — bundles a connection's STDPSymmetric rule with the per-step state plus an internal t_now clock.

    STDPEntrySymmetric::change_plasticity

    fn STDPEntrySymmetric::change_plasticity(e : STDPEntrySymmetric, new_param : STDPSymmetric) -> Unit

    Runtime swap of STDPSymmetric parameters. Preserves trace state.

    STDPEntrySymmetric::new

    fn STDPEntrySymmetric::new(conn_index : Int, n_pre : Int, n_post : Int, param? : STDPSymmetric) -> STDPEntrySymmetric

    STDPGerstner

    pub(all) struct STDPGerstner {
    a_pre : Float
    a_post : Float
    tau_pre : Float
    tau_post : Float
    w_max : Float
    w_min : Float
    }

    STDPGerstner parameter struct.

    STDPGerstner::new

    STDPMexicanHat

    pub(all) struct STDPMexicanHat {
    a : Float
    tau : Float
    w_max : Float
    w_min : Float
    }

    STDPMexicanHat parameter struct (Festa, Cusseddu, Gjorgjieva 2024).

    STDPMexicanHat::new

    Defaults match Julia's STDPMexicanHat (A=10e-2, τ=20ms, Wmax=30pF).

    STDPSymmetric

    pub(all) struct STDPSymmetric {
    a_x : Float
    a_y : Float
    tau_x : Float
    tau_y : Float
    alpha_pre : Float
    alpha_post : Float
    w_max : Float
    w_min : Float
    }

    STDPSymmetric parameter struct (Festa et al. 2024, inhibitory STDP with zero-integral kernel).

    Fields:
    • a_x : LTP learning rate (pre→post facilitation)
    • a_y : LTD learning rate (post→pre depression)
    • tau_x : time constant for tr_x / to_x traces (pre and post spike-traces)
    • tau_y : time constant for tr_y / to_y traces (pre and post spike-traces)
    • alpha_pre : constant offset on pre-spike
    • alpha_post : constant offset on post-spike
    • w_max / w_min : weight bounds

    STDPSymmetric::new

    Defaults match Julia's STDPSymmetric (A_x=A_y=3e-2, tau_x=50ms, tau_y=500ms, alpha_pre=alpha_post=0, w_max=30, w_min=0).

    STDPSymmetricVariables

    pub struct STDPSymmetricVariables {
    tr_x : Array[Float]
    tr_y : Array[Float]
    to_x : Array[Float]
    to_y : Array[Float]
    }

    STDPSymmetricVariables — four trace arrays: tr_x[j] : pre-synaptic trace that bumps on pre-spike, decays toward 0 with time constant tau_x. Read on post-spike. tr_y[j] : pre-synaptic trace that bumps on pre-spike, decays toward 0 with time constant tau_y. Read on post-spike. to_x[i] : post-synaptic trace that bumps on post-spike, decays toward 0 with time constant tau_x. Read on pre-spike. to_y[i] : post-synaptic trace that bumps on post-spike, decays toward 0 with time constant tau_y. Read on pre-spike.

    STDPSymmetricVariables::new

    fn STDPSymmetricVariables::new(n_pre : Int, n_post : Int) -> STDPSymmetricVariables

    STDPVariables

    pub struct STDPVariables {
    tpre : Array[Float]
    tpost : Array[Float]
    last_pre : Array[Float]
    last_post : Array[Float]
    active : Array[Bool]
    }

    STDPVariables — tracks pre/post spike traces per synapse.

    STDPVariables::new

    fn STDPVariables::new(n_pre : Int, n_post : Int) -> STDPVariables

    Initialise STDP variables for a given pre/post population size.

    STImage

    pub struct STImage {
    data : Array[Float]
    t : Int
    b : Int
    c : Int
    h : Int
    w : Int
    }

    5D tensor [T, B, C, H, W] in flat row-major Float32 layout.

    STImage::batch_slice

    fn STImage::batch_slice(self : STImage) -> Int

    Stride helper: number of elements per [C, H, W] "batch slice".

    STImage::frame_block

    fn STImage::frame_block(self : STImage) -> Int

    Stride helper: number of elements per [B, C, H, W] "frame block".

    STImage::from_frames

    fn STImage::from_frames(frames : Array[Image]) -> STImage raise Failure

    Stack T 4D [N=1, C, H, W] images along a new time axis. All input images must share the same (c, h, w) shape. Result is [T, 1, C, H, W].

    STImage::get

    fn STImage::get(self : STImage, t : Int, b : Int, c : Int, h : Int, w : Int) -> Float

    Read [t, b, c, h, w].

    STImage::get_batch

    fn STImage::get_batch(self : STImage, b : Int) -> Image

    Extract a single batch b as a 4D Image { n: T, c, h, w }. Useful when the time axis should be flattened into the batch axis (e.g. vSTDP conv across time).

    STImage::get_frame

    fn STImage::get_frame(self : STImage, t : Int) -> Image

    Extract a single time step t as a 4D Image { n: 1, c, h, w }. Returns a fresh Image (data is copied, not aliased).

    STImage::numel

    fn STImage::numel(self : STImage) -> Int

    Total element count (T * B * C * H * W).

    STImage::offset

    fn STImage::offset(self : STImage, t : Int, b : Int, c : Int, h : Int, w : Int) -> Int

    Flat offset of [t, b, c, h, w] in data.

    STImage::set

    fn STImage::set(self : STImage, t : Int, b : Int, c : Int, h : Int, w : Int, v : Float) -> Unit

    Write [t, b, c, h, w].

    STImage::shape

    fn STImage::shape(self : STImage) -> (Int, Int, Int, Int, Int)

    Shape as (t, b, c, h, w).

    STImage::zeros

    fn STImage::zeros(t : Int, b : Int, c : Int, h : Int, w : Int) -> STImage

    Empty [T, B, C, H, W] tensor filled with zeros.

    STPEntryKind

    pub(all) enum STPEntryKind {
    MarkramSTP_(MarkramSTPEntry)
    MarkramSTPHet_(MarkramSTPEntryHet)
    MarkramSTPTimestep_(MarkramSTPEntryTimestep)
    }

    STPEntryKind — enum-dispatched wrapper around STP entry types. Currently MarkramSTP_ (homogeneous) and MarkramSTPHet_ (per-pre heterogeneous) are implemented; future variants can be added.

    Sac

    pub struct Sac {
    policy : LinearSoftmaxPolicy
    q1 : LinearQNet
    q2 : LinearQNet
    q1_target : LinearQNet
    q2_target : LinearQNet
    alpha : Float
    }

    SAC policy + critic + target critics + temperature α.

    Sac::new

    fn Sac::new(n_states : Int, n_actions : Int, alpha : Float, seed : UInt64) -> Sac

    SacAutoAlpha

    pub struct SacAutoAlpha {
    policy : LinearSoftmaxPolicy
    q1 : LinearQNet
    q2 : LinearQNet
    q1_target : LinearQNet
    q2_target : LinearQNet
    log_alpha : Float
    target_entropy : Float
    alpha_lr : Float
    }

    SAC agent with auto-tuned entropy temperature.

    SacAutoAlpha::new

    fn SacAutoAlpha::new(n_states : Int, n_actions : Int, log_alpha_init : Float, target_entropy : Float, alpha_lr : Float, seed : UInt64) -> SacAutoAlpha

    SimLog

    pub struct SimLog {
    t : Float
    active_monitors : Int
    total_spikes : Int
    }

    Log entry recorded by heterogeneous_sim_for_with_log.

    SimpleCNN

    pub struct SimpleCNN {
    layers : Array[Layer]
    in_c : Int
    c1 : Int
    c2 : Int
    }

    A small CNN: Conv(c1, c2) → ReLU → MaxPool → Flatten → FC(10). Designed for 28x28 MNIST-like inputs (single channel by default).

    Input: [n, in_c, 28, 28] Block 1: Conv2d(in_c -> c1, 3x3, pad=1) -> ReLU -> MaxPool 2x2 / 2 Block 2: Conv2d(c1 -> c2, 3x3, pad=1) -> ReLU -> MaxPool 2x2 / 2 FC: Linear(c2 * 7 * 7 -> 10)

    SimpleCNN::new

    fn SimpleCNN::new(in_c : Int, c1 : Int, c2 : Int, seed : UInt64) -> SimpleCNN

    Build a SimpleCNN. in_c is input channels (1 for MNIST, 3 for color images).

    SingleExpParameter

    pub struct SingleExpParameter {
    tau_e : Float
    tau_i : Float
    e_i : Float
    e_e : Float
    gsyn_e : Float
    gsyn_i : Float
    }

    SingleExpParameter — single exponential synapse parameters.

    SingleExpParameter::new

    Default SingleExpParameter (Julia defaults: τe=6ms, τi=0.5ms, E_i=-75mV, E_e=0mV, gsyn_e=1.0, gsyn_i=1.0).

    SingleExpSynapse

    pub(all) struct SingleExpSynapse {
    tau_e : Float
    tau_i : Float
    e_i : Float
    e_e : Float
    gsyn_e : Float
    gsyn_i : Float
    }

    SingleExpSynapse — single-exponential synaptic dynamics. Fields:
    • tau_e : decay time constant for excitatory synapses (ms)
    • tau_i : decay time constant for inhibitory synapses (ms)
    • e_i : reversal potential for inhibitory synapses (mV)
    • e_e : reversal potential for excitatory synapses (mV)
    • gsyn_e : scalar synaptic conductance for excitatory synapses
    • gsyn_i : scalar synaptic conductance for inhibitory synapses

    SingleExpSynapse::new

    Defaults match Julia's SingleExpSynapse (tau_e=6ms, tau_i=0.5ms, e_i=-75mV, e_e=0mV, gsyn_e=gsyn_i=1.0).

    SingleExpSynapseVars

    pub struct SingleExpSynapseVars {
    n : Int
    ge : Array[Float]
    gi : Array[Float]
    }

    SingleExpSynapseVars — per-neuron state for SingleExpSynapse.

    SingleExpSynapseVars::new

    SmoothSkew

    pub(all) enum SmoothSkew {
    None
    Left
    Right
    }

    Skew option for gaussian_smooth (substitute for Julia's skewed::Symbol).

    SparseMatrixCSR

    pub struct SparseMatrixCSR {
    rows : Int
    cols : Int
    rowptr : Array[Int]
    colptr : Array[Int]
    vals : Array[Float]
    }

    Sparse matrix in compressed-sparse-row format.

    SparseMatrixCSR::empty

    fn SparseMatrixCSR::empty(rows : Int, cols : Int) -> SparseMatrixCSR

    Build an empty N×M sparse matrix.

    SparseMatrixCSR::forward

    fn SparseMatrixCSR::forward(m : SparseMatrixCSR, pre_fire : Array[Bool], post_g : Array[Float]) -> Unit

    Forward a pre-synaptic neuron's outgoing weights to the post-synaptic target's g array (e.g. glu for :ge, gaba for :gi).

    SparseMatrixCSR::forward_rate

    fn SparseMatrixCSR::forward_rate(m : SparseMatrixCSR, pre_rate : Array[Float], post_g : Array[Float]) -> Unit

    Rate-mode forward: for each row, add w * pre_rate[i] to post_g[j]. Unlike the spike-driven forward, this does not gate on a Boolean mask — every pre-synaptic neuron contributes proportional to its firing rate. Used by RateSynapse for Wilson-Cowan networks.

    SparseMatrixCSR::from_dense

    fn SparseMatrixCSR::from_dense(dense : Array[Array[Float]], threshold : Float) -> SparseMatrixCSR

    Build a CSR sparse matrix from a dense Float32 matrix. Density = fraction of non-zero entries preserved.

    SparseMatrixCSR::get

    fn SparseMatrixCSR::get(m : SparseMatrixCSR, i : Int, j : Int) -> Float

    Get the value at (i, j), or 0.0 if not stored.

    SparseMatrixCSR::nnz

    fn SparseMatrixCSR::nnz(m : SparseMatrixCSR) -> Int

    Number of stored non-zeros.

    SparseMatrixCSR::random

    fn SparseMatrixCSR::random(rows : Int, cols : Int, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> SparseMatrixCSR

    Build a sparse matrix with random Normal(μ, σ) weights and connection probability p. Equivalent to Julia's sparse_matrix(post, pre, μ, σ, p).

    rng is the Xoshiro stream. The Float32 draw path is bit-exact with Julia's rand(rng, Float32) for the same seed.

    SparseMatrixCSR::random_with_rule

    fn SparseMatrixCSR::random_with_rule(rows : Int, cols : Int, mu : Float, sigma : Float, p : Float, rule : ConnectRule, rng : Xoshiro) -> SparseMatrixCSR

    Build a sparse matrix with the given connection rule. See ConnectRule for the semantics of each rule.

    SparseMatrixCSR::row_nnz

    fn SparseMatrixCSR::row_nnz(m : SparseMatrixCSR, i : Int) -> Int

    Number of connections in row i (out-degree of pre-synaptic neuron i).

    SparseMatrixCSR::set

    fn SparseMatrixCSR::set(m : SparseMatrixCSR, i : Int, j : Int, v : Float) -> Unit

    Set the value at (i, j). If a value already exists, replace it; otherwise append. Updates rowptr in place.

    SpikeSurrogate

    pub struct SpikeSurrogate {
    spike : Array[Float]
    grad : Array[Float]
    }

    Combined hard-spike + surrogate-gradient envelope.

    Given membrane potentials u and threshold vt, returns:
    • spike[i] = H(u[i] - vt) (forward 0/1 spike)
    • grad[i] = σ'(u[i] - vt; β) (backward surrogate)

    Use this in BPTT loops to avoid recomputing u - vt twice per layer. The forward / backward are decoupled: the forward signal is the true Heaviside (matches the IF neuron step exactly), while the backward uses the surrogate for gradient flow.

    SpikeTimeParameter

    pub struct SpikeTimeParameter {
    spiketimes : Array[Float]
    neurons : Array[Int]
    }

    Parameters for SpikeTimeStimulus. spiketimes is sorted ascending (in ms). neurons[i] is the pre-synaptic index that fires at spiketimes[i].

    SpikeTimeParameter::new

    fn SpikeTimeParameter::new(spiketimes : Array[Float], neurons : Array[Int]) -> SpikeTimeParameter

    Construct a SpikeTimeParameter from parallel arrays. Sorts by spike time so the next-spike pointer walks monotonically.

    SpikeTimeStimulus

    pub struct SpikeTimeStimulus {
    n : Int
    param : SpikeTimeParameter
    next_spike : Array[Float]
    next_index : Array[Int]
    fire : Array[Bool]
    g : Array[Float]
    }

    SpikeTimeStimulus — injects spikes at exact times. Mirrors Julia's SNN.SpikeTimeStimulus(E, :ge; param, conn). Uses next_spike + next_index as monotonic pointers into the param's spike list. g is the post-synaptic receptor (e.g. e_pop.glu for :ge).

    SpikeTimeStimulus::new

    fn SpikeTimeStimulus::new(e_pop : IF, sym : String, spiketimes : Array[Float], neurons : Array[Int]) -> SpikeTimeStimulus

    Construct a SpikeTimeStimulus targeting the g receptor of a post-synaptic IF population. spiketimes should be in ms and ascending; neurons are pre-synaptic indices in [0, N).

    SpikingAttention

    pub struct SpikingAttention {
    d_model : Int
    num_heads : Int
    d_k : Int
    beta : Float
    w_q : LinearParam
    w_k : LinearParam
    w_v : LinearParam
    w_o : LinearParam
    }

    Backward-compat struct (was the v0.24.0 name for multi-head self-attention). Structurally identical to SpikingMultiHeadAttention. New code should use SpikingMultiHeadAttention directly.

    SpikingAttention::new

    fn SpikingAttention::new(d_model : Int, num_heads : Int, beta : Float, seed : UInt64) -> SpikingMultiHeadAttention

    Backward-compat: build a SpikingMultiHeadAttention under the historical SpikingAttention name (same struct shape).

    SpikingAttnCache

    pub struct SpikingAttnCache {
    seq_len : Int
    x : Array[Float]
    q : Array[Float]
    k : Array[Float]
    v : Array[Float]
    scores : Array[Float]
    weights : Array[Float]
    out_pre : Array[Float]
    mask : Array[Float]
    scale : Float
    beta : Float
    }

    Backward-compat cache (was SpikingAttnCache in v0.24.0).

    SpikingCrossAttention

    pub struct SpikingCrossAttention {
    d_model : Int
    num_heads : Int
    d_k : Int
    beta : Float
    w_q : LinearParam
    w_k : LinearParam
    w_v : LinearParam
    w_o : LinearParam
    }

    Spiking cross-attention parameter container. Q is computed from x_q, K/V are computed from a separate x_kv input.

    SpikingCrossAttention::new

    fn SpikingCrossAttention::new(d_model : Int, num_heads : Int, beta : Float, seed : UInt64) -> SpikingCrossAttention

    Construct a SpikingCrossAttention with Xavier-normal init.

    SpikingCrossAttnCache

    pub struct SpikingCrossAttnCache {
    seq_len_q : Int
    seq_len_kv : Int
    x_q : Array[Float]
    x_kv : Array[Float]
    q : Array[Float]
    k : Array[Float]
    v : Array[Float]
    scores : Array[Float]
    weights : Array[Float]
    out_pre : Array[Float]
    mask : Array[Float]
    scale : Float
    beta : Float
    }

    Forward / backward cache for cross-attention.

    SpikingMultiHeadAttention

    pub struct SpikingMultiHeadAttention {
    d_model : Int
    num_heads : Int
    d_k : Int
    beta : Float
    w_q : LinearParam
    w_k : LinearParam
    w_v : LinearParam
    w_o : LinearParam
    }

    Spiking multi-head self-attention parameter container.

    SpikingMultiHeadAttention::new

    fn SpikingMultiHeadAttention::new(d_model : Int, num_heads : Int, beta : Float, seed : UInt64) -> SpikingMultiHeadAttention

    Construct a SpikingMultiHeadAttention with Xavier-normal init.

    SpikingMultiHeadAttnCache

    pub struct SpikingMultiHeadAttnCache {
    seq_len : Int
    x : Array[Float]
    q : Array[Float]
    k : Array[Float]
    v : Array[Float]
    scores : Array[Float]
    weights : Array[Float]
    out_pre : Array[Float]
    mask : Array[Float]
    scale : Float
    beta : Float
    }

    Forward / backward cache for multi-head self-attention.

    SpikingSynapse

    pub struct SpikingSynapse {
    pre : IF
    post : IF
    sym : String
    name : String
    matrix : SparseMatrixCSR
    delays : Array[Float]
    rho : Array[Float]
    pending_times : Array[Float]
    pending_posts : Array[Int]
    pending_weights : Array[Float]
    }

    SpikingSynapse state — CSR sparse matrix of connection weights plus the post-synaptic target's receptor (ge or gi).

    If delays is non-empty, connections have per-edge delays (in ms). When pre fires, the spike is scheduled for delivery at t + delays[s] instead of being applied immediately. The pending_* queues track scheduled events; deliver_pending_synapse drains them when the current sim time reaches the delivery time.

    If rho is non-empty, each connection has a per-edge weight modifier (parallel to matrix.vals). The actual synaptic current applied is matrix.vals[s] * rho[s]. This is used by Markram STP to scale each spike by the pre-synaptic resource availability. An empty rho means "no scaling" (rho defaults to 1.0F).

    SpikingSynapse::init_rho

    fn SpikingSynapse::init_rho(c : SpikingSynapse) -> Unit

    Allocate the per-connection rho array (parallel to matrix.vals) and initialise all entries to 1.0F. Call this once after building the connectivity, before enabling STP. With rho non-empty, forward_synapse will multiply each weight by rho[s] when applying it to the post-synaptic receptor.

    SpikingSynapse::new

    fn SpikingSynapse::new(pre : IF, post : IF, sym : String) -> SpikingSynapse

    Construct an empty SpikingSynapse. Add connections with connect! or random_connections!. No delays, no STP — pre spikes deliver immediately at full weight.

    SpikingSynapse::random

    fn SpikingSynapse::random(pre : IF, post : IF, sym : String, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> SpikingSynapse

    Build a random connectivity matrix with mean weight mu, weight std sigma, and connection probability p. Matches Julia's SpikingSynapse(pre, post, sym; conn = (mu, sigma, p)). Default rule is Bernoulli.

    SpikingSynapse::random_with_delays

    fn SpikingSynapse::random_with_delays(pre : IF, post : IF, sym : String, mu : Float, sigma : Float, p : Float, rng : Xoshiro, d_mean : Float, d_std : Float) -> SpikingSynapse

    Build a random connectivity matrix with per-connection delays sampled from a Normal distribution (d_mean, d_std). Matches Julia's SpikingSynapse(...; delay_dist = Normal(d_mean, d_std)).

    If d_std == 0.0F, all delays are exactly d_mean. Delays are clamped at >= 0 ms.

    SpikingSynapse::random_with_rule

    fn SpikingSynapse::random_with_rule(pre : IF, post : IF, sym : String, mu : Float, sigma : Float, p : Float, rule : ConnectRule, rng : Xoshiro) -> SpikingSynapse

    Build a random connectivity matrix with an explicit connection rule. Mirrors Julia's SpikingSynapse(pre, post, sym; conn =(mu, sigma, p, rule=:FixedIn)). See ConnectRule for the semantics.

    SpikingSynapse::set_constant_delay

    fn SpikingSynapse::set_constant_delay(c : SpikingSynapse, d : Float) -> Unit

    Set a constant delay (in ms) for every stored connection. Mirrors Julia's delay_dist = Normal(d_mean, 0) constant case.

    SpikingSynapse::set_delays

    fn SpikingSynapse::set_delays(c : SpikingSynapse, delays : Array[Float]) -> Unit

    Set per-connection delays (parallel to matrix.vals, in ms). Used for delay_dist support. After this call, pre spikes are scheduled for delivery at t + delays[s] instead of applied immediately.

    SpikingSynapse::with_name

    fn SpikingSynapse::with_name(c : SpikingSynapse, name : String) -> SpikingSynapse

    Set the human-readable label on a SpikingSynapse. Mirrors Julia's name = "..." keyword argument. Returns a new struct (MoonBit fields are immutable); the caller must rebind:

    let s = s.with_name("my_syn")

    SpikingSynapseAdEx

    pub struct SpikingSynapseAdEx {
    pre : AdEx
    post : AdEx
    sym : String
    matrix : SparseMatrixCSR
    }

    SpikingSynapse that targets an AdEx post-synaptic neuron. Supports :ge / :he (excitatory, both routed to glu) and :gi / :hi / :gaba (inhibitory, all routed to gaba). The Julia SNN distinguishes :he from :ge via different rise-time constants, but our simplified DoubleExp model uses the same path for both.

    SpikingSynapseAdEx::new

    fn SpikingSynapseAdEx::new(pre : AdEx, post : AdEx, sym : String) -> SpikingSynapseAdEx

    SpikingSynapseAdEx::random

    fn SpikingSynapseAdEx::random(pre : AdEx, post : AdEx, sym : String, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> SpikingSynapseAdEx

    Build a SpikingSynapseAdEx with random CSR connectivity. mu/sigma control the Normal weight distribution; p is the per-edge Bernoulli connection probability.

    SpikingSynapseAdEx::random_with_rule

    fn SpikingSynapseAdEx::random_with_rule(pre : AdEx, post : AdEx, sym : String, mu : Float, sigma : Float, p : Float, rule : ConnectRule, rng : Xoshiro) -> SpikingSynapseAdEx

    Build a SpikingSynapseAdEx with an explicit connection rule.

    SpikingSynapseHH

    pub struct SpikingSynapseHH {
    pre : HH
    post : HH
    sym : String
    matrix : SparseMatrixCSR
    }

    SpikingSynapse targeting an HH post-synaptic neuron.

    SpikingSynapseHH::new

    fn SpikingSynapseHH::new(pre : HH, post : HH, sym : String) -> SpikingSynapseHH

    SpikingSynapseHH::random

    fn SpikingSynapseHH::random(pre : HH, post : HH, sym : String, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> SpikingSynapseHH

    SpikingSynapseIZ

    pub struct SpikingSynapseIZ {
    pre : IZ
    post : IZ
    sym : String
    matrix : SparseMatrixCSR
    }

    SpikingSynapse targeting an IZ post-synaptic neuron. sym ∈ {"ge", "gi"}: excitatory routes to post.ge, inhibitory routes to post.gi. (Julia's IZ uses :v for both with sign of μ determining E/I, but we keep the exc/inc split for clarity and to match our existing convention.)

    SpikingSynapseIZ::new

    fn SpikingSynapseIZ::new(pre : IZ, post : IZ, sym : String) -> SpikingSynapseIZ

    SpikingSynapseIZ::random

    fn SpikingSynapseIZ::random(pre : IZ, post : IZ, sym : String, mu : Float, sigma : Float, p : Float, rng : Xoshiro) -> SpikingSynapseIZ

    Build a SpikingSynapseIZ with random CSR connectivity.

    SpikingTransformerBlock

    pub struct SpikingTransformerBlock {
    d_model : Int
    num_heads : Int
    d_ff : Int
    beta : Float
    ln_1 : LayerNorm
    ln_2 : LayerNorm
    sa : SpikingMultiHeadAttention
    ffn_w1 : LinearParam
    ffn_w2 : LinearParam
    }

    Spiking transformer block parameter container.

    SpikingTransformerBlock::new

    fn SpikingTransformerBlock::new(d_model : Int, num_heads : Int, beta : Float, seed : UInt64, d_ff? : Int) -> SpikingTransformerBlock

    Construct a SpikingTransformerBlock. d_ff defaults to 4·d_model.

    SpikingTransformerBlockCache

    pub struct SpikingTransformerBlockCache {
    seq_len : Int
    x : Array[Float]
    x2 : Array[Float]
    x_norm1 : Array[Float]
    x_norm2 : Array[Float]
    ln1_cache : LayerNormCache
    sa_cache : SpikingMultiHeadAttnCache
    ln2_cache : LayerNormCache
    ffn_hidden_pre_gelu : Array[Float]
    ffn_hidden_post_gelu : Array[Float]
    mask : Array[Float]
    }

    Forward / backward cache.

    SpikingTransformerBlockGrad

    pub struct SpikingTransformerBlockGrad {
    ln1_d_gamma : Array[Float]
    ln1_d_beta : Array[Float]
    sa_grad : MHAGrad
    ln2_d_gamma : Array[Float]
    ln2_d_beta : Array[Float]
    ffn_d_w1 : Array[Float]
    ffn_d_b1 : Array[Float]
    ffn_d_w2 : Array[Float]
    ffn_d_b2 : Array[Float]
    }

    Gradient bundle (reuses MHAGrad for the spiking attention projection gradients since the Linear layer structure is identical).

    StackedLstmCache

    pub struct StackedLstmCache {
    caches : Array[Array[LstmCellCache]]
    hs : Array[Array[Array[Float]]]
    cs : Array[Array[Array[Float]]]
    }

    Forward cache: per-timestep, per-layer cell caches plus the forward outputs (needed by backward and for loss computation).

    StackedLstmParam

    pub struct StackedLstmParam {
    d_x : Int
    d_h : Int
    n_layers : Int
    layers : Array[LstmCellParam]
    }

    Parameter bundle for a stack of L LSTM cells. All layers share d_h; layer 0 has input dim d_x, subsequent layers have input dim d_h.

    StackedLstmParam::new

    fn StackedLstmParam::new(d_x : Int, d_h : Int, n_layers : Int, seed : UInt64) -> StackedLstmParam

    Build a multi-layer LSTM with n_layers cells, each with hidden dim d_h. Seeds are layered (seed, seed+1, ...).

    StdpConfavreux2025Param

    pub(all) struct StdpConfavreux2025Param {
    eta : Float
    alpha : Float
    gamma : Float
    mu : Float
    nu : Float
    tau_pre : Float
    tau_post : Float
    w_min : Float
    w_max : Float
    }

    StdpConfavreux2025Param::new

    StdpConfavreux2025State

    pub struct StdpConfavreux2025State {
    n_pre : Int
    n_post : Int
    tpre : Array[Float]
    tpost : Array[Float]
    last_pre : Array[Float]
    last_post : Array[Float]
    }

    StdpConfavreux2025State::new

    fn StdpConfavreux2025State::new(n_pre : Int, n_post : Int) -> StdpConfavreux2025State

    StdpGerstnerParam

    pub(all) struct StdpGerstnerParam {
    a_pre : Float
    a_post : Float
    tau_pre : Float
    tau_post : Float
    w_min : Float
    w_max : Float
    }

    STDP Gerstner parameters.

    StdpGerstnerParam::new

    StdpGerstnerState

    pub struct StdpGerstnerState {
    n_pre : Int
    n_post : Int
    tpre : Array[Float]
    tpost : Array[Float]
    last_pre : Array[Float]
    last_post : Array[Float]
    }

    STDP Gerstner state (per-network traces).

    StdpGerstnerState::new

    fn StdpGerstnerState::new(n_pre : Int, n_post : Int) -> StdpGerstnerState

    StepLR

    pub struct StepLR {
    base_lr : Float
    step_size : Int
    gamma : Float
    current_step : Int
    }

    StepLR state. lr drops by gamma every step_size calls to next.

    StepLR::new

    fn StepLR::new(base_lr : Float, step_size : Int, gamma : Float) -> StepLR

    Build a StepLR scheduler.

    StepLR::next

    fn StepLR::next(self : StepLR) -> StepLR

    Increment the step counter; returns a fresh state.

    StepLR::step

    fn StepLR::step(self : StepLR) -> Float

    Compute the current learning rate.

    StepRecord

    pub struct StepRecord {
    s : Int
    a : Int
    r : Float
    s_next : Int
    done : Bool
    }

    Per-step transition tuple stored in the n-step buffer.

    StimulusGroup

    pub struct StimulusGroup {
    name : String
    elements : Array[String]
    active : Array[Bool]
    }

    StimulusGroup — a list of stimuli that can be operated on as one.

    elements is a flat array of stimulus tags ("Poisson", "SpikeTime", "Current", ...). Operations on the group (set_active, stimulate) iterate and dispatch per tag.

    StimulusGroup::is_active

    fn StimulusGroup::is_active(s : StimulusGroup, i : Int) -> Bool

    Get the active flag for element i.

    StimulusGroup::n_elements

    fn StimulusGroup::n_elements(s : StimulusGroup) -> Int

    Number of stimuli in the group.

    StimulusGroup::new

    fn StimulusGroup::new(name : String, elements : Array[String]) -> StimulusGroup

    Build a StimulusGroup from a name + list of stimulus tags.

    StimulusGroup::set_active_all

    fn StimulusGroup::set_active_all(s : StimulusGroup, active : Bool) -> Unit

    Set the active flag for ALL stimuli in the group.

    StimulusGroup::set_active_one

    fn StimulusGroup::set_active_one(s : StimulusGroup, i : Int, active : Bool) -> Unit

    Set the active flag for a single element.

    StimulusGroup::tag

    fn StimulusGroup::tag(s : StimulusGroup, i : Int) -> String

    Get the tag of element i.

    SumTree

    pub struct SumTree {
    capacity : Int
    tree : Array[Float]
    data_count : Int
    }

    Flat-array sum tree. Holds up to capacity non-negative priorities.

    SumTree::add

    fn SumTree::add(self : SumTree, priority : Float) -> Int

    Add a new priority to the next empty leaf slot (FIFO order). Returns the tree index of the leaf, or -1 if the tree is at full capacity.

    On overwrite (when data_count == capacity), the call is a no-op and returns -1. Use set_at(slot, ...) to overwrite an existing slot.

    SumTree::capacity

    fn SumTree::capacity(self : SumTree) -> Int

    Underlying slot capacity (in leaves). Slots are filled lazily as data is added.

    SumTree::find_prefix

    fn SumTree::find_prefix(self : SumTree, target_sum_in : Float) -> (Int, Float)

    Cumulative-sum lookup: find the leaf whose prefix sum covers target_sum.

    Algorithm (Schaul 2016, Algorithm 1, simplified): parent = root (idx 0)

    loop: left = 2parent + 1 right = 2parent + 2 if left >= len(tree): // parent is a leaf return (parent, tree[parent]) if target_sum <= tree[left]: parent = left else: target_sum -= tree[left] parent = right

    Returns (tree_idx, priority_at_that_leaf). If target_sum exceeds the total, the rightmost leaf is returned.

    SumTree::find_prefix_batch

    fn SumTree::find_prefix_batch(self : SumTree, segment_values : Array[Float]) -> Array[(Int, Float)]

    Batch cumulative-sum lookup: draw n_segments uniform segments over [0, sum) and return one leaf per segment. Segments partition [0, total] evenly, so over many calls each leaf's selection probability approaches p_i / Σp_j.

    SumTree::get_leaf

    fn SumTree::get_leaf(self : SumTree, slot : Int) -> Float

    Get priority at a leaf slot index (NOT a tree index). Leaf i is stored at tree index capacity - 1 + i in our layout.

    SumTree::len

    fn SumTree::len(self : SumTree) -> Int

    Number of priority slots (capacity in leaves).

    SumTree::new

    fn SumTree::new(capacity : Int) -> SumTree

    Create an empty sum tree. All priorities start at 0.

    SumTree::set_at

    fn SumTree::set_at(self : SumTree, slot : Int, new_priority : Float) -> Unit

    Set priority at an existing leaf slot (used to update after TD-error updates).

    SumTree::set_priority

    fn SumTree::set_priority(self : SumTree, tree_idx : Int, new_priority : Float) -> Unit

    Set priority at a tree index, propagating the delta up to the root. Caller computes delta as (new - old) — this method does NOT read the existing value, which keeps the cost O(log N) without an extra read.

    SumTree::total

    fn SumTree::total(self : SumTree) -> Float

    Total priority sum = root node.

    SynapseNormalization

    pub struct SynapseNormalization {
    param : NormParam
    n_post : Int
    w0 : Array[Float]
    w1 : Array[Float]
    mu : Array[Float]
    targets : Array[SynapseTarget]
    }

    SynapseNormalization — holds the initial weight sum W0[i] for each post-neuron i, and a list of synapse targets whose weights to mutate. Mirrors Julia's SynapseNormalization struct (subset of fields).

    SynapseNormalization::new

    Construct a SynapseNormalization from a list of synapse targets. All targets MUST share the same post_id AND the same n_post — the caller is expected to verify this before calling. (We do not abort at construction time because MoonBit's abort is a polymorphic bottom type that the type system doesn't track as a raise site — making try...catch ergonomics poor. Julia's @assert is a runtime check that throws AssertionError; we rely on the caller to ensure post-population consistency.)

    SynapseTarget

    pub(all) struct SynapseTarget {
    post_id : Int
    vals : Array[Float]
    rowptr : Array[Int]
    }

    SynapseTarget — holds a reference to a synapse's CSR (vals + rowptr) and a post-population identifier. The mutation loop uses rowptr[i] and vals[j] to access the actual weights. post_id identifies which post-neuron population the buffer targets and is used by callers to ensure all targets in the normalization share the same post-population.

    T5RelativePosition

    pub struct T5RelativePosition {
    num_heads : Int
    max_distance : Int
    weight : Array[Float]
    }

    T5-style relative position bias parameter container.

    T5RelativePosition::new

    fn T5RelativePosition::new(num_heads : Int, max_distance : Int, seed : UInt64) -> T5RelativePosition

    Construct a T5 relative position bias. max_distance controls how far apart positions can be before the bias saturates (T5 paper uses 128; we use a smaller default for demos).

    Tape

    pub struct Tape[A] {
    insts : Array[Inst[A]]
    names : Array[String]
    }

    A linear sequence of instructions recorded during the forward pass. Use Tape::variable to declare inputs, Tape::op1/op2 to record operations, Tape::eval to run forward, and Tape::diff_backward (or diff_forward) to compute gradients.

    Tape::diff_backward

    fn[A : Diffable + Add + Sub] Tape::diff_backward(self : Tape[A], mem : Array[A]) -> Array[A]

    Compute backward-mode gradients. Returns an array grads where grads[i] is ∂output / ∂inst[i] — i.e. the partial derivative of the last instruction (assumed to be the scalar output) with respect to the i-th instruction. The gradient of the output w.r.t. itself is seeded to A::one().

    Tape::diff_forward

    fn[A : Diffable + Mul + Add + Sub] Tape::diff_forward(self : Tape[A], mem : Array[A], wrt? : Int) -> Array[A]

    Compute forward-mode derivative of every intermediate value with respect to the variable at index wrt. Returns an array df[i] = ∂inst[i] / ∂inst[wrt]. The seed df[wrt] = 1.

    Caveat: the backward functions for our primitive ops expect the upstream gradient as the first argument. To re-use them in forward mode, we substitute 1 for the gradient — this gives the local derivative at the input (since bwd(1, x) = df/dx for our convention where bwd(g, x) = g · df/dx).

    Tape::eval

    fn[A : Diffable + Add + Sub] Tape::eval(self : Tape[A]) -> Array[A]

    Run the forward pass, evaluating every instruction and writing its result into the corresponding memory slot. Returns an array of length insts.length() where mem[i] is the value of the i-th instruction (variables first, then operations in order).

    Tape::new

    fn[A] Tape::new() -> Tape[A]

    Create an empty tape. All subsequent operations are appended to this tape in order.

    Tape::op1

    fn[A] Tape::op1(self : Tape[A], name : String, fwd : (A) -> A, bwd : (A, A) -> A) -> ((Loc[A]) -> Loc[A])

    Append a unary operation to the tape. Returns a function that takes a Loc[A] input and produces a Loc[A] for the output.

    bwd : (A, A) -> A takes the upstream gradient and the original input value and returns the input's gradient (g · df/dx evaluated at the input).

    Tape::op2

    fn[A] Tape::op2(self : Tape[A], name : String, fwd : (A, A) -> A, bwd_lhs : (A, A, A) -> A, bwd_rhs : (A, A, A) -> A) -> ((Loc[A], Loc[A]) -> Loc[A])

    Append a binary operation to the tape. Returns a function that takes two Loc[A] inputs and produces a Loc[A] for the output.

    bwd_lhs(g, lhs, rhs) returns the lhs gradient (g · df/dlhs at (lhs, rhs)); bwd_rhs(g, lhs, rhs) returns the rhs gradient. Both functions receive BOTH original operands so they can implement partial derivatives that depend on the partner (e.g. df/db = -a/b² for a / b).

    Tape::variable

    fn[A] Tape::variable(self : Tape[A], value : A) -> Loc[A]

    Append a variable with the given value to the tape. Returns the memory slot where the variable's value will be stored. Variables are leaves of the computation graph — they receive gradient in diff_backward but do not propagate further.

    Tensor

    pub struct Tensor {
    data : Array[Float]
    shape : Array[Int]
    }

    Tensor struct: row-major flat data + shape.

    Tensor::from

    fn Tensor::from(data : Array[Float], shape : Array[Int]) -> Tensor

    Build a Tensor from a flat data array + shape. Does NOT copy data.

    Tensor::numel

    fn Tensor::numel(self : Tensor) -> Int

    Element count.

    Tensor::ones

    fn Tensor::ones(shape : Array[Int]) -> Tensor

    Build a Tensor with the given shape, filled with ones.

    Tensor::reshape

    fn Tensor::reshape(self : Tensor, shape : Array[Int]) -> Tensor

    Reshape to a new shape. Panics if total element count mismatches.

    Tensor::zeros

    fn Tensor::zeros(shape : Array[Int]) -> Tensor

    Build a Tensor with the given shape, filled with zeros.

    Time

    pub struct Time {
    t : Array[Float]
    tt : Array[Int]
    dt : Float
    }

    Time tracker for the SNN simulator. Holds the current simulation time t (Float32, ms), the integer step counter tt (Int32), and the integration step size dt (Float32, default 0.125).

    Time::from_ms

    fn Time::from_ms(time : Float) -> Time

    Construct a Time struct from a numeric time value (ms). The initial step counter is time / 0.125 rounded to Int32, matching Julia's Int32(tts) truncation (Float-to-Int casts truncate toward zero).

    Time::new

    fn Time::new() -> Time

    Construct a Time struct at t=0 with dt=0.125f0.

    TransformerBlock

    pub struct TransformerBlock {
    d_model : Int
    num_heads : Int
    d_ff : Int
    ln_1 : LayerNorm
    ln_2 : LayerNorm
    mha : MultiHeadAttention
    ffn_w1 : LinearParam
    ffn_w2 : LinearParam
    }

    Transformer block parameter container.

    TransformerBlock::new

    fn TransformerBlock::new(d_model : Int, num_heads : Int, seed : UInt64, d_ff? : Int) -> TransformerBlock

    Construct a Transformer block. d_ff defaults to 4 * d_model.

    TransformerBlockCache

    pub struct TransformerBlockCache {
    seq_len : Int
    x : Array[Float]
    x2 : Array[Float]
    x_norm1 : Array[Float]
    x_norm2 : Array[Float]
    ln1_cache : LayerNormCache
    attn_cache : AttnCache
    ln2_cache : LayerNormCache
    ffn_hidden_pre_gelu : Array[Float]
    ffn_hidden_post_gelu : Array[Float]
    mask : Array[Float]
    }

    Forward / backward cache for a Transformer block.

    TransformerBlockGrad

    pub struct TransformerBlockGrad {
    ln1_d_gamma : Array[Float]
    ln1_d_beta : Array[Float]
    mha_grad : MHAGrad
    ln2_d_gamma : Array[Float]
    ln2_d_beta : Array[Float]
    ffn_d_w1 : Array[Float]
    ffn_d_b1 : Array[Float]
    ffn_d_w2 : Array[Float]
    ffn_d_b2 : Array[Float]
    }

    Per-block gradient bundle.

    TripletRule

    pub(all) struct TripletRule {
    a2_ltp : Float
    a3_ltp : Float
    a2_ltd : Float
    a3_ltd : Float
    tau_x : Float
    tau_y : Float
    tau_p : Float
    tau_m : Float
    }
    TripletRule (Pfister 2006) — three-factor STDP with A2/A3/A3̂/A3̄ amplitudes. Port of refs/SNNModels.jl/src/connections/sparse_plasticity/dump.jl::TripletRule. Field names mapped to ASCII (Julia uses superscripts/subscripts). a2_ltp = A⁺₂ — post-after-pre pair LTP rate a3_ltp = A⁺₃ — triplet LTP rate a2_ltd = A⁻₂ — pre-after-post pair LTD rate a3_ltd = A⁻₃ — triplet LTD rate tau_x = τˣ — pre trace time constant tau_y = τʸ — post trace time constant tau_p = τ⁺ — triplet potentiation time constant tau_m = τ⁻ — triplet depression time constant

    TripletRule::pfister_alltoall_full

    fn TripletRule::pfister_alltoall_full() -> TripletRule
    Defaults from pfister_visualcortex(true, true) (all-to-all, full triplet pair and triplet terms).

    Tripod

    pub struct Tripod {
    soma_param : AdExParameter
    soma_spike : AdExPostSpike
    d1 : Dendrite
    d2 : Dendrite
    i_s : Array[Float]
    i_d1 : Array[Float]
    i_d2 : Array[Float]
    ge_s : Array[Float]
    gi_s : Array[Float]
    ge_d1 : Array[Float]
    gi_d1 : Array[Float]
    ge_d2 : Array[Float]
    gi_d2 : Array[Float]
    glu_s : Array[Float]
    gaba_s : Array[Float]
    glu_d1 : Array[Float]
    gaba_d1 : Array[Float]
    glu_d2 : Array[Float]
    gaba_d2 : Array[Float]
    e_e : Float
    e_i : Float
    tau_e : Float
    tau_i : Float
    gsyn_e : Float
    gsyn_i : Float
    n : Int
    v_s : Array[Float]
    w_s : Array[Float]
    v_d1 : Array[Float]
    v_d2 : Array[Float]
    fire : Array[Bool]
    threshold : Array[Float]
    tabs : Array[Int]
    dv : Array[Float]
    dv_temp : Array[Float]
    syn_curr_s : Array[Float]
    syn_curr_d1 : Array[Float]
    syn_curr_d2 : Array[Float]
    }

    Tripod neuron state — soma (AdEx) + 2 dendrites (d1, d2).

    Tripod::new

    fn Tripod::new(n : Int, soma_param : AdExParameter, rng : Xoshiro) -> Tripod

    Default Tripod: AdEx soma + 2 Dendrites with human_dend defaults.

    TripodHet

    pub struct TripodHet {
    soma_param : AdExParameterHet
    soma_spike : AdExPostSpike
    c : Array[Float]
    gl : Array[Float]
    d1 : Dendrite
    d2 : Dendrite
    i_s : Array[Float]
    i_d1 : Array[Float]
    i_d2 : Array[Float]
    ge_s : Array[Float]
    gi_s : Array[Float]
    ge_d1 : Array[Float]
    gi_d1 : Array[Float]
    ge_d2 : Array[Float]
    gi_d2 : Array[Float]
    glu_s : Array[Float]
    gaba_s : Array[Float]
    glu_d1 : Array[Float]
    gaba_d1 : Array[Float]
    glu_d2 : Array[Float]
    gaba_d2 : Array[Float]
    e_e : Float
    e_i : Float
    tau_e : Float
    tau_i : Float
    gsyn_e : Float
    gsyn_i : Float
    n : Int
    v_s : Array[Float]
    w_s : Array[Float]
    v_d1 : Array[Float]
    v_d2 : Array[Float]
    fire : Array[Bool]
    threshold : Array[Float]
    tabs : Array[Int]
    dv : Array[Float]
    dv_temp : Array[Float]
    syn_curr_s : Array[Float]
    syn_curr_d1 : Array[Float]
    syn_curr_d2 : Array[Float]
    }

    TripodHet — Tripod with heterogeneous per-neuron AdEx soma params. Same compartments (:soma + :d1 + :d2) and same Heun integration as Tripod, but each neuron reads its own AdExParameterHet fields.

    TripodHet::new

    fn TripodHet::new(n : Int, soma_param : AdExParameterHet, rng : Xoshiro) -> TripodHet

    Construct a TripodHet population. soma_param must already be filled with per-neuron arrays of length N. Dendrite parameters are derived from d1/d2 (Julia's human_dend defaults).

    Turnover

    pub struct Turnover {
    param : TurnoverParam
    synapse : SpikingSynapse
    pre : Array[Float]
    post : Array[Float]
    p : Array[Float]
    p_rewire : Array[Float]
    p_values : Array[Float]
    }

    Turnover — wraps a single SpikingSynapse with structural plasticity state. pre / post are per-pre / per-post low-pass activity traces (sized to synapse.pre.fire.length() / synapse.post.fire.length()). p is a dense N_pre × N_post candidate-score matrix. p_rewire is the threshold (computed by quantile(p_values, fraction)). p_values is the working buffer of scores per existing connection.

    Turnover::new

    fn Turnover::new(param : TurnoverParam, synapse : SpikingSynapse) -> Turnover

    Build a Turnover wrapping syn. The activity traces are zero-initialised; the candidate matrix p defaults to all-ones.

    TurnoverParam

    pub(all) enum TurnoverParam {
    RandomTurnover_(RandomTurnover)
    ActivityDependentTurnover_(ActivityDependentTurnover)
    }

    Enum-dispatched parameter type for the turnover rule.

    VStdpParam

    pub(all) struct VStdpParam {
    a_ltp : Float
    a_ltd : Float
    v_rest : Float
    delta_v : Float
    tau_pre : Float
    tau_post : Float
    w_min : Float
    w_max : Float
    }

    VStdpParam::new

    fn VStdpParam::new() -> VStdpParam

    VStdpState

    pub struct VStdpState {
    n_pre : Int
    n_post : Int
    tpre : Array[Float]
    tpost : Array[Float]
    v_post : Array[Float]
    }

    VStdpState::new

    fn VStdpState::new(n_pre : Int, n_post : Int, v_post_init : Array[Float]) -> VStdpState

    VstdpParameter

    pub(all) struct VstdpParameter {
    a_ltd : Float
    a_ltp : Float
    theta_ltd : Float
    theta_ltp : Float
    tau_pre : Float
    tau_post : Float
    w_max : Float
    w_min : Float
    }

    VstdpParameter — Litwin-Kumar-Doiron 2014 voltage-dependent STDP.

    VstdpParameter::new

    VstdpVariables

    pub struct VstdpVariables {
    w : Array[Float]
    }

    VstdpVariables — per-connection weight state for vSTDP.

    VstdpVariables::new

    fn VstdpVariables::new(n_pre : Int, n_post : Int) -> VstdpVariables

    WCParameter

    pub(all) struct WCParameter {
    dummy : Float
    }

    WilsonCowan parameters — just a marker struct for the population type.

    WCParameter::new

    WilsonCowan

    pub struct WilsonCowan {
    param : WCParameter
    n : Int
    x : Array[Float]
    r : Array[Float]
    g : Array[Float]
    i : Array[Float]
    }

    WilsonCowan population — a 1-D ODE per neuron with tanh output. State variables:
    • x[i] : internal activity (Float)
    • r[i] : output firing rate (Float) = tanh(x[i])
    • g[i] : synaptic input from RateSynapse (Float)
    • I[i] : external input current (Float)

    WilsonCowan::new

    fn WilsonCowan::new(n : Int, rng : Xoshiro) -> WilsonCowan

    Build a WilsonCowan population. Initial x is Normal(0, 0.5), matching 0.5randn(N) in the Julia source.

    Xoshiro

    pub struct Xoshiro {
    s0 : UInt64
    s1 : UInt64
    s2 : UInt64
    s3 : UInt64
    }

    Xoshiro256++ RNG state. Four UInt64 words; mutable so we can advance.

    Xoshiro::default

    fn Xoshiro::default() -> Xoshiro

    Default seed (0).

    Xoshiro::from_state

    fn Xoshiro::from_state(s0 : UInt64, s1 : UInt64, s2 : UInt64, s3 : UInt64) -> Xoshiro

    Construct a Xoshiro state from four raw UInt64 words. Matches Julia's Xoshiro(s0, s1, s2, s3) (which also sets s4 = 1s0+3s1+5s2+7s3, but s4 is metadata and is not used in rand).

    Xoshiro::new

    fn Xoshiro::new(seed : UInt64) -> Xoshiro

    Seed Xoshiro from a single UInt64 via splitmix64. Matches Julia's TaskLocalRNG/Xoshiro bulk-generation seeding without the SHA-256 step (only used for integer seeds; pass raw state via from_state for full bit-exact reproduction).
    let M : Float

    abs_f32

    fn abs_f32(x : Float) -> Float

    Float32 absolute value, no FFI needed.

    actor_critic_rollout

    fn actor_critic_rollout(env : GridWorld, policy : LinearSoftmaxPolicy, value_net : LinearValueNet, max_steps : Int, rng : Xoshiro) -> EpisodeWithValues

    Roll out one episode under the current policy, recording values.

    actor_critic_update

    fn actor_critic_update(policy : LinearSoftmaxPolicy, value_net : LinearValueNet, episode : EpisodeWithValues, gamma : Float, lr_policy : Float, lr_value : Float) -> Float

    One-step TD actor-critic update. Computes per-step TD errors δ_t = r_t + γ · V(s_{t+1}) · (1 - done_t) - V(s_t), then:

    • critic: w_v += α_v · δ_t · x_{s_t}
    • actor: d_logit[i] = (1{i == a_t} - π(a|s_t)) · δ_t w += α_p · d_logit ⊗ x_{s_t}

    Returns the mean absolute TD error over the episode.

    adafactor_update_1d

    fn adafactor_update_1d(bias : Array[Float], d_bias : Array[Float], state : Adafactor1DState, lr : Float, eps? : Float, beta2? : Float) -> Unit

    Adafactor 1D update.

    adafactor_update_2d

    fn adafactor_update_2d(weight : Array[Float], d_weight : Array[Float], state : Adafactor2DState, lr : Float, eps? : Float, beta2? : Float) -> Unit

    Adafactor 2D update. beta2 defaults to 0.999 (matches Adam).

    adafactor_update_linear

    fn adafactor_update_linear(weight : Array[Float], bias : Array[Float], d_weight : Array[Float], d_bias : Array[Float], w_state : Adafactor2DState, b_state : Adafactor1DState, lr : Float) -> Unit

    Convenience: full layer update (weight + bias) for a 2D Linear.

    adagrad_init

    fn adagrad_init(weight_len : Int, bias_len : Int) -> AdaGradState

    Allocate zero-initialised AdaGrad state.

    adagrad_update_arrays

    fn adagrad_update_arrays(weight : Array[Float], bias : Array[Float], d_weight : Array[Float], d_bias : Array[Float], state : AdaGradState, lr : Float, eps : Float) -> (Array[Float], Array[Float], AdaGradState)

    One AdaGrad update at raw-array level.

    adagrad_update_conv

    fn adagrad_update_conv(param : Conv2dParam, d_weight : Array[Float], d_bias : Array[Float], state : AdaGradState, lr : Float, eps : Float) -> (Conv2dParam, AdaGradState)

    AdaGrad wrapper for Conv2dParam.

    adagrad_update_linear

    fn adagrad_update_linear(param : LinearParam, d_weight : Array[Float], d_bias : Array[Float], state : AdaGradState, lr : Float, eps : Float) -> (LinearParam, AdaGradState)

    AdaGrad wrapper for LinearParam.

    adam_init

    fn adam_init(weight_len : Int, bias_len : Int) -> AdamState

    Allocate zero-initialised Adam state for a parameter of given weight_len / bias_len. Initial step = 0 (no bias correction yet).

    adam_next_step

    fn adam_next_step(state : AdamState) -> Int

    Pure accessor: returns state.step + 1. Used internally by adam_update_arrays and exposed for callers that want to inspect the next step counter without mutating the state.

    adam_update_arrays

    fn adam_update_arrays(weight : Array[Float], bias : Array[Float], d_weight : Array[Float], d_bias : Array[Float], state : AdamState, lr : Float, beta1 : Float, beta2 : Float, eps : Float) -> (Array[Float], Array[Float], AdamState)

    One Adam update step at raw-array level. Returns the updated (weight, bias) arrays plus a new AdamState with the incremented step counter. The input state is read-only; pass the returned state into the next call.

    adam_update_conv

    fn adam_update_conv(param : Conv2dParam, d_weight : Array[Float], d_bias : Array[Float], state : AdamState, lr : Float, beta1 : Float, beta2 : Float, eps : Float) -> (Conv2dParam, AdamState)

    Adam wrapper for Conv2dParam.

    adam_update_linear

    fn adam_update_linear(param : LinearParam, d_weight : Array[Float], d_bias : Array[Float], state : AdamState, lr : Float, beta1 : Float, beta2 : Float, eps : Float) -> (LinearParam, AdamState)

    Adam wrapper for LinearParam.

    adamw_init

    fn adamw_init(weight_len : Int, bias_len : Int) -> AdamWState

    Allocate zero-initialised AdamW state for a parameter of given weight_len / bias_len. step starts at 0 (bias correction uses t = state.step + 1).

    adamw_update_arrays

    fn adamw_update_arrays(weight : Array[Float], bias : Array[Float], d_weight : Array[Float], d_bias : Array[Float], state : AdamWState, lr : Float, beta1 : Float, beta2 : Float, eps : Float, weight_decay : Float) -> (Array[Float], Array[Float], AdamWState)

    One AdamW update at raw-array level. Returns the updated (weight, bias) arrays plus the new state with step incremented. weight_decay is applied additively to the parameter update (param <- param - lr*(...) + lr*weight_decay*param), matching the PyTorch optim.AdamW convention.

    adamw_update_conv

    fn adamw_update_conv(param : Conv2dParam, d_weight : Array[Float], d_bias : Array[Float], state : AdamWState, lr : Float, beta1 : Float, beta2 : Float, eps : Float, weight_decay : Float) -> (Conv2dParam, AdamWState)

    AdamW wrapper for Conv2dParam.

    adamw_update_linear

    fn adamw_update_linear(param : LinearParam, d_weight : Array[Float], d_bias : Array[Float], state : AdamWState, lr : Float, beta1 : Float, beta2 : Float, eps : Float, weight_decay : Float) -> (LinearParam, AdamWState)

    AdamW wrapper for LinearParam.

    add_backward

    fn add_backward(d_output : Array[Float]) -> (Array[Float], Array[Float])

    Elementwise backward. Returns two INDEPENDENT copies of d_output, one for each operand of add_forward. The caller adds each to the downstream parameter gradient as needed (or treats them as the final d_input for the respective operand).

    add_forward

    fn add_forward(a : Array[Float], b : Array[Float]) -> Array[Float]

    Elementwise forward: out[i] = a[i] + b[i]. Both inputs must have the same length.

    adex_connect

    fn adex_connect(c : SpikingSynapseAdEx, pre : Int, post : Int, w : Float) -> Unit

    adex_sim_for

    fn adex_sim_for(model : AdExModel, duration : Float) -> Unit

    adex_sinexp_step_synapses

    fn adex_sinexp_step_synapses(p : AdExSinExp, dt : Float) -> Unit

    Update the AdExSinExp synapse state for one step. Bit-exact port of Julia's update_synapses! for SingleExpSynapse: ge[i] += glu[i] gi[i] += gaba[i] ge[i] += dt * (-ge[i] / τe) gi[i] += dt * (-gi[i] / τi) glu[i] = 0 gaba[i] = 0

    adex_sinexp_synaptic_current

    fn adex_sinexp_synaptic_current(p : AdExSinExp) -> Unit

    Compute synaptic current: syn_curr = ge * (v - E_e) * gsyn_e + gi * (v - E_i) * gsyn_i Same formula as the DoubleExp AdEx variant — the synaptic dynamics differ, but the resulting current is identical.

    adex_step_synapses

    fn adex_step_synapses(p : AdEx, dt : Float) -> Unit

    Re-export the synapse helpers for AdEx.

    adex_synaptic_current

    fn adex_synaptic_current(p : AdEx) -> Unit

    aggregate_scaling_forward

    fn aggregate_scaling_forward(c : AggregateScaling, fire : Array[Float], dt : Float) -> Unit

    Forward step: updates the per-post-neuron homeostatic trace y and the target-sum WT. Called every simulation step before plasticity. Matches Julia's forward!(c, param):

    1. y[i] -= y[i] / tau_a (decay)
    2. if fire[i]: y[i] += 1 (bump on spike)
    3. WT[i] += (1 - WT[i]/Wmax) * (1 - y[i]/Y[i]) / tau_e (drive WT toward (1 - y/Y) so WT saturates at Wmax when y=Y)

    aggregate_scaling_plasticity

    fn aggregate_scaling_plasticity(c : AggregateScaling, step_count : Int) -> Unit

    Plasticity step: called every simulation step. If the periodic interval has elapsed, recompute the per-post-neuron weight sum wt[i], compute μ[i] = (WT[i] - Wmin) / wt[i], and rescale all weights targeting i: W[s] = W[s] * μ[i] + Wmin.

    Matches Julia's plasticity!(c, param, dt, T) (periodic variant) + plasticity!(c, param) (immediate variant).

    all_windows

    fn all_windows(data : Array[Double], ratio : Double, max_b : Int, v_range : Array[Double]) -> Array[Int]

    Count maxima per bandwidth h ∈ {1, 2, ..., max_b}. Mirrors Julia's all_windows(data, ratio, max_b) — returns an Array[Int] of length max_b where entry [h-1] is count_maxima(global_kde(h, data, v_range), ratio).

    alpha_synapse

    fn alpha_synapse(tau_r : Float, tau_d : Float) -> Float

    alpha_synapse(τr, τd) — the α factor used in the 2-state ODE h += α * target where target is the spike-input pulse. Julia formula: α = (τd - τr) / (τd * τr).

    ampere

    let ampere : Float

    any_n_neurons

    fn any_n_neurons(p : AnyPop) -> Int

    ar_fit_ols

    fn ar_fit_ols(y : Array[Float], p : Int) -> ArParam

    Fit AR(p) via OLS normal equations. Solves XᵀX · θ = Xᵀy via Gauss-Jordan elimination, where the design matrix X has columns [y[t-1], ..., y[t-p], 1] (last column = intercept).

    ar_fit_yule_walker

    fn ar_fit_yule_walker(y : Array[Float], p : Int) -> ArParam

    Fit AR(p) via the Yule-Walker equations. De-means the series, computes PACF[1..p], and uses those as the AR coefficients. The intercept is set to the series mean.

    ar_fitted

    fn ar_fitted(y : Array[Float], param : ArParam) -> Array[Float]

    Fitted values ŷ[t] = c + Σᵢ φᵢ · y[t-i] for t = p, p+1, ..., n-1. Returns an array of length n - p whose index k corresponds to source time t = p + k.

    ar_grad_coeffs

    fn ar_grad_coeffs(y : Array[Float], param : ArParam) -> Array[Float]

    Gradient of the residual loss w.r.t. each coefficient φᵢ (length p). From ∂/∂φᵢ Σₜ ε[t]² = -2 Σₜ ε[t] · y[t-i]. Used for sanity checking the OLS / Yule-Walker fits or for gradient-based fitting.

    ar_residual_loss

    fn ar_residual_loss(y : Array[Float], param : ArParam) -> Float

    Sum of squared residuals Σₜ ε[t]² — the AR(p) fitting objective.

    ar_residuals

    fn ar_residuals(y : Array[Float], param : ArParam) -> Array[Float]

    Residuals ε[t] = y[t] - ŷ[t] for t ≥ p. Length n - p.

    argmax

    fn argmax(scores : Array[Float]) -> Int

    Index of the maximum element in scores. First occurrence wins on ties. Returns 0 for an empty array.

    arima_fit

    fn arima_fit(y : Array[Float], p : Int, d : Int, q : Int, max_iter : Int, lr : Float) -> ArimaFit

    Fit ARIMA(p, d, q) by differencing y d times, fitting ARMA(p, q) on the differenced series via gradient descent, and returning the full parameter bundle. max_iter and lr control the GD fit.

    arima_forecast

    fn arima_forecast(y : Array[Float], fit : ArimaFit, n_ahead : Int) -> Array[Float]

    Forecast n_ahead steps into the future from a fitted ArimaFit on a series y. Returns an array of length n_ahead containing point forecasts for y[N+1], ..., y[N+n_ahead].

    arima_mae

    fn arima_mae(actual : Array[Float], predicted : Array[Float]) -> Float

    Mean absolute error between two equal-length arrays.

    arima_rmse

    fn arima_rmse(actual : Array[Float], predicted : Array[Float]) -> Float

    Root-mean-squared error between two equal-length arrays.

    arima_synthesize

    fn arima_synthesize(n : Int, p : Int, d : Int, q : Int, intercept : Float, ar_coeffs : Array[Float], ma_thetas : Array[Float], seed : UInt64) -> Array[Float]

    Synthetic ARIMA(p, d, q) series generator. Generate z via ARMA(p, q), then inverse-difference (cumulative sum + extend) d times to produce the integrated series y.

    arma_fit

    fn arma_fit(y : Array[Float], p : Int, q : Int, max_iter : Int, lr : Float) -> ArmaParam

    Fit ARMA(p, q) by gradient descent on the residual loss. Returns the converged ArmaParam. Simple fixed-step Adam without momentum.

    arma_grad

    fn arma_grad(y : Array[Float], param : ArmaParam) -> (Array[Float], Array[Float])

    Gradient of the residual loss w.r.t. both AR and MA coefficients. Returns (ar_grad, ma_grad) as a tuple. Lengths match ar_coeffs and ma_thetas.

    arma_loss_value

    fn arma_loss_value(y : Array[Float], intercept : Float, ar_coeffs : Array[Float], ma_thetas : Array[Float]) -> Float

    Forward-only ARMA loss used for finite-difference validation of arma_grad. Returns the residual loss for the given parameters.

    arma_residual_loss

    fn arma_residual_loss(y : Array[Float], param : ArmaParam) -> Float

    Sum of squared residuals Σ ε[t]² — the ARMA fitting objective.

    arma_residuals

    fn arma_residuals(y : Array[Float], param : ArmaParam) -> Array[Float]

    Compute innovations ε[t] for t = 0, 1, ..., n-1 using the ARMA recursion. Pre-sample y values and residuals are taken as 0.

    average_conn_strength

    fn average_conn_strength(m : Array[Array[Float]], pops : Array[PopIndex], mu : Float) -> Array[Array[Float]]

    average_conn_strength(M, pops, μ) — for each pair (i, j), compute the mean of the block M[pops[i].range, pops[j].range] divided by μ.

    M is the dense weight matrix (a CSR would also work via m.get(i, j) but the test uses dense). pops is the array of PopIndex produced by population_indices. μ is the per-synapse scale factor (the test divides by μ=1.0).

    Returns an (N_pops, N_pops) matrix where each entry is the mean block weight divided by μ. Empty blocks return 0.0F.

    average_weight

    fn average_weight(pre_pop_neurons : Array[Int], post_pop_neurons : Array[Int], synapse : SpikingSynapse) -> Float

    Mean of all synaptic weights whose pre-neuron ∈ pre_pop_neurons AND post-neuron ∈ post_pop_neurons. Returns 0.0F if the set is empty. Walks the CSR by pre-neuron (row-major).

    average_weight_dynamics

    fn average_weight_dynamics(pre_pop_neurons : Array[Int], post_pop_neurons : Array[Int], synapse : SpikingSynapse, record : Array[Float], n_steps : Int) -> Array[Float]

    Mean of all synapse weights in record[:, t] (a per-timestep weight record matrix of shape [n_connections, n_steps]) whose pre-neuron ∈ pre_pop_neurons AND post-neuron ∈ post_pop_neurons. Returns Array[Float] of length n_steps.

    record is structured as a flat row-major Array[Float] of length n_connections * n_steps (MoonBit doesn't support true 2D arrays); index via record_get(record, n_steps, s, t) for row s, col t.

    avgpool2d_backward

    fn avgpool2d_backward(d_output : Array[Float], n : Int, c : Int, h : Int, w : Int, param : AvgPool2dParam) -> Array[Float]

    Backward pass. Distributes the upstream gradient uniformly across all positions in each kernel window.

    avgpool2d_forward

    fn avgpool2d_forward(input : Array[Float], n : Int, c : Int, h : Int, w : Int, param : AvgPool2dParam) -> Array[Float]

    Forward pass.

    ballandstick_dend_step_synapses

    fn ballandstick_dend_step_synapses(p : BallAndStick, dt : Float) -> Unit

    Single-exp synapse step for the dendrite.

    ballandstick_param_new

    fn ballandstick_param_new() -> DendNeuronParameter

    BallAndStickParameter factory — 1 dendrite (default ds=(150μm, 400μm), human physiology, s→d).

    ballandstick_soma_step_synapses

    fn ballandstick_soma_step_synapses(p : BallAndStick, dt : Float) -> Unit

    Single-exp synapse step for the soma: ge += glu; ge decay. Same formula as AdExSinExp.

    batch_norm2d_backward

    fn batch_norm2d_backward(d_output : Array[Float], cache : BatchNormCache, bn : BatchNorm2d) -> (Array[Float], Array[Float], Array[Float])

    Backward pass. Given d_output (shape [n, c, h, w]) and the cache from forward, returns (d_input, d_gamma, d_beta). All shapes match the forward.

    batch_norm2d_forward

    fn batch_norm2d_forward(input : Array[Float], n : Int, c : Int, h : Int, w : Int, bn : BatchNorm2d) -> (Array[Float], BatchNormCache)

    Forward pass. bn may be in training mode (uses batch stats) or inference mode (uses running_mean / running_var). When training=true and bn.momentum > 0, the running stats are updated in-place.

    bilstm_backward

    fn bilstm_backward(hs_f : Array[Array[Float]], hs_b : Array[Array[Float]], target : Array[Array[Float]], cache : BiLstmCache, h0_f : Array[Float], c0_f : Array[Float], h0_b : Array[Float], c0_b : Array[Float], param : BiLstmParam) -> (Array[Array[Float]], Array[Float], Array[Float], Array[Float], Array[Float], LstmCellGrad, LstmCellGrad)

    BPTT backward for the BiLSTM. Returns (d_xs, d_h0_f, d_c0_f, d_h0_b, d_c0_b, grad_f, grad_b).

    Forward BPTT walks t = T-1, ..., 0 with the forward cache. Backward BPTT walks t = 0, ..., T-1 with the backward cache. d_xs[t] accumulates contributions from both directions.

    bilstm_concat

    fn bilstm_concat(hs_f : Array[Array[Float]], hs_b : Array[Array[Float]], t : Int) -> Array[Float]

    Build the concat hidden state [hs_f[t]; hs_b[t]] at time t.

    bilstm_forward

    fn bilstm_forward(xs : Array[Array[Float]], h0_f : Array[Float], c0_f : Array[Float], h0_b : Array[Float], c0_b : Array[Float], param : BiLstmParam) -> (Array[Array[Float]], Array[Array[Float]], Array[Array[Float]], Array[Array[Float]], BiLstmCache)

    Run the BiLSTM forward over an input sequence. Returns (hs_f, hs_b, cs_f, cs_b, cache). hs_f[t] and hs_b[t] are the per-direction hidden states (each length d_h).

    bilstm_identity_dataset

    fn bilstm_identity_dataset(seq_len : Int, d_x : Int, d_target : Int, seed : UInt64) -> (Array[Array[Float]], Array[Array[Float]])

    Synthetic identity-shift task for BiLSTM. xs has dim d_x; target has dim 2 * d_target (matching concat hidden dim).

    bilstm_sequence_loss

    fn bilstm_sequence_loss(hs_f : Array[Array[Float]], hs_b : Array[Array[Float]], target : Array[Array[Float]]) -> Float

    Mean-squared-error loss on concat hidden states vs target.

    bilstm_sgd_step

    fn bilstm_sgd_step(param : BiLstmParam, grad_f : LstmCellGrad, grad_b : LstmCellGrad, lr : Float) -> Unit

    Apply SGD to both directions.

    bilstm_train_n_steps

    fn bilstm_train_n_steps(xs : Array[Array[Float]], target : Array[Array[Float]], h0_f : Array[Float], c0_f : Array[Float], h0_b : Array[Float], c0_b : Array[Float], param : BiLstmParam, n_steps : Int, lr : Float, clip : Float) -> Float

    Run K-step SGD training on a single input/target sequence.

    box_muller

    fn box_muller(rng : Xoshiro) -> (Double, Double)

    Box-Muller transform: produce a pair of N(0, 1) Float64 from two uniform Float64 draws. Matches Julia's randn() which uses the same algorithm.

    c51_forward

    fn c51_forward(net : C51Net, state : Int) -> Array[Array[Float]]

    Forward: returns probs[a][i] (n_actions × n_atoms).

    c51_greedy_action

    fn c51_greedy_action(c51 : C51, state : Int) -> Int

    Greedy action for state s based on the target net. a* = argmax_a Σ_i p(s, a, i) · z_i.

    c51_net_logits

    fn c51_net_logits(net : C51Net, state : Int) -> Array[Array[Float]]

    Forward pass: returns logits[a][i] for the one-hot state s.

    c51_projected_target

    fn c51_projected_target(c51 : C51, reward : Float, next_state : Int, done : Bool) -> Array[Float]

    Compute the projected target distribution for one transition. Returns m[i] (n_atoms) — a probability distribution over atoms.

    The "greedy" next action is a* = argmax_a Σ_i p(s', a, i) · z_i (Double DQN-style online-net evaluation is not implemented here — we use the target net for both selection and evaluation, which is the original Bellemare 2017 formulation).

    c51_q_value

    fn c51_q_value(probs : Array[Float], support : Array[Float]) -> Float

    Recover Q(s, a) from the distribution.

    c51_soft_update

    fn c51_soft_update(c51 : C51, tau : Float) -> Unit

    Polyak soft update of target net from online net.

    c51_softmax_atoms

    fn c51_softmax_atoms(logits : Array[Float]) -> Array[Float]

    Softmax over the atom dimension (last axis).

    c51_support

    fn c51_support(v_min : Float, v_max : Float, n_atoms : Int) -> Array[Float]

    Build the support array (V_min + i · Δz).

    c51_update

    fn c51_update(c51 : C51, states : Array[Int], actions : Array[Int], rewards : Array[Float], next_states : Array[Int], dones : Array[Bool]) -> Float

    One C51 update step: cross-entropy loss + SGD on (s, a) logits.

    For each transition in the batch: m = projected_target(r, s', done) // n_atoms p = softmax(logits(s)[a]) // n_atoms L = -Σ_i m[i] · log p[i] // cross-entropy d_logits[a][i] = (p[i] - m[i]) // gradient of CE wrt logits

    Returns mean cross-entropy loss.

    c_mem

    fn c_mem(cd : Float, d : Float, l : Float) -> Float

    Bit-exact match for Julia's C_mem formula: C_mem = Cd * π * d * l in Float32. Cd is specific capacitance (pF/cm²). Output in pF.

    ca_plasticity_step

    fn ca_plasticity_step(w : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool], colptr : Array[Int], rowptr : Array[Int], vars : CaPlasticityVariables, param : CaPlasticityParameter, t_now : Float, dt : Float) -> Unit
    One step of CaPlasticityParameter (trace-based pair-spike STDP).

    Mirrors Julia's CaRule.jl plasticity! for STDPParameter:
    1. Weight update on pre-fire: W[s] += tpost[i] (for s = (j -> i) where fireJ[j])
    2. Weight update on post-fire: W[s] += tpre[j] (for s = (j -> i) where fireI[i])
    3. Trace decay: tpre[j] += dt * (-tpre[j]) / tau_pre tpost[i] += dt * (-tpost[i]) / tau_post
    4. Spike bumps: fireJ[j]: tpre[j] += A_pre fireI[i]: tpost[i] += A_post
    5. Clamp weights to [Wmin, Wmax].

    Note: Julia applies weight updates BEFORE the trace updates (so the update uses the previous step's trace values, which is the same as an exponential-decay integrator discretised at the start of the interval). We mirror that order exactly for bit-exact reproducibility against Julia's @inbounds @fastmath update sequence.

    CSR layout (same as stdp_step): rowptr[j]..rowptr[j+1] are the non-zero positions for row j; colptr[s] = i (post-neuron).

    causal_mask

    fn causal_mask(seq_len : Int) -> Array[Float]

    Build a causal mask of shape (seq_len, seq_len). Positions (i, j) with j > i are masked (set to -1e9); j <= i are unconstrained (set to 0).

    Returns row-major flat Array[Float] of length seq_len * seq_len.

    causal_mask_heads

    fn causal_mask_heads(seq_len : Int, num_heads : Int) -> Array[Float]

    Convenience: causal mask directly broadcast to (num_heads × seq_len × seq_len) for direct MHA consumption.

    chain_backward

    fn chain_backward(layers : Array[Layer], caches : Array[LayerCache], d_output : Array[Float], n : Int, c : Int, h : Int, w : Int) -> (Array[Float], Array[LayerGrad])

    Backward pass. Iterates the chain in reverse, threading d_output through each layer's backward and emitting per-layer parameter gradients. Returns (d_input, grads) where grads[i] corresponds to layers[i].

    chain_forward

    fn chain_forward(layers : Array[Layer], input : Array[Float], n : Int, c : Int, h : Int, w : Int) -> (Array[Float], Array[LayerCache], Int, Int, Int, Int)

    Forward pass over the chain. Returns the final output, one cache per layer (in forward order), and the final (n, c, h, w) shape.

    Note: after Flatten or Linear the chain internally treats the data as (n, k, 1, 1). Subsequent Conv2d / MaxPool2d / BN / LN layers will fail at runtime — this is a semantic topology error.

    classification_accuracy

    fn classification_accuracy(predicted : Array[Int], labels : Array[Int]) -> Float

    Classification accuracy: fraction of (predicted[i] == labels[i]). Returns 0.0F for empty input.
    let cm : Float

    cm2

    let cm2 : Float

    coincident_fraction

    fn coincident_fraction(a : Array[Float], b : Array[Float], dt : Float) -> Float

    Fraction of spikes in A that have a coincident spike in B within ±dt. Returns 0.0 if A is empty. B must be pre-sorted; A is iterated linearly and binary-searched in B (O(N_A log N_B)).

    compare_float_traces

    fn compare_float_traces(name : String, actual : Array[Float], expected : Array[Float], atol : Float, tol_ulps : Int) -> ParityResult

    Compare two float traces element-by-element. Both arrays must have the same length. Tolerance is in ULPs (tol_ulps) and absolute raw units (atol); passes iff every element is within both.

    compare_spike_trains

    fn compare_spike_trains(name : String, actual : Array[Int], expected : Array[Int], tol_steps : Int) -> ParityResult

    Compare two spike trains (arrays of spike-time step indices). Tolerance is tol_steps — each actual spike must match an expected spike within tol_steps of it. Returns the count of matched spikes and unmatched spikes (greedy nearest-neighbour matching).

    compose

    fn compose(pops : Array[AnyPop], conns : Array[SpikingSynapse], stims? : Array[AnyStim], monitors? : Array[Monitor], stdp? : Array[STDPEntryKind], stp? : Array[STPEntryKind]) -> HeterogeneousModel

    Construct a heterogeneous model from populations, connections, and (optionally) stimuli and monitors. Time starts at 0 with dt=0.125F.

    compute_returns

    fn compute_returns(rewards : Array[Float], gamma : Float) -> Array[Float]

    Monte-Carlo discounted returns G_t = Σ_{k >= t} γ^(k-t) · r_k.

    conductance_synapse_current

    fn conductance_synapse_current(vars : SingleExpSynapseVars, param : SingleExpSynapse, v : Array[Float], syn_curr : Array[Float]) -> Unit

    synaptic_current for SingleExpSynapse / DoubleExpSynapse — syn_curr = ge * (v - E_e) * gsyn_e + gi * (v - E_i) * gsyn_i.

    constant

    fn[A] constant(value : A) -> Loc[A]

    Wrap a literal value as a Loc::Const. Constants do not receive gradient in backward mode — they are not in the parameter list.

    conv2d_backward

    fn conv2d_backward(input : Array[Float], d_output : Array[Float], n : Int, c_in : Int, h : Int, w : Int, param : Conv2dParam) -> (Array[Float], Array[Float], Array[Float])

    Conv2d backward pass. Returns (d_input, d_weight, d_bias).

    conv2d_forward

    fn conv2d_forward(input : Array[Float], n : Int, c_in : Int, h : Int, w : Int, param : Conv2dParam) -> Array[Float]

    Forward pass for a 2D convolution.

    input : length = n * c_in * h * w returns : length = n * c_out * ho * wo

    Pad-region values are zero (i.e. zero-padded). Out-of-bounds reads contribute nothing.

    corridor_observation

    fn corridor_observation(env : CorridorEnv, position : Int) -> Array[Float]

    Build the observation vector at time t. Concat of (indicator, position_one_hot).

    count_by_target

    fn count_by_target(recs : ReceptorsByTarget) -> Int

    Count of receptors per target (helper for verification).

    count_gaba

    fn count_gaba(recs : ReceptorsByTarget) -> Int

    Number of receptors with target "gaba" (GABAa + GABAb combined).

    count_glu

    fn count_glu(recs : ReceptorsByTarget) -> Int

    Number of receptors with target "glu" (AMPA + NMDA combined).

    count_maxima

    fn count_maxima(kernel : Array[Double], ratio : Double) -> Int

    Count the number of "real" maxima in kernel above the ratio threshold. Mirrors Julia's count_maxima.

    count_nnz

    fn count_nnz(arr : Array[Float]) -> Int

    Count non-zero entries in a Float array.

    critical_window

    fn critical_window(data : Array[Double], ratio : Double, max_b : Int, v_range : Array[Double]) -> Int

    Find the critical window h* below which the KDE becomes bimodal. Searches h ∈ {1, 3, 5, ..., max_b} (odd integers). Returns the first h where is_bimodal(global_kde(h, data, v_range), ratio) is false (unimodal). Returns max_b if no such h exists.

    cross_entropy_loss

    fn cross_entropy_loss(log_probs : Tensor, targets : Tensor) -> CrossEntropyLoss

    Compute the cross-entropy loss given log-probabilities and integer target indices.

    cross_entropy_one

    fn cross_entropy_one(prob : Array[Float], target : Array[Float]) -> Float

    Cross-entropy for one row: loss = -sum(target * log(prob)).

    current_synapse_current

    fn current_synapse_current(vars : CurrentSynapseVars, syn_curr : Array[Float]) -> Unit

    synaptic_current for CurrentSynapse — syn_curr = -(ge - gi). Ge / gi decay via the current_synapse_step so this just computes the current.

    current_synapse_step

    fn current_synapse_step(vars : CurrentSynapseVars, param : CurrentSynapse, glu : Array[Float], gaba : Array[Float], dt : Float) -> Unit

    Update step for CurrentSynapseVars: spike-driven conductance update + exponential decay. Caller is responsible for clearing glu / gaba buffers afterwards.

    deliver_pending_compartment_ball

    fn deliver_pending_compartment_ball(c : CompartmentSynapseBall, t_now : Float) -> Unit

    Drain pending events for BallAndStick synapses whose delivery time has arrived.

    deliver_pending_compartment_tripod

    fn deliver_pending_compartment_tripod(c : CompartmentSynapseTripod, t_now : Float) -> Unit

    Tripod variant.

    deliver_pending_synapse

    fn deliver_pending_synapse(c : SpikingSynapse, t_now : Float) -> Unit

    Drain the pending event queue: apply weights for events whose delivery time has arrived (<= t_now). Removes them from the queue. Events are processed in insertion order; since the compose sim loop advances t_now monotonically and each step adds events at t_now + d for some d > 0, the queue is effectively FIFO when processed each step.

    delta_synapse_current

    fn delta_synapse_current(vars : DeltaSynapseVars, syn_curr : Array[Float]) -> Unit

    synaptic_current for DeltaSynapse — syn_curr = -(ge - gi), then zero ge / gi (instantaneous). Mirrors Julia's synaptic_current!(p, synapse::DeltaSynapse, synvars::DeltaSynapseVars).

    delta_synapse_step

    fn delta_synapse_step(vars : DeltaSynapseVars, glu : Array[Float], gaba : Array[Float]) -> Unit

    Update step for DeltaSynapseVars: instantaneous spike-driven conductance — ge[i] += glu[i]; gi[i] += gaba[i]. No decay. Caller is responsible for clearing glu / gaba buffers afterwards.

    demo_ma1_loss

    fn demo_ma1_loss(y : Array[Float], theta_init : Float) -> Float

    Demo 3: MA(1) residual-loss gradient w.r.t. θ (the moving-average coefficient). Given a series y and an initial θ value, build the tape computing ε[0] = y[0]; ε[t] = y[t] - θ · ε[t-1] for t ≥ 1; loss = Σ ε[t]². Returns dloss/dθ.

    Why this matters: the MA backward in classical ARIMA is hard to derive by hand because every residual depends on the previous one, which depends on θ. With Tape, each residual is just a mul
    • sub and the chain rule propagates correctly through the loop.

    demo_polynomial

    fn demo_polynomial(x_val : Float) -> Float

    Demo 1: Polynomial f(x) = x³ - 2x + 1. Returns the gradient df/dx at x_val. Closed-form: df/dx = 3x² - 2.

    demo_rosenbrock

    fn demo_rosenbrock(x_val : Float, y_val : Float) -> (Float, Float)

    Demo 2: Rosenbrock function f(x, y) = (a - x)² + b · (y - x²)². Returns (df/dx, df/dy) at the given (x_val, y_val). Classic test for non-linear optimisation — narrow curved valley.

    dendrite_step

    fn dendrite_step(d : Dendrite, v : Array[Float], v_parent : Array[Float], i_ext : Array[Float]) -> Array[Float]

    Compute the dendritic voltage change for one forward-Euler step. dv[i] = ((El[i] - v[i]) * gm[i] + (v_parent[i] - v[i]) * gax[i] + i_ext[i]) / C[i] where i_ext is any external current injected into the dendrite (synaptic currents, etc.) added by the caller.

    This returns Δv per neuron (not v itself). The caller adds dt * Δv[i] to v_d[i] and handles parent axial currents.

    double_dqn_update_step

    fn double_dqn_update_step(online : LinearQNet, target : LinearQNet, states : Array[Int], actions : Array[Int], rewards : Array[Float], next_states : Array[Int], dones : Array[Bool], gamma : Float, lr : Float) -> Float

    Double DQN update step. Same signature as dqn_update_step but uses the online net for argmax selection.

    Returns mean |δ| for monitoring.

    double_dqn_vs_vanilla_diff

    fn double_dqn_vs_vanilla_diff(online : LinearQNet, target : LinearQNet, next_states : Array[Int], gamma : Float) -> Float

    Compute the absolute difference between Double DQN and vanilla DQN target values for a batch. Useful as a sanity check that the decoupled rule produces different gradients than the coupled rule (which it should whenever online != target).

    double_exp_conductance_current

    fn double_exp_conductance_current(vars : DoubleExpSynapseVars, param : DoubleExpSynapse, v : Array[Float], syn_curr : Array[Float]) -> Unit

    double_exp_synapse_current

    fn double_exp_synapse_current(vars : DoubleExpSynapseVars, param : DoubleExpSynapse, v : Array[Float]) -> Array[Float]

    Return-style double_exp_synapse_current. Same formula as double_exp_conductance_current, but allocates and returns a fresh array. Useful for tests and one-shot callers.

    double_exp_synapse_step

    fn double_exp_synapse_step(vars : DoubleExpSynapseVars, param : DoubleExpSynapse, glu : Array[Float], gaba : Array[Float], dt : Float) -> Unit

    Update step for DoubleExpSynapseVars: spike-driven conductance update + rise/decay exponentials. Mirrors Julia's 2-state Euler in SNNModels.jl/src/.../synapses/DoubleExpSynapse.jl: ge += dt * (he - ge) / tau_de gi += dt * (hi - gi) / tau_di he += dt * (-he) / tau_re + glu # impulse on rise-var hi += dt * (-hi) / tau_ri + gaba # impulse on rise-var Caller is responsible for clearing glu / gaba buffers afterwards.

    dqn_update_step

    fn dqn_update_step(online : LinearQNet, target : LinearQNet, states : Array[Int], actions : Array[Int], rewards : Array[Float], next_states : Array[Int], dones : Array[Bool], gamma : Float, lr : Float) -> Float

    Compute the mean squared TD error over a batch, and apply a gradient step to online to minimise it.

    Loss per transition: δ² = (r + γ · max_a' Q̂(s', a'; θ_target) · (1 - done) - Q(s, a; θ_online))²

    Gradient w.r.t. W[i, j] (for the chosen action a): d_W[a, j] -= lr · 2 · δ · x_s[j] d_b[a] -= lr · 2 · δ

    For unchosen actions: zero gradient. Returns mean |δ| (avg absolute TD error) for monitoring.

    dqn_update_step_n_step

    fn dqn_update_step_n_step(online : LinearQNet, target : LinearQNet, states : Array[Int], actions : Array[Int], rewards : Array[Float], next_states : Array[Int], h_effs : Array[Int], use_bootstraps : Array[Bool], gamma : Float, lr : Float) -> Float

    N-step variant of dqn_update_step. Each row in the batch is an n-step transition:

    (s_root, a_root, G_n, s_lookahead, h_eff, use_bootstrap)

    where:

    • G_n — discounted reward sum Σ γ^k r_{root+k}
    • s_lookahead — the s_{root + h_eff} used for bootstrap
    • h_eff — effective horizon (number of rewards summed, 1..=n)
    • use_bootstrap — true if h_eff == n and the n-step window is non-terminal

    Loss for a row (with bootstrap):

    δ = G_n + γ^h_eff · max_a' Q̂(s_lookahead, a') - Q(s_root, a_root)

    Without bootstrap (terminal reached inside window):

    δ = G_n - Q(s_root, a_root)

    Returns mean |δ| over the batch.

    dropout_backward

    fn dropout_backward(d_output : Array[Float], mask : Array[Float], d : Dropout) -> Array[Float]

    Backward pass. mask must be the same mask returned by dropout_forward. d_output is the upstream gradient.

    dropout_forward

    fn dropout_forward(input : Array[Float], d : Dropout, rng : Xoshiro) -> (Array[Float], Array[Float])

    Forward pass. Returns (out, mask) where mask is the per-element Bernoulli mask (length n). Use mask in the corresponding backward pass to recover the exact same scaling.

    dsac_critic_update

    fn dsac_critic_update(agent : DSac, states : Array[Int], actions : Array[Int], targets : Array[Float], lr : Float) -> Float

    Single critic update step: minimise Σ_i NLL(target_i | μ_i, σ_i) over the batch. Gradient on μ and σ (via log-σ parameterisation) is computed by a closed-form delta on the chosen action and zero on the others.

    For simplicity, we apply the gradient only to the chosen action's μ and log-σ. Returns mean NLL for monitoring.

    dsac_sample_action

    fn dsac_sample_action(agent : DSac, state : Int, rng : Xoshiro) -> Int

    Sample action from the policy (uses sac_sample_action).

    dsac_soft_target

    fn dsac_soft_target(q1_target : LinearGaussianQNet, q2_target : LinearGaussianQNet, policy : LinearSoftmaxPolicy, alpha : Float, next_state : Int, reward : Float, done : Bool, gamma : Float, rng : Xoshiro) -> Float

    Compute the DSAC soft-Bellman target scalar for a transition. Returns a single sample from the target distribution (a Gaussian centred on the SAC soft-target of the min-of-two critics with a stochastic perturbation).

    dsac_soft_update

    fn dsac_soft_update(agent : DSac, tau : Float) -> Unit

    Soft (Polyak) target update: target ← (1 - τ) · target + τ · online.

    duarte_glu_soma

    fn duarte_glu_soma() -> Receptor

    Duarte Glu soma receptor: E_rev=0, τr=0.26, τd=2.0, g0=0.73, target="glu". Matches Julia DuarteGluSoma.

    dueling_dqn_update_step

    fn dueling_dqn_update_step(online : DuelingQNet, target : DuelingQNet, states : Array[Int], actions : Array[Int], rewards : Array[Float], next_states : Array[Int], dones : Array[Bool], gamma : Float, lr : Float) -> Float

    Compute the analytic gradient of the squared TD error for the Dueling architecture. For each (s, a) pair:

    dueling_eps_greedy_action

    fn dueling_eps_greedy_action(q_net : DuelingQNet, state : Int, epsilon : Float, rng : Xoshiro) -> Int

    ε-greedy action using dueling Q-net.

    dueling_q_argmax

    fn dueling_q_argmax(q : Array[Float]) -> Int

    Greedy action (argmax over Q).

    dueling_q_forward

    fn dueling_q_forward(net : DuelingQNet, x : Array[Float]) -> Array[Float]

    Forward: Q(s, a) = V(s) + A(s, a) - mean_a A(s, a).

    dueling_qnet_copy

    fn dueling_qnet_copy(dst : DuelingQNet, src : DuelingQNet) -> Unit

    Copy weights from src into dst.

    dump_weights

    fn dump_weights(synapse : SpikingSynapse) -> Array[Float]

    Flat copy of all non-zero weights (parallel to matrix.vals). Length == nnz. Useful for checksum-style parity vs Julia.

    dump_weights_count

    fn dump_weights_count(synapse : SpikingSynapse) -> Int

    Number of stored non-zero weights (= matrix.nnz).

    dump_weights_dense

    fn dump_weights_dense(synapse : SpikingSynapse) -> Array[Array[Float]]

    Convert CSR → dense n_pre × n_post matrix (zero-fills missing edges). Returns an Array[Array[Float]] of length n_pre where each row has length n_post. Useful for cross-validation against Julia's dense connection representation.

    dump_weights_min_max

    fn dump_weights_min_max(synapse : SpikingSynapse) -> (Float, Float)

    Min and max weight over all non-zero edges. Returns (0.0F, 0.0F) for an empty matrix (caller must check nnz first).

    dump_weights_sum

    fn dump_weights_sum(synapse : SpikingSynapse) -> Float

    Sum of all weights. Returns 0.0F for an empty matrix.

    dump_weights_to_csv

    fn dump_weights_to_csv(synapse : SpikingSynapse) -> String

    CSV string of all non-zero weights in (pre, post, weight) form. Lines are: pre,post,weight. No header. Empty matrix → empty string. Useful for offline inspection / Python interop.

    dump_weights_with_indices

    fn dump_weights_with_indices(synapse : SpikingSynapse) -> Array[(Int, Int, Float)]

    List of (pre_idx, post_idx, weight) triples for all non-zero edges. Pre_idx is found by binary-searching matrix.rowptr for the largest k such that rowptr[k] <= s. Iteration order matches matrix.vals.

    end_interval

    fn end_interval(x : Float, intervals : Array[Array[Float]]) -> Float

    Returns the end of the interval containing x, or -1.0F if none. (Convenience counterpart to start_interval — useful in attack_decay functions from inputs.jl.)

    episode_return

    fn episode_return(episode : Episode) -> Float

    Total undiscounted return of an episode.

    eps_greedy_action

    fn eps_greedy_action(q_net : LinearQNet, state : Int, epsilon : Float, rng : Xoshiro) -> Int

    ε-greedy action. With probability ε, sample uniformly; else greedy.

    epsp_normalise

    fn epsp_normalise(v : Array[Float], n_compartments : Int, compartment : Int, t_start : Int, t_end : Int) -> Array[Float]

    Z-score normalisation: returns the standard score (v - mean) / std for the voltage trace v[compartment, t_start:t_end]. Useful for comparing EPSP magnitudes across compartments with different baseline voltages.

    epsp_normalise_by_compartment

    fn epsp_normalise_by_compartment(v : Array[Float], n_compartments : Int, t_start : Int, t_end : Int) -> Array[Array[Float]]

    Normalise ALL compartments at once over the time window [t_start, t_end]. Returns Array[Array[Float]] of length n_compartments, where each inner array is [length window] z-scores. Useful for computing relative EPSP magnitudes across the soma and all dendrites simultaneously.

    epsp_normalise_windowed_by_compartment

    fn epsp_normalise_windowed_by_compartment(v : Array[Float], n_compartments : Int, t_start : Int, t_end : Int, win_start : Int, win_end : Int) -> Array[Array[Float]]

    epsp_normalise_windowed_by_compartment(v, n_compartments, t_start,t_end, win_start, win_end) — like epsp_normalise_by_compartment but uses only the [win_start, win_end] sub-window for the per-compartment mean/std computation, while still returning z-scores over the full [t_start, t_end] range. Useful when the baseline window (e.g. before a spike) differs from the observation window (e.g. after the spike).

    epsp_pair

    fn epsp_pair(v : Array[Float], n_compartments : Int, spiketime : Int, rest : Float, compartment : Int) -> (Float, Float)

    epsp_pair(v, n_compartments, spiketime, rest, compartment) — returns (exc_peak, inh_trough) in one pass. Walks the trace once and tracks max + min simultaneously. Useful when both excitatory and inhibitory responses are expected (e.g. EI-balanced stimuli).

    epsp_pair_window

    fn epsp_pair_window(v : Array[Float], n_compartments : Int, t_start : Int, t_end : Int, rest : Float, compartment : Int) -> (Float, Float)

    epsp_pair_window(v, n_compartments, t_start, t_end, rest, compartment) — (exc_peak, inh_trough) within [t_start, t_end].

    erff

    fn erff(x : Float) -> Float

    Float32 error function: matches Julia's erf(Float32, x) and the C erff function. Used by sparse-GELU activation (x * Φ(x)) and other cumulative-Gaussian activations.

    eval_noisy_dqn

    fn eval_noisy_dqn(env : GridWorld, online : NoisyQNet, n_episodes : Int, max_steps : Int) -> Float

    Evaluate a trained NoisyQNet over n_episodes greedy episodes (μ weights, no noise). Returns mean episode return.

    exc_peak

    fn exc_peak(v : Array[Float], n_compartments : Int, spiketime : Int, rest : Float, compartment : Int) -> Float

    exc_peak(v, n_compartments, spiketime, rest, compartment) — returns max(v[compartment, spiketime:end]) - rest. Mirrors Julia's get_EPSP(...; inh=false) for the excitatory case.

    exc_peak_window

    fn exc_peak_window(v : Array[Float], n_compartments : Int, t_start : Int, t_end : Int, rest : Float, compartment : Int) -> Float

    exc_peak_window(v, n_compartments, t_start, t_end, rest, compartment) — peak deviation within a closed interval [t_start, t_end]. Mirrors Julia's get_EPSP(..., interval=...) overload.

    exc_peak_with_time

    fn exc_peak_with_time(v : Array[Float], n_compartments : Int, spiketime : Int, rest : Float, compartment : Int) -> (Float, Int)

    exc_peak_with_time(v, n_compartments, spiketime, rest, compartment) — returns (peak, time_of_peak) where peak is the voltage deviation (max - rest) and time_of_peak is the 0-based timestep at which the maximum occurred. Mirrors Julia's get_EPSP extended output.

    exp256

    fn exp256(x : Float) -> Float

    Fast approximation of exp(x) using 256 squaring iterations. Clamps x to [-10, +∞).

    exp32

    fn exp32(x : Float) -> Float

    Fast approximation of exp(x) using 32 squaring iterations. Clamps x to [-10, +∞).

    exp64

    fn exp64(x : Float) -> Float

    Fast approximation of exp(x) using 64 squaring iterations. Clamps x to [-10, +∞).

    expf

    fn expf(x : Float) -> Float

    Float32 exponential: matches Julia's exp(Float32, x) and the C expf function. Used by AdEx (exponential spike-initiation term) and other biophysical models.

    eyal_equivalent_nar

    fn eyal_equivalent_nar(nar : Float, tau_d : Float) -> Receptors

    EyalEquivalentNAR(NAR, τd) — build a Receptors collection for the Quaresima 2024 model with NMDA-to-AMPA ratio NAR. Julia reference from quaresima_2024_updown.jl: ampa_g0 = 0.73 * (1 + NAR0 - NAR), where NAR0 = 1.31/0.73 ≈ 1.795 nmda_g0 = 0.73 * NAR Receptors = (EyalGluNAR(NAR, τd), MilesGabaDend)

    We re-use the existing eyal_glu_dend() and miles_gaba_dend() helpers from receptor_types.mbt (these match the Julia defaults for the MilesGabaDend side); the AMPA/NMDA g0 values are scaled per NAR.

    eyal_glu_dend

    fn eyal_glu_dend() -> Glutamatergic

    Eyal Glu dendrite (Glutamatergic bundle): AMPA(0.0, 0.26, 2.0, 0.73)
    • NMDA(0.0, 8, 35, 1.31). Matches Julia EyalGluDend.

    eyal_nmda

    fn eyal_nmda() -> NMDAVoltageDependency

    Eyal NMDA voltage-dependency preset. mg = 1.0 (1mM normalised), b = 3.36, k = -0.077. Matches Julia EyalNMDA. (Functionally a no-op alias for the existing NMDAVoltageDependency::eyal().)

    f2l

    fn f2l(s : String, l : Int) -> String

    Format s to exactly l characters by right-padding with spaces (if s.length() < l) or truncating (if s.length() > l). Mirrors Julia's f2l(s, l=10).

    f2l_default

    fn f2l_default(s : String) -> String

    Convenience overload: f2l(s) with default length 10.

    f_sign_sqrt

    fn f_sign_sqrt(x : Float) -> Float

    Factorised-noise reparameterisation: f(x) = sign(x) · sqrt(|x|). Reduces CPU cost vs direct Gaussian sampling (paper §3.1).

    fano_factor

    fn fano_factor(per_neuron_counts : Array[Int]) -> Float

    Fano factor over an array of per-neuron spike counts: var / mean. Returns 0.0F for a population with ≤1 neurons or all-zero counts.

    farad

    let farad : Float

    fast_sigmoid_forward

    fn fast_sigmoid_forward(x : Float, beta : Float) -> Float

    Fast-sigmoid soft forward (Zenke & Ganguli 2018, eq. 3).

    s(x; β) = x / (1 + β·|x|)

    Range: (-1/β, 1/β). At x=0 returns 0; saturates to ±1/β at infinity. Used as a differentiable proxy for Heaviside when a soft spike signal is needed (e.g. attention, soft-decoding). For the hard (0/1) spike used in the IF neuron, use heaviside_step below.

    fast_sigmoid_forward_array

    fn fast_sigmoid_forward_array(u_minus_vt : Array[Float], beta : Float) -> Array[Float]

    Elementwise fast-sigmoid soft forward applied to u - vt.

    Returns a fresh Array[Float] of the same length as u_minus_vt where element i is (u[i] - vt) / (1 + β·|u[i] - vt|).

    fast_sigmoid_surrogate

    fn fast_sigmoid_surrogate(x : Float, beta : Float) -> Float

    Fast-sigmoid surrogate scalar (Zenke & Ganguli 2018, eq. 4).

    σ'(x; β) = 1 / (1 + β·|x|)²

    • σ'(0; β) = 1 (peak)
    • σ'(x; β) = σ'(-x; β) (even)
    • σ'(x; β) → 0 as |x| → ∞

    fast_sigmoid_surrogate_array

    fn fast_sigmoid_surrogate_array(u_minus_vt : Array[Float], beta : Float) -> Array[Float]

    Elementwise fast-sigmoid surrogate applied to u - vt.

    Returns a fresh Array[Float] of the same length as u_minus_vt where element i is 1 / (1 + β·|u[i] - vt|)².

    This is the standard backward pass for BPTT through an IF neuron: for each neuron, the gradient flowing back through the spike emission is multiplied by σ'(u[i] - vt).

    filter_items

    fn filter_items(pops : Array[(String, Int)]) -> Array[(String, Int)]

    Filter a list of (label, neuron_count) pairs by the default rule: remove any population whose label starts with "noise".

    filter_items_with

    fn filter_items_with(pops : Array[(String, Int)], rule : FilterRule) -> Array[(String, Int)]

    Apply a filter rule to a list of (label, neuron_count) pairs.

    find_pre_for_conn

    fn find_pre_for_conn(rowptr : Array[Int], s : Int) -> Int

    Binary search rowptr to find which pre-synaptic neuron owns connection index s (i.e., the largest j with rowptr[j] ≤ s).

    finite_diff

    fn finite_diff(f : (Float) -> Float, x : Float, h : Float) -> Float

    Central-difference finite-difference gradient. Used as the reference in tests to validate Tape-computed gradients.

    firing_rate_cv

    fn firing_rate_cv(per_neuron_spikes : Array[Array[Float]], t_start : Float, t_end : Float) -> Float

    Coefficient of variation (CV) of per-neuron firing rates across the population. CV = std(rates) / mean(rates). Returns 0.0F if mean is zero or population is empty.

    firing_rate_dynamics

    fn firing_rate_dynamics(per_neuron_spikes : Array[Array[Float]], window : Float, dt : Float) -> Array[Float]

    Sliding-window mean firing rate (Hz). Returns an array of length n_steps where each entry is the mean rate over the window [i*dt, i*dt + window]. window is in ms.

    flatten_backward

    fn flatten_backward(d_output : Array[Float], n : Int, c : Int, h : Int, w : Int) -> Array[Float]

    Flatten backward pass. d_output is the upstream gradient with length n * c * h * w (the flattened shape). Returns a fresh d_input of the same length.

    flatten_forward

    fn flatten_forward(input : Array[Float], n : Int, c : Int, h : Int, w : Int) -> Array[Float]

    Flatten NCHW [n, c, h, w] to (n, c*h*w). The output array is a COPY of the input (so the caller can mutate it freely).

    float32_close

    fn float32_close(a : Float, b : Float, tol_ulps : Int) -> Bool

    Compare two Float32 values with a ULP tolerance. Returns true if the distance in ULPs is ≤ tol_ulps.

    float32_ulp_distance

    fn float32_ulp_distance(a : Float, b : Float) -> Int

    floats_to_int_keys

    fn floats_to_int_keys(data : Array[Float]) -> Array[Int]

    Quantise a Float array to Int by scaling by 1e6 and truncating.

    fnv1a_32

    fn fnv1a_32(data : Array[Int]) -> Int

    FNV-1a 32-bit hash over an Int array.

    forward_adex_synapse

    fn forward_adex_synapse(c : SpikingSynapseAdEx) -> Unit

    forward_compartment_ball

    fn forward_compartment_ball(c : CompartmentSynapseBall, t_now : Float) -> Unit

    Forward a single time-step's pre-synaptic spikes through the BallAndStick-targeted compartment synapse. For each (pre-fire, outgoing-conn) pair, add w (× ρ if STP is active) to the post-synaptic compartment's glu (for :ge) or gaba (for :gi).

    If delays is non-empty, spikes are scheduled for delivery at t_now + delays[s] instead of immediate application. Use deliver_pending_compartment_ball to drain them.

    forward_compartment_tripod

    fn forward_compartment_tripod(c : CompartmentSynapseTripod, t_now : Float) -> Unit

    Tripod variant of forward_compartment_ball.

    forward_hh_synapse

    fn forward_hh_synapse(c : SpikingSynapseHH) -> Unit

    Forward: for each pre-synaptic neuron that fires, add w to the post-synaptic neuron's ge (or gi if sym is "gi").

    forward_iz_synapse

    fn forward_iz_synapse(c : SpikingSynapseIZ) -> Unit

    Forward: for each pre-synaptic neuron that fires, add w to the post-synaptic neuron's ge (or gi if sym is "gi").

    forward_pinning_synapse

    fn forward_pinning_synapse(c : PINningSparseSynapse) -> Unit

    Forward: for each pre j, walk rowptr[j]..rowptr[j+1] and update q[colptr[s]] += P[s] * rJ[j] g[colptr[s]] += W[s] * rJ[j] This is the rate-mode analogue of forward_rate_synapse. Resets q and g to zero first (matches Julia's fill!(q, zero) and fill!(g, zero) then update).

    forward_rate_synapse

    fn forward_rate_synapse(c : RateSynapse) -> Unit

    Forward: g[post[i]] += W[i] * rJ[pre[j]] for each connection (j → i).

    forward_receptor_synapse

    fn forward_receptor_synapse(s : ReceptorSynapse) -> Unit

    Forward pre-synaptic spikes into the per-edge glu / gaba buffers. For each pre j that fires, walk its outgoing edges; for each edge (j, k) with target receptor r:
    • if r is in glu_receptors, add weight to glu[k]
    • if r is in gaba_receptors, add weight to gaba[k]
    • else, drop the weight (no routing configured for this receptor)

    forward_receptor_tripod_synapse

    fn forward_receptor_tripod_synapse(s : ReceptorSynapseTripod, target_receptor : Int, t_now : Float) -> Unit

    Forward pre-synaptic spikes through the receptor synapse. When pre[j] fires, each (j → post[k]) edge contributes weight to the appropriate input buffer (glu or gaba) for the target compartment.

    target_receptor[s] = 0/1/2/3 selects which receptor of the 4 to feed. For simplicity in this version, all connections use the same target_receptor (passed at forward time as a single Int). For heterogeneous per-edge targeting, use the dedicated constructor that takes a per-row Array[Int] — TODO.

    forward_synapse

    fn forward_synapse(c : SpikingSynapse, t_now : Float) -> Unit

    Forward a single time-step's spikes through the synapse. For each pre-synaptic neuron that fired, add w to the post-synaptic neuron's glu (for :ge) or gaba (for :gi) field.

    If the synapse has non-empty delays, each spike is scheduled for delivery at t_now + delays[s] instead of being applied immediately. Use deliver_pending_synapse to drain pending events when their delivery time arrives.

    If the synapse has non-empty rho, each spike is scaled by rho[s] (per-connection weight modifier, used by Markram STP).

    g_axial

    fn g_axial(ri : Float, d : Float, l : Float) -> Float

    Bit-exact match for Julia's G_axial formula: G_axial = π * d² / (4 * Ri * l) in Float32. Ri is axial resistivity (Ω·cm). Output is in nS. Default Ri for human dendrite: 200 Ω·cm (human_dend in Julia).

    g_mem

    fn g_mem(rd : Float, d : Float, l : Float) -> Float

    Bit-exact match for Julia's G_mem formula: G_mem = l * d * π / Rd in Float32. Rd is membrane specific resistance (Ω·cm²). Output in nS.

    gae_advantages_returns

    fn gae_advantages_returns(episode : PpoGaeEpisode, gamma : Float, gae_lambda : Float) -> (Array[Float], Array[Float])

    Compute GAE advantages per transition in an episode, and the TD(0)-style returns R_t (for value-target training). Returns a parallel array of (advantage, return) per step.

    gaussian_nll

    fn gaussian_nll(y : Float, mu : Float, sigma : Float) -> Float

    Compute Gaussian NLL: -log p(y | μ, σ) = 0.5·((y-μ)/σ)² + log σ + 0.5·log(2π). Returns a scalar Float.

    gaussian_q_forward

    fn gaussian_q_forward(net : LinearGaussianQNet, x : Array[Float]) -> (Array[Float], Array[Float])

    Forward pass: returns (mus, sigmas) where mus[a] = μ(s, a), sigmas[a] = σ(s, a).

    gaussian_qnet_copy

    fn gaussian_qnet_copy(dst : LinearGaussianQNet, src : LinearGaussianQNet) -> Unit

    Copy weights from src to dst (all four: w_mu, b_mu, w_sigma, b_sigma).

    gaussian_smooth

    fn gaussian_smooth(xs : Array[Float], x : Array[Float], sigma : Float, skew : SmoothSkew) -> Array[Float]

    Apply a normalised Gaussian kernel to x sampled on xs.

    xs : position grid (Float[]); only step size xs[1]-xs[0] is used x : signal to smooth (Float[], length n) sigma : kernel std-dev in same units as xs skew : SmoothSkew::None / Left / Right

    Returns Float[] of length n. Boundary bins are renormalised by accumulated kernel weight so edge values aren't biased to zero.

    gelu

    fn gelu(x : Float) -> Float

    GELU scalar (tanh approximation).

    gelu(0) = 0 exactly. gelu(x) ≈ x for large positive x, gelu(x) ≈ 0 for large negative x.

    gelu_backward

    fn gelu_backward(input : Array[Float], d_output : Array[Float]) -> Array[Float]

    GELU backward pass. input is the original forward input; d_output is the upstream gradient. Returns d_input (new array).

    gelu_forward

    fn gelu_forward(input : Array[Float]) -> Array[Float]

    GELU element-wise forward. Returns a new array (does not mutate).

    gelu_grad

    fn gelu_grad(x : Float) -> Float

    GELU scalar derivative dgelu/dx (tanh approximation).

    gelu_sparse

    fn gelu_sparse(x : Float) -> Float

    Exact GELU scalar via x · Φ(x).

    gelu_sparse_backward

    fn gelu_sparse_backward(input : Array[Float], d_output : Array[Float]) -> Array[Float]

    Exact GELU backward pass.

    gelu_sparse_forward

    fn gelu_sparse_forward(input : Array[Float]) -> Array[Float]

    Exact GELU element-wise forward. Returns a new array (no mutation).

    gelu_sparse_grad

    fn gelu_sparse_grad(x : Float) -> Float

    Exact GELU scalar derivative.

    dgelu/dx = Φ(x) + x · φ(x) = 0.5·(1 + erff(x/√2)) + x · (1/√(2π)) · expf(-x²/2)

    gerstner_kernel

    fn gerstner_kernel(dt : Float, tau_pre : Float, tau_post : Float, a_pre : Float, a_post : Float) -> Float

    Compute the Gerstner STDP kernel value ΔW at a given Δt = t_post - t_pre.

    Convention: Δt > 0 means post fires after pre (LTP, positive ΔW). Δt < 0 means post fires before pre (LTD, negative ΔW).

    Kernel formula: For Δt > 0: ΔW = A_pre * exp(-Δt / τ_pre) (potentiation) For Δt < 0: ΔW = -A_post * exp(Δt / τ_post) (depression) For Δt = 0: ΔW = 0

    Returns 0 if |Δt| is very large (beyond ~5 τ in either direction). a_pre is the LTP amplitude, a_post is the LTD amplitude (positive values representing magnitude).

    get_dt

    fn get_dt(t : Time) -> Float

    Get the integration step size dt in ms. Matches get_dt.

    get_maxima

    fn get_maxima(data : Array[Double]) -> Array[Int]

    Find indices of local maxima in data (strict: data[i] > data[i-1] AND data[i] > data[i+1]). Excludes endpoints. Returns Array[Int] of 0-based indices into data.

    get_poisson_spikes

    fn get_poisson_spikes(rate : Float, dt : Float, rng : Xoshiro) -> Float

    Bernoulli spike sample: returns 1.0F with probability rate * dt, otherwise 0.0F. Mirrors Julia get_poisson_spikes(; rate, dt). rate is in kHz; dt is in ms (so ratedt is dimensionless). For a kHz rate × ms dt, ratedt ∈ [0, 1].

    get_synapse_symbol

    fn get_synapse_symbol(sym : String) -> String

    Map a synaptic receptor symbol to its canonical domain. "glu" → "glu" "gaba" → "gaba" "he" / "ge" → "glu" "hi" / "gi" → "gaba" anything else → passthrough

    get_time

    fn get_time(t : Time) -> Float

    Get the current simulation time in ms. Matches Julia's get_time(T).

    get_time_heterogeneous

    fn get_time_heterogeneous(m : HeterogeneousModel) -> Float

    Return the current simulation time (ms) of the model.

    get_tt

    fn get_tt(t : Time) -> Int

    Get the current integration timestep counter. Matches get_tt.

    global_avg_pool2d_backward

    fn global_avg_pool2d_backward(d_output : Array[Float], n : Int, c : Int, h : Int, w : Int) -> Array[Float]

    Backward for Global Average Pooling. Distributes the upstream gradient uniformly across all (h, w) positions in each channel.

    global_avg_pool2d_forward

    fn global_avg_pool2d_forward(input : Array[Float], n : Int, c : Int, h : Int, w : Int) -> Array[Float]

    Global Average Pooling. Pools the entire spatial dimension to a single value per (n, c). Result shape: [n, c, 1, 1].

    global_kde

    fn global_kde(h : Double, data : Array[Double], v_range : Array[Double]) -> Array[Double]

    Compute the global KDE curve over v_range for a given h. Returns Array[Double] of length v_range.length(). Float64 throughout (Julia's Float64 reference).

    gohm

    let gohm : Float

    gru_cell_backward

    fn gru_cell_backward(cache : GruCellCache, d_h_t : Array[Float], param : GruCellParam, grad : GruCellGrad) -> (Array[Float], Array[Float])

    Backward step of a single GRU cell. Returns (d_h_prev, d_x) — gradients w.r.t. the cell's inputs. Parameter gradients accumulate into grad.

    gru_cell_forward

    fn gru_cell_forward(x : Array[Float], h_prev : Array[Float], param : GruCellParam) -> (Array[Float], GruCellCache)

    Forward step of a single GRU cell. Returns (h_t, cache).

    gru_cell_sgd_step

    fn gru_cell_sgd_step(param : GruCellParam, grad : GruCellGrad, lr : Float) -> Unit

    Apply accumulated gradients to parameters with simple SGD.

    gru_clip_grad

    fn gru_clip_grad(grad : GruCellGrad, clip : Float) -> Unit

    Clamp gradient values to [-clip, grad_clip]. Modifies grad in place (clips all entries).

    gru_identity_dataset

    fn gru_identity_dataset(seq_len : Int, d_x : Int, d_target : Int, seed : UInt64) -> (Array[Array[Float]], Array[Array[Float]])

    Generate a simple synthetic sequence-prediction task: the target at time t+1 is a delayed version of input at time t (i.e., learn the identity over a 1-step shift). xs has dim d_x; target has dim d_target.

    gru_policy_forward

    fn gru_policy_forward(policy : GruPolicy, obs_seq : Array[Array[Float]], h0 : Array[Float], rng : Xoshiro) -> (Array[Int], GruPolicyCache)

    Run the GRU policy forward. Returns (action_seq, cache).

    gru_policy_gradient_update

    fn gru_policy_gradient_update(policy : GruPolicy, actions : Array[Int], returns : Array[Float], cache : GruPolicyCache, lr : Float) -> Unit

    REINFORCE-style policy gradient update with GRU.

    gru_sequence_backward

    fn gru_sequence_backward(predicted : Array[Array[Float]], target : Array[Array[Float]], caches : Array[GruCellCache], h0 : Array[Float], param : GruCellParam) -> (Array[Array[Float]], Array[Float], GruCellGrad)

    BPTT backward: given predicted, target, and caches, returns (d_xs, d_h0, grad).

    gru_sequence_forward

    fn gru_sequence_forward(xs : Array[Array[Float]], h0 : Array[Float], param : GruCellParam) -> (Array[Array[Float]], Array[GruCellCache])

    Run a GRU cell over an input sequence. Returns (hs, caches) where:
    • hs[t] is the hidden state at time t (length d_h)
    • caches has length seq_len for BPTT.

    gru_sequence_loss

    fn gru_sequence_loss(predicted : Array[Array[Float]], target : Array[Array[Float]]) -> Float

    Mean-squared-error loss between predicted hidden states and a target sequence (both length n, each entry length d_h).

    gru_train_n_steps

    fn gru_train_n_steps(xs : Array[Array[Float]], target : Array[Array[Float]], h0 : Array[Float], param : GruCellParam, n_steps : Int, lr : Float, clip : Float) -> Float

    Run K-step SGD training on a single input/target sequence. Returns the final loss. Optionally clips gradients to [-clip, clip].

    heaviside_step

    fn heaviside_step(x : Float, vt : Float) -> Float

    Hard Heaviside step at x with threshold vt.

    Returns 1.0 if x > vt, else 0.0. This is the actual forward spike signal emitted by IF / LIF neurons; the gradient of this function is approximated by fast_sigmoid_surrogate during BPTT.

    heterogeneous_sim_for

    fn heterogeneous_sim_for(m : HeterogeneousModel, duration : Float) -> Unit

    Run the heterogeneous model for duration ms starting at t=0.

    heterogeneous_sim_for_with_log

    fn heterogeneous_sim_for_with_log(m : HeterogeneousModel, duration : Float, log_every_ms : Float) -> Array[SimLog]

    Drive the model for duration ms, returning a list of SimLog snapshots taken every log_every_ms of simulated time. The model is advanced using step_heterogeneous directly (no monitors are recorded into by this function — callers should set up monitors separately).

    hetrec_refresh_random

    fn hetrec_refresh_random(p : HetRec, rng : Xoshiro) -> Unit

    Fill the random cache from the Xoshiro stream (Float32 path). Mirrors Julia's rand!(randcache) which draws Float32 values.

    hh_connect

    fn hh_connect(c : SpikingSynapseHH, pre : Int, post : Int, w : Float) -> Unit

    let hz : Float

    ifsinexp_step

    fn ifsinexp_step(pop : IFSinExp, dt : Float) -> Unit

    Advance an IFSinExp population by one timestep of length dt.
    1. Membrane update: dv = dt * ((v - el - r*i) / tm) → v += dv.
    2. Single-exp synapse update: ge[i] += glu[i]; gi[i] += gaba[i]; ge[i] += dt * (-ge[i] / tau_e); gi[i] += dt * (-gi[i] / tau_i); glu[i] = 0; gaba[i] = 0;
    3. Fire check: if v >= vt, fire, reset v=vr, record fire_t.

    image_from_bytes

    fn image_from_bytes(bytes : Bytes) -> Image raise
    DecodeError

    Decode a byte buffer into our 4D NCHW Float Image. Auto-detects the format (BMP/QOI/TGA/PNG/GIF/JPEG/ICO/TIFF). Raises @moonbit_image.DecodeError on failure.

    image_from_moonbit_image

    fn image_from_moonbit_image(img :
    Image
    ) -> Image

    Wrap an upstream Image as our 4D NCHW Float Image (3 channels, RGB). Alpha is dropped. To preserve alpha, use image_from_moonbit_image_rgba instead.

    image_from_moonbit_image_rgba

    fn image_from_moonbit_image_rgba(img :
    Image
    ) -> Image

    Wrap an upstream Image as RGBA (4 channels).

    infer_receptors

    fn infer_receptors(receptors : Array[Receptor]) -> ReceptorsByTarget

    Group receptor indices by their target field ("glu" / "gaba").

    infer_receptors_from

    fn infer_receptors_from(rs : Receptors) -> ReceptorsByTarget

    Convenience: infer_receptors_from(rs) builds a ReceptorsByTarget from a Receptors collection directly (without exposing rs.rec).

    inh_trough

    fn inh_trough(v : Array[Float], n_compartments : Int, spiketime : Int, rest : Float, compartment : Int) -> Float

    inh_trough(v, n_compartments, spiketime, rest, compartment) — returns min(v[compartment, spiketime:end]) - rest. Mirrors Julia's get_EPSP(...; inh=true).

    inh_trough_window

    fn inh_trough_window(v : Array[Float], n_compartments : Int, t_start : Int, t_end : Int, rest : Float, compartment : Int) -> Float

    inh_trough_window(v, n_compartments, t_start, t_end, rest, compartment) — trough within [t_start, t_end]. Symmetric counterpart.

    inh_trough_with_time

    fn inh_trough_with_time(v : Array[Float], n_compartments : Int, spiketime : Int, rest : Float, compartment : Int) -> (Float, Int)

    inh_trough_with_time(v, n_compartments, spiketime, rest, compartment) — returns (trough, time_of_trough) for inhibitory EPSP. Symmetric counterpart of exc_peak_with_time.

    integrate_adex_multi

    fn integrate_adex_multi(p : AdExMultiTimescale, param : AdExMultiTimescaleParameter, dt : Float) -> Unit

    integrate! — runs update_synapses! → synaptic_current! → update_soma!. Matches Julia's integrate! ordering exactly.

    integrate_any

    fn integrate_any(p : AnyPop, dt : Float) -> Unit

    integrate_extended_if

    fn integrate_extended_if(p : ExtendedIF, param : ExtendedIFParameter, dt : Float) -> Unit

    integrate! — runs update_synapses! then update_neuron!. Mirrors Julia's integrate!(p, param, dt) exactly.

    integrate_gif

    fn integrate_gif(p : GIF, param : GIFParameter, dt : Float) -> Unit

    integrate! — runs update_neuron!. Synaptic current is set externally (syn_curr array).

    integrate_ifcanahp

    fn integrate_ifcanahp(p : IFCANAHP, param : IFCANAHParameter, dt : Float) -> Unit

    integrate! — runs update_neuron! then update_spike!. Synaptic current is set externally (syn_curr array). Mirrors Julia's integrate!(p, param, dt) minus the multi-receptor synaptic current computation (we use a single combined syn_curr).

    is_bimodal

    fn is_bimodal(kernel : Array[Double], ratio : Double) -> Bool

    Check if data (a KDE curve) is bimodal at the given ratio threshold. A local maximum is "real" if kernel[max] > ratio * global_max. Returns true if ≥ 2 real maxima.

    isa_model

    fn isa_model(m : NetworkModel) -> Bool

    isi_cv2

    fn isi_cv2(spike_times_per_neuron : Array[Array[Float]]) -> Array[Float]

    Compute ISI_CV2 for every neuron in a population. Input is Array[Array[Float]] indexed by neuron id, each inner array is the sorted spike times for that neuron.

    Mirrors Julia's ISI_CV2(spiketimes::Spiketimes) which returns one CV2 per neuron via broadcasting.

    isi_cv2_one

    fn isi_cv2_one(spike_times : Array[Float]) -> Float

    Compute ISI_CV2 for a single spike train (one neuron).

    Formula: ISI[i] = spike_times[i] - spike_times[i-1] for i = 1 .. N-1 CV2[i] = 2 * |ISI[i] - ISI[i-1]| / (ISI[i] + ISI[i-1]) for i = 2 .. N-1 return mean(CV2)

    Returns 0.0 for:
    • empty spike trains (no ISIs)
    • single-spike trains (only 1 ISI, no CV2 pair)
    • NaN (zero ISIs)

    Mirrors Julia's ISI_CV2(spiketime::Vector{Float32}).

    istdp_potential_step

    fn istdp_potential_step(w : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool], colptr : Array[Int], rowptr : Array[Int], v_post : Array[Float], vars : IstdpPotentialVariables, param : IstdpPotential, t_now : Float, dt : Float) -> Unit

    One step of the IstdpPotential rule.

    Trace model (continuous-time Euler): tpre[j] += dt * (-tpre[j]) / tau_y tpost[i] += dt * -(tpost[i] - v_post[i]) / tau_y On a pre spike: tpre[j] += 1 On a post spike: tpost[i] += 1

    Weight update (per stored connection s = (j -> i)): If pre fired: w[s] += eta * (tpost[i] - v0) If post fired: w[s] += eta * tpre[j] Clamp w[s] to [w_min, w_max].

    v_post is the post-synaptic membrane potential array (length n_post). Each element v_post[i] is in mV (internal units).

    istdp_rate_step

    fn istdp_rate_step(w : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool], colptr : Array[Int], rowptr : Array[Int], vars : IstdpRateVariables, param : IstdpRate, t_now : Float, dt : Float) -> Unit

    One step of the IstdpRate rule.

    Trace model (continuous-time Euler, dt-step integration): tpre[j] += dt * (-tpre[j]) / tau_y tpost[i] += dt * (-tpost[i]) / tau_y On a pre spike: tpre[j] += 1 On a post spike: tpost[i] += 1

    Weight update (per stored connection s = (j -> i)): If pre fired: w[s] += eta * (tpost[i] - 2 * r * tau_y) If post fired: w[s] += eta * tpre[j] Clamp w[s] to [w_min, w_max].

    CSR layout (matches the rest of the SNN port): rowptr[j]..rowptr[j+1] lists non-zero positions for row j (pre-neuron j); colptr[s] = i (post-neuron). We walk rowptr[j] once per j and apply both the pre-fire and post-fire contributions in the same loop (same fused pattern as stdp_step and stdp_confavreux_step).

    iz_connect

    fn iz_connect(c : SpikingSynapseIZ, pre : Int, post : Int, w : Float) -> Unit

    iz_reset

    fn iz_reset(p : IZ) -> Unit

    Reset an IZ population to its initial state: all voltages back to -65, recovery to b * -65, conductances zeroed, no fires, no refractory. Mirrors Julia's reset! semantic for IF/AdEx but specialised for IZ (no I parameter reset; i is preserved so external input current can be re-driven without rebuild).

    iz_reset_with_postspike

    fn iz_reset_with_postspike(p : IZ, postspike : IZPostSpike) -> Unit

    iz_reset_with_postspike — like iz_reset but ALSO clears the refractory state (tabs to 0) and re-initialises tabs_const to the given IZPostSpike's value. Useful for hot-swapping the refractory period mid-simulation.

    kde

    fn kde(t : Double, h : Double, data : Array[Double]) -> Double

    Normal-kernel density estimate at sample point t, using window width h over data. Returns Float64. Formula: KDE(t) = (1/N) * (1/h) * sum exp(-((x_i - t)^2) / h)

    kei_balance_per_neuron

    fn kei_balance_per_neuron(voltage_data : Array[Float], n_steps : Int, n_neurons : Int, target_rate : Float, tolerance : Float) -> Array[Int]

    Find the time index per neuron where voltage_data[t, n] is closest to target_rate. If the closest value is within tolerance (default 2.3 mV), return that index; otherwise return 0.

    voltage_data is a flat row-major Array[Float] of shape [n_steps × n_neurons] — index via voltage_data[t * n_neurons + n]. target_rate is in mV (e.g. -55.0F). Result: Array[Int] of length n_neurons.

    kei_balance_population

    fn kei_balance_population(voltage_data : Array[Float], n_steps : Int, n_neurons : Int, target_rate : Float, tolerance : Float) -> Int

    Find a single global time index where the per-neuron MEAN voltage is closest to target_rate. Useful when looking at the population-average settling time.

    khz

    let khz : Float

    layer_norm_backward

    fn layer_norm_backward(d_output : Array[Float], cache : LayerNormCache, ln : LayerNorm) -> (Array[Float], Array[Float], Array[Float])

    Backward pass. Returns (d_input, d_gamma, d_beta). d_gamma and d_beta are accumulated across the batch dim (length = c * h * w each).

    layer_norm_forward

    fn layer_norm_forward(input : Array[Float], n : Int, c : Int, h : Int, w : Int, ln : LayerNorm) -> (Array[Float], LayerNormCache)

    Forward pass.

    layer_scale_backward

    fn layer_scale_backward(sublayer : Array[Float], d_output : Array[Float], ls : LayerScale) -> (Array[Float], Array[Float])

    Backward pass. Returns (d_sublayer, d_gamma). d_sublayer is gamma * d_output (element-wise). d_gamma[i] = sum over batch of d_output[*, i] * sublayer[*, i].

    layer_scale_forward

    fn layer_scale_forward(sublayer : Array[Float], ls : LayerScale) -> Array[Float]

    Forward pass: out[i] = gamma[i] * sublayer[i] (per-channel).

    lenet5_backward

    fn lenet5_backward(m : LeNet5, caches : Array[LayerCache], d_output : Array[Float], n : Int) -> (Array[Float], Array[LayerGrad])

    Backward pass. d_output has shape [n, 10, 1, 1].

    lenet5_forward

    fn lenet5_forward(m : LeNet5, input : Array[Float], n : Int) -> (Array[Float], Array[LayerCache], Int, Int, Int, Int)

    Forward pass for LeNet5. Input must be [n, 1, 32, 32].

    linear_backward

    fn linear_backward(input : Array[Float], d_output : Array[Float], n : Int, param : LinearParam) -> (Array[Float], Array[Float], Array[Float])

    Linear backward pass. Returns (d_input, d_weight, d_bias).

    linear_forward

    fn linear_forward(input : Array[Float], n : Int, param : LinearParam) -> Array[Float]

    Forward pass for a linear (dense) layer.

    input : length = n * in_features returns : length = n * out_features

    linear_network

    fn linear_network(n : Int) -> Array[Float]

    linear_network_with

    fn linear_network_with(n : Int, sigma_w : Float, w_max : Float) -> Array[Float]

    logf

    fn logf(x : Float) -> Float

    Float32 natural logarithm: matches Julia's log(Float32, x) and the C logf function. Used by STDP kernels with log-ratio traces.

    logrange

    fn logrange(x1 : Float, x2 : Float, n : Int) -> Array[Float]

    Log-spaced array from x1 to x2 with n entries (inclusive endpoints). logrange(x1, x2, n)[k] = 10^(log10(x1) + k * (log10(x2) - log10(x1)) / (n-1)).

    Uses Float64 for log/exp to match Julia's logspace-style precision, then casts back to Float32 for the result.

    lstm_cell_backward

    fn lstm_cell_backward(cache : LstmCellCache, d_h_t : Array[Float], d_c_t : Array[Float], param : LstmCellParam, grad : LstmCellGrad) -> (Array[Float], Array[Float], Array[Float])

    Backward step of a single LSTM cell. Returns (d_h_prev, d_x, d_c_prev) — gradients w.r.t. the cell's inputs. Parameter gradients accumulate into grad.

    lstm_cell_forward

    fn lstm_cell_forward(x : Array[Float], h_prev : Array[Float], c_prev : Array[Float], param : LstmCellParam) -> (Array[Float], LstmCellCache)

    Forward step of a single LSTM cell. Returns (h_t, cache).

    lstm_cell_sgd_step

    fn lstm_cell_sgd_step(param : LstmCellParam, grad : LstmCellGrad, lr : Float) -> Unit

    Apply accumulated gradients to parameters with simple SGD.

    lstm_clip_grad

    fn lstm_clip_grad(grad : LstmCellGrad, clip : Float) -> Unit

    Clamp gradient values to [-clip, grad_clip]. Modifies grad in place (clips all entries).

    lstm_episode_return

    fn lstm_episode_return(rewards : Array[Float]) -> Float

    Total episode return (sum of rewards).

    lstm_identity_dataset

    fn lstm_identity_dataset(seq_len : Int, d_x : Int, seed : UInt64) -> (Array[Array[Float]], Array[Array[Float]])

    Generate a simple synthetic sequence-prediction task: the target at time t+1 is a delayed version of input at time t (i.e., learn the identity over a 1-step shift). Returns (xs, target) both length seq_len, each entry length d_x.

    lstm_policy_forward

    fn lstm_policy_forward(policy : LstmPolicy, obs_seq : Array[Array[Float]], h0 : Array[Float], c0 : Array[Float], rng : Xoshiro) -> (Array[Int], LstmPolicyCache)

    Run the LSTM policy forward. Returns (action_seq, cache).

    lstm_policy_gradient_update

    fn lstm_policy_gradient_update(policy : LstmPolicy, actions : Array[Int], returns : Array[Float], cache : LstmPolicyCache, lr : Float) -> Unit

    REINFORCE-style policy gradient update with LSTM.

    lstm_ppo_collect_batch

    fn lstm_ppo_collect_batch(env_n_cells : Int, policy : LstmPolicy, n_episodes : Int, gamma : Float, max_steps : Int, seed : UInt64) -> RecurrentPpoBatch

    Collect a batch from the LSTM policy on the corridor POMDP. Uses Monte-Carlo returns as advantages (no value baseline).

    lstm_ppo_update_step

    fn lstm_ppo_update_step(policy : LstmPolicy, batch : RecurrentPpoBatch, clip_eps : Float, lr : Float) -> Float

    Apply one PPO-clipped update step. Modifies policy.w_out (output projection) in place. With 1 epoch and small lr the importance ratio stays near 1, so clipping rarely activates — this is the standard single-pass online PPO setting.

    lstm_qnet_forward

    fn lstm_qnet_forward(qnet : LstmQNet, obs_seq : Array[Array[Float]], h0 : Array[Float], c0 : Array[Float]) -> (Array[Array[Float]], LstmQNetCache)

    Forward pass through the Q-net over a sequence of observations. Returns the Q-values per time step + a cache for BPTT.

    lstm_sequence_backward

    fn lstm_sequence_backward(predicted : Array[Array[Float]], target : Array[Array[Float]], caches : Array[LstmCellCache], h0 : Array[Float], c0 : Array[Float], param : LstmCellParam) -> (Array[Array[Float]], Array[Float], Array[Float], LstmCellGrad)

    BPTT backward: given predicted, target, and caches, returns (d_xs, d_h0, d_c0, grad).

    Each d_xs[t] is the gradient w.r.t. xs[t].

    lstm_sequence_forward

    fn lstm_sequence_forward(xs : Array[Array[Float]], h0 : Array[Float], c0 : Array[Float], param : LstmCellParam) -> (Array[Array[Float]], Array[Float], Array[LstmCellCache])

    Run an LSTM cell over an input sequence. Returns (hs, cs_final, caches) where:
    • hs[t] is the hidden state at time t (length d_h)
    • cs_final is the final cell state (length d_h)
    • caches has length seq_len for BPTT.

    lstm_sequence_loss

    fn lstm_sequence_loss(predicted : Array[Array[Float]], target : Array[Array[Float]]) -> Float

    Mean-squared-error loss between predicted hidden states and a target sequence (both length n, each entry length d_h).

    lstm_train_n_steps

    fn lstm_train_n_steps(xs : Array[Array[Float]], target : Array[Array[Float]], h0 : Array[Float], c0 : Array[Float], param : LstmCellParam, n_steps : Int, lr : Float, clip : Float) -> Float

    Run K-step SGD training on a single input/target sequence. Returns the final loss. Optionally clips gradients to [-clip, clip].
    let m2 : Float

    let mA : Float

    let mF : Float

    let mM : Float

    let mS : Float

    let mV : Float

    ma1_loss_value

    fn ma1_loss_value(y : Array[Float], theta : Float) -> Float

    Compute MA(1) residual loss (forward only, no tape). Used for finite-difference validation of demo_ma1_loss.

    ma_fit

    fn ma_fit(y : Array[Float], q : Int, max_iter : Int, lr : Float) -> MaParam

    Fit MA(q) by gradient descent on the conditional sum-of-squares loss. Returns the converged MaParam. Simple fixed-step Adam without momentum — adequate for short series / small q.

    ma_grad_thetas

    fn ma_grad_thetas(y : Array[Float], param : MaParam) -> Array[Float]

    Gradient ∂loss/∂θⱼ for j = 0..q-1 (length q). The MA recursion couples every θⱼ to every residual, so we have to track partial derivatives of all residuals w.r.t. all parameters jointly:

    ∂ε[t]/∂θⱼ = -Σ_{k=0..q-1} θₖ · ∂ε[t-1-k]/∂θⱼ - ε[t-1-j] = -Σ_k θₖ · de[t-1-k] - ε[t-1-j]

    Initialised at t = 0: ∂ε[0]/∂θⱼ = 0 (no recursion yet). Then propagated forward; gradient accumulated as ∂loss/∂θⱼ = 2 Σₜ ε[t] · ∂ε[t]/∂θⱼ.

    ma_loss_value

    fn ma_loss_value(y : Array[Float], intercept : Float, thetas : Array[Float]) -> Float

    Forward-only MA(q) loss used for finite-difference validation of ma_grad_thetas. Returns the conditional sum-of-squares loss for the given thetas (length q) and intercept.

    ma_residual_loss

    fn ma_residual_loss(y : Array[Float], param : MaParam) -> Float

    Sum of squared residuals Σₜ ε[t]² — the MA(q) fitting objective (a.k.a. conditional sum-of-squares).

    ma_residuals

    fn ma_residuals(y : Array[Float], param : MaParam) -> Array[Float]

    Compute innovations ε[t] for t = 0, 1, ..., n-1 using the MA(q) recursion. Pre-sample residuals (t < 0) are initialised to 0, which is the standard "no pre-sample data" assumption used in most textbook implementations.

    make_random_weights

    fn make_random_weights(n_pre : Int, n_post : Int, mu : Float, p : Float, rng : Xoshiro) -> Array[Float]

    Build a dense n_pre × n_post weight matrix with Bernoulli(p) connectivity. Connections get weight mu; non-connections are 0.

    markram_stp_step

    fn markram_stp_step(syn : SpikingSynapse, vars : MarkramSTPVariables, param : MarkramSTPParameter, t_now : Float) -> Unit

    Apply one Markram STP update step to the synapse syn, mutating vars and broadcasting ρ to syn.rho[] for outgoing edges.

    Math (matches Julia's update_traces! for MarkramSTPParameterEvent): for j in eachindex(fireJ): if fireJ[j]: ΔT = max(0, t_now - last_spike[j]) last_spike[j] = t_now u[j] = U - (U - u[j]) * exp(-ΔT / τF) x[j] = 1 - (1 - x[j]) * exp(-ΔT / τD) _ρ[j] = u[j] * x[j] for s in colptr[j]:colptr[j+1]: ρ[s] = _ρ[j] u[j] += U * (1 - u[j]) x[j] += -u[j] * x[j]

    Requires syn.rho to be non-empty (caller must have called SpikingSynapse::init_rho(syn) once after building the connectivity).

    markram_stp_step_het

    fn markram_stp_step_het(syn : SpikingSynapse, vars : MarkramSTPVariables, param : MarkramSTPParameterHet, t_now : Float) -> Unit

    Apply one Markram STP update step using per-pre-neuron parameters (τD[j], τF[j], U[j]). Mirrors Julia's update_traces! for MarkramSTPParameterHet: for j in eachindex(fireJ): if fireJ[j]: ΔT = max(0, t_now - last_spike[j]) last_spike[j] = t_now u[j] = U[j] - (U[j] - u[j]) * exp(-ΔT / τF[j]) x[j] = 1 - (1 - x[j]) * exp(-ΔT / τD[j]) _ρ[j] = u[j] * x[j] for s in colptr[j]:colptr[j+1]: ρ[s] = _ρ[j] u[j] += U[j] * (1 - u[j]) x[j] -= u[j] * x[j]

    Same syn.rho precondition as markram_stp_step.

    markram_stp_step_timestep

    fn markram_stp_step_timestep(syn : SpikingSynapse, vars : MarkramSTPVariables, param : MarkramSTPParameterTimestep, t_now : Float, dt : Float) -> Unit

    One timestep Markram STP step.

    Order of operations (mirrors Julia):
    1. Spike bumps: if pre fire[j], apply u += U*(1-u) and x += -u*x.
    2. Continuous-time Euler: u += dt*(U-u)/tau_f, x += dt*(1-x)/tau_d for every j.
    3. Recompute rho_pre[j] = u[j] * x[j] and broadcast to syn.rho[s] for s in rowptr[j]:rowptr[j+1].

    mask_broadcast

    fn mask_broadcast(mask_2d : Array[Float], seq_len : Int, num_heads : Int) -> Array[Float]

    Broadcast a 2D mask (seq_len × seq_len) to per-head shape (num_heads × seq_len × seq_len). All heads share the same mask.

    mask_from_allowed

    fn mask_from_allowed(seq_len : Int, allowed : Array[Array[Bool]]) -> Array[Float]

    Build a per-position allowed-keys mask from a 2D boolean table.

    allowed[i][j] is true iff position i is allowed to attend to key j. Returns a (seq_len × seq_len) additive mask: 0 where allowed, -1e9 where blocked.

    math_exp_f32

    fn math_exp_f32(x : Float) -> Float

    Float32 exp via libm FFI (matches Julia's exp(Float32, x)).

    math_log_f32

    fn math_log_f32(x : Float) -> Float

    Float32 log via libm FFI (matches Julia's log(Float32, x)).

    max_abs_diff

    fn max_abs_diff(a : Array[Float], b : Array[Float]) -> (Float, Int)

    Compare two gradients. Returns the max absolute difference and the index of the worst component. Useful for asserting tolerance.

    maxpool2d_backward

    fn maxpool2d_backward(d_output : Array[Float], argmax_idx : Array[Int], n : Int, c : Int, h : Int, w : Int, param : MaxPool2dParam) -> Array[Float]

    MaxPool2d backward pass.

    d_output is the upstream gradient of shape [n, c, ho, wo]. argmax_idx is the recorded argmax from maxpool2d_forward_with_idx.

    returns d_input of shape [n, c, h, w].

    maxpool2d_forward

    fn maxpool2d_forward(input : Array[Float], n : Int, c : Int, h : Int, w : Int, param : MaxPool2dParam) -> Array[Float]

    Forward pass for a 2D max pool.

    input : length = n * c * h * w returns : length = n * c * ho * wo

    maxpool2d_forward_with_idx

    fn maxpool2d_forward_with_idx(input : Array[Float], n : Int, c : Int, h : Int, w : Int, param : MaxPool2dParam) -> (Array[Float], Array[Int])

    MaxPool2d forward + argmax recording.

    argmax_idx is filled in with the flat input index of the argmax for each output position. If the window is entirely OOB (impossible when stride > 0 since at least one cell is in-bounds), the index is set to -1.

    returns (output, argmax_idx)

    merge_heterogeneous

    fn merge_heterogeneous(a : HeterogeneousModel, b : HeterogeneousModel) -> HeterogeneousModel

    Merge two heterogeneous models into one. Populations, connections, stimuli, monitors, STDP, and STP entries are concatenated. The returned model uses the first model's time tracker (the second model's time is dropped).

    Connections and STDP/STP entries that referenced indices in the second model are NOT remapped — callers are responsible for composing models whose internal indices are consistent.

    metaplasticity_step

    fn metaplasticity_step(norm : SynapseNormalization) -> Unit

    Apply one step of normalization. For each post-neuron:
    1. W1[i] = sum of all synapse weights targeting i.
    2. Compute μ[i] based on the rule (multiplicative or additive).
    3. Apply μ[i] to each weight (multiply or add).

    Always runs (per-step form). For Julia's periodic gating (only normalize every τ/dt steps), use metaplasticity_step_gated instead.

    metaplasticity_step_gated

    fn metaplasticity_step_gated(norm : SynapseNormalization, step_count : Int, dt : Float) -> Unit

    Periodic form of metaplasticity_step — mirrors Julia's outer plasticity!(c, param, dt, T) which gates the step on ((tt) % round(Int, τ / dt)) < dt. Only runs the normalization when step_count is a multiple of τ / dt (rounded).

    When τ == 0, never fires (matches Julia's τ=0 default which disables the periodic rule). Otherwise fires every round(τ/dt) steps.

    Note: this is a simple "every-N-steps" gate, not a true-time gate. Julia uses T.t / dt for the same purpose; our step_count parameter is the equivalent (caller maintains the step counter).

    metre

    let metre : Float

    Base unit constants. All values are Float (Float32) to match Julia behaviour exactly. mV, ms, nS, pA, pF, GΩ are normalised to 1.0 — i.e. internal computations use these as the conversion factor.

    mexican_hat_kernel

    fn mexican_hat_kernel(x : Float) -> Float

    Pure MexicanHat kernel: MexicanHat(x) = (1 - x) * exp(-x / sqrt(2)).

    Returns 0 when x is NaN (matches Julia's isnan guard).

    miles_gaba_dend

    fn miles_gaba_dend() -> GABAergic

    Miles GABA dendrite (GABAergic bundle): GABAa(-70, 4.8, 29, 0.27)
    • GABAb(-90, 30, 400, 0.006). Matches Julia MilesGabaDend.

    miles_gaba_soma

    fn miles_gaba_soma() -> Receptor

    Miles GABA soma receptor: E_rev=-70, τr=0.1, τd=15.0, g0=0.38, target="gaba". Matches Julia MilesGabaSoma = Receptor(E_rev=-70, τr=0.1, τd=15, g0=0.38, target=:gaba).

    mini_spike_former_backward

    fn mini_spike_former_backward(cache : MiniSpikeFormerCache, msf : MiniSpikeFormer) -> (Array[Float], Array[Float], Array[Float], Array[Float], SpikingTransformerBlockGrad, Array[Float], Array[Float])

    Backward pass: returns gradients for all model parameters.

    mini_spike_former_eval_accuracy

    fn mini_spike_former_eval_accuracy(images : Array[Float], labels : Array[Int], msf : MiniSpikeFormer) -> Float

    Classification accuracy on a list of (image, label) samples. Each image is evaluated independently (batch=1 forward pass). Returns correct / total as a Float; returns 0.0 for an empty label list.

    mini_spike_former_eval_loss

    fn mini_spike_former_eval_loss(images : Array[Float], labels : Array[Int], msf : MiniSpikeFormer) -> Float

    Compute the mean cross-entropy loss over a small batch of synthetic images (one pass, no parameter updates).

    mini_spike_former_forward

    fn mini_spike_former_forward(images : Array[Float], batch : Int, label : Int, msf : MiniSpikeFormer) -> (Float, MiniSpikeFormerCache)

    Forward pass: extract patches → linear patch embed → + class token → + pos embed → SpikingTransformerBlock → take class token → linear classifier → softmax.

    mini_spike_former_predict

    fn mini_spike_former_predict(images : Array[Float], msf : MiniSpikeFormer) -> Int

    Predict argmax class for a single 28×28 image. Reuses mini_spike_former_forward (the label is unused for argmax).

    mini_spike_former_train_n_steps

    fn mini_spike_former_train_n_steps(images : Array[Float], label : Int, n_steps : Int, lr : Float, msf : MiniSpikeFormer) -> (Float, Float)

    Train n_steps SGD updates on a single (image, label) sample and report (initial_acc, final_acc) on that same sample. Initial accuracy is measured before any updates; final accuracy is measured after the K updates. Sample must be a single 28×28 image (length 784); pass [label] to eval_accuracy via the single-sample path.

    mini_spike_former_train_step

    fn mini_spike_former_train_step(images : Array[Float], label : Int, lr : Float, msf : MiniSpikeFormer) -> Float

    One training step (single sample, batch=1) with simple SGD + per-parameter gradient clipping (clip=1.0). Updates parameters in place. Returns the loss.

    mlp_backward

    fn mlp_backward(m : MLP, caches : Array[LayerCache], d_output : Array[Float], n : Int) -> (Array[Float], Array[LayerGrad])

    Backward pass for an MLP. d_output has length n * sizes[last].

    mlp_forward

    fn mlp_forward(m : MLP, input : Array[Float], n : Int) -> (Array[Float], Array[LayerCache], Int)

    Forward pass for an MLP. Input shape is [n, sizes[0]]; the chain handles layer chaining internally.
    let mm : Float

    mohm

    let mohm : Float

    monitor_count_spikes

    fn monitor_count_spikes(data : Array[Float]) -> Int

    Count the number of 1.0F entries in a fire monitor's data array (each entry is 0.0F or 1.0F per step). This equals the total spike count for the monitored neuron.

    monitor_firing_rate

    fn monitor_firing_rate(data : Array[Float]) -> Float

    Compute the firing rate in Hz from a fire monitor's data array (each entry is 0.0F or 1.0F per step). With dt=0.125ms, each step is 0.000125s = 0.125ms = 0.125/1000 s. rate_Hz = count / (n_steps * dt) = count / (n_steps * 0.000125) Internal rate (in 0.001 Hz units) = count / (n_steps * 0.125)
    let ms : Float

    msiemens

    let msiemens : Float

    multi_head_attention_backward

    fn multi_head_attention_backward(cache : AttnCache, d_output : Array[Float], mha : MultiHeadAttention) -> (Array[Float], MHAGrad)

    Self-attention backward pass. Returns (d_input, MHAGrad).

    multi_head_attention_forward

    fn multi_head_attention_forward(x : Array[Float], mha : MultiHeadAttention, mask : Array[Float]) -> (Array[Float], AttnCache)

    Self-attention forward pass.

    x : length seq_len * d_model, row-major mask~ : optional additive mask shape num_heads * seq_len *seq_len (pass an empty array to skip). returns : (out, cache) where out is length seq_len * d_model.

    multi_step_return

    fn multi_step_return(rewards : Array[Float], dones : Array[Bool], q_next_max : Float, gamma : Float, n_steps : Int, t : Int) -> Float

    Compute the n-step return from a contiguous subsequence of transitions starting at index t: G_t = r_t + γ·r_{t+1} + γ²·r_{t+2} + ... + γ^(n-1)·r_{t+n-1} + γ^n · max_a' Q̂(s_{t+n}, a')

    If done[k] is true at any intermediate step, the n-step return truncates cleanly: g = 0 (no future rewards, no bootstrap).
    let nA : Float

    let nF : Float

    let nM : Float

    let nS : Float

    n_total

    fn n_total(pops : Array[AnyPop]) -> Int

    name

    fn name(pre : String, post : String, k : String) -> String

    Generate a Symbol name for a connection between populations: :pre_to_post or :pre_to_post_k if k is provided. Mirrors Julia's name(pre, post, k=nothing).

    name2

    fn name2(pre : String, post : String) -> String

    Two-argument form: name(pre, post) → "pre_to_post".

    network_summary

    fn network_summary(m : HeterogeneousModel) -> NetworkSummary

    Compute a summary of the model.

    neuron_firing_rate

    fn neuron_firing_rate(spike_times : Array[Float], t_start : Float, t_end : Float) -> Float

    Firing rate (Hz) of a single neuron over [t_start, t_end]. Returns 0.0F if the window duration is zero.

    next_f32

    fn next_f32(r : Xoshiro) -> Float

    rand(r, Float32): Float32(rand(r, UInt32) >>> 8) * Float32(0x1.0p-24). Matches Julia's rand(r, CloseOpen01{Float32}).

    next_f64

    fn next_f64(r : Xoshiro) -> Double

    rand(r, Float64): Float64(rand(r, UInt64) >>> 11) * 0x1.0p-53. Matches Julia's rand(r, CloseOpen01_64).

    next_u32

    fn next_u32(r : Xoshiro) -> UInt

    rand(r, UInt32): take top 32 bits of rand(r, UInt64) and cast. Matches Julia's (rand(rng, UInt64) >>> (64 - 8*4)) % UInt32.

    next_u64

    fn next_u64(r : Xoshiro) -> UInt64

    xoshiro256++ step: advance state and return a UInt64 in [0, 2^64). Matches Julia's rand(rng, UInt64).
    let nm : Float

    nm2

    let nm2 : Float

    nmda_gating

    fn nmda_gating(v : Float, nmda : NMDAVoltageDependency) -> Float

    Compute the NMDA voltage-dependent gating B(v) = 1/(1 + (mg/b)exp(kv)). Matches Julia's nmda_gating(v, NMDA).

    noisy_dqn_select_action

    fn noisy_dqn_select_action(net : NoisyQNet, state : Int, rng : Xoshiro) -> Int

    ε-free action selection. Just greedy over the noisy forward — the noise drives exploration implicitly (paper §3.2).

    noisy_dqn_select_action_eval

    fn noisy_dqn_select_action_eval(net : NoisyQNet, state : Int) -> Int

    ε-free action selection at evaluation (μ weights only).

    noisy_dqn_update_step

    fn noisy_dqn_update_step(online : NoisyQNet, target : NoisyQNet, states : Array[Int], actions : Array[Int], rewards : Array[Float], next_states : Array[Int], dones : Array[Bool], gamma : Float, lr : Float, rng : Xoshiro) -> Float

    One update step on a mini-batch. Updates BOTH μ and σ weights (per paper §3.4). Returns the mean |δ| for monitoring.

    noisy_linear_forward

    fn noisy_linear_forward(input : Array[Float], n : Int, param : NoisyLinearParam, rng : Xoshiro) -> Array[Float]

    Forward pass with sampled noise. rng produces two N(0,1) sets (ε_i of size in_features, ε_j of size out_features) per call; the rest is deterministic.

    input : length = n * in_features returns: length = n * out_features

    noisy_linear_forward_eval

    fn noisy_linear_forward_eval(input : Array[Float], n : Int, param : NoisyLinearParam) -> Array[Float]

    Deterministic forward (no noise). Used for the "evaluation" pass where exploration should be off — equivalent to a regular Linear with the μ weights. Matches paper §3.3 "greedy action at evaluation time uses only μ parameters."

    noisy_linear_param_count

    fn noisy_linear_param_count(param : NoisyLinearParam) -> Int

    Number of trainable parameters (μ + σ for weight and bias).

    noisy_q_argmax

    fn noisy_q_argmax(net : NoisyQNet, x : Array[Float], rng : Xoshiro) -> Int

    Greedy action under noise (training): argmax noisy_q_forward.

    The paper draws an independent noise sample per forward call — this is what drives exploration. The action selection is still greedy, but the Q-values themselves are stochastic.

    noisy_q_argmax_eval

    fn noisy_q_argmax_eval(net : NoisyQNet, x : Array[Float]) -> Int

    Greedy action under μ-only weights (evaluation / testing).

    noisy_q_forward

    fn noisy_q_forward(net : NoisyQNet, x : Array[Float], rng : Xoshiro) -> Array[Float]

    Forward pass with sampled noise. Returns Array[Float] of length n_actions (Q-values for the single input state).

    noisy_q_forward_eval

    fn noisy_q_forward_eval(net : NoisyQNet, x : Array[Float]) -> Array[Float]

    Deterministic forward (no noise). Used at evaluation time and when the caller wants pure μ weights. Equivalent to running the same architecture with all σ = 0.

    noisy_qnet_copy

    fn noisy_qnet_copy(dst : NoisyQNet, src : NoisyQNet) -> Unit

    Deep-copy μ + σ weights from src to dst. Used to sync online → target after every sync_every episodes.

    noisy_qnet_param_count

    fn noisy_qnet_param_count(net : NoisyQNet) -> Int

    Total trainable parameters (μ + σ for both NoisyLinear layers).

    norm_synapse

    fn norm_synapse(tau_r : Float, tau_d : Float) -> Float

    norm_synapse(τr, τd) = 1 / (-exp(-t_p/τr) + exp(-t_p/τd)) where t_p = τr * τd / (τd - τr) * log(τd / τr).

    nsiemens

    let nsiemens : Float

    numerical_gradient

    fn numerical_gradient(input : Array[Float], eps : Float, forward : (Array[Float]) -> Float) -> Array[Float]

    Compute the central-difference numerical gradient of a scalar loss loss(input) (computed by forward) along each coordinate of input.

    forward is a closure that takes an input array and returns a scalar (sum-of-outputs loss).

    Returns a fresh array of the same length as input.

    ohm

    let ohm : Float

    one_hot

    fn one_hot(label : Int, num_classes : Int) -> Array[Float]

    One-hot encode an Int label.

    ornstein_uhlenbeck_step

    fn ornstein_uhlenbeck_step(rng : Xoshiro, x : Float, theta : Float, mu : Float, sigma : Float, dt : Float) -> Float

    Ornstein-Uhlenbeck process step. Returns the new state X(t+dt).

    X(t+dt) = X(t) + θ * (μ - X(t)) * dt + σ * ξ * sqrt(dt)

    where ξ ~ N(0, 1) is drawn via Box-Muller from the supplied Xoshiro RNG. Mirrors Julia's OrnsteinUhlenbeckProcess(x, param) used in Lagzi2022 Assembly Formation experiments.

    Result is clamped at X >= 0 (rates must be non-negative).
    let pA : Float

    let pF : Float

    parity_combined_hash

    fn parity_combined_hash(seed : UInt64) -> Int

    Format a parity dump as a single Int that combines all three sub-hashes. Useful for cross-language comparison (Julia can reproduce this with the same recipe).

    parity_full_dump

    fn parity_full_dump(seed : UInt64) -> (Int, Int, Int)

    Build a deterministic parity dump: a 3-tuple of (xoshiro_hash, if_trajectory_hash, sparse_hash) for a fixed seed. Useful as a one-shot sanity check.

    parity_hash_floats

    fn parity_hash_floats(data : Array[Float]) -> Int

    Convenience: hash a Float array via quantization.

    parity_hash_if_trajectory

    fn parity_hash_if_trajectory(n_steps : Int, dt : Float, i_drive : Float, seed : UInt64) -> Int

    Hash the public state of an IF neuron over a fixed-step simulation with constant tonic current i_drive. Returns an Int that should match a Julia run with the same parameters.

    parity_hash_sparse_matrix

    fn parity_hash_sparse_matrix(n_pre : Int, n_post : Int, mu : Float, sigma : Float, p : Float, seed : UInt64) -> Int

    Hash the weight matrix of a randomly-initialised sparse connection matrix. Returns an Int hash of the vals + colptr concatenated.

    parity_hash_xoshiro

    fn parity_hash_xoshiro(seed : UInt64, n : Int) -> Int

    Hash the public state of an Xoshiro RNG after seeding with seed and advancing n steps. Caller must use the same seed + step count on the Julia side to verify.

    parse_csv

    fn parse_csv(text : String) -> Array[Array[Float]]

    Parse multi-line CSV text into 2D Float array. Splits on '\n', then each line via parse_csv_row. Handles '\r\n' (Windows).

    parse_csv_row

    fn parse_csv_row(line : String) -> Array[Float]

    Parse a single CSV row into Array[Float]. Splits on ',', trims whitespace, parses each field via parse_float.

    parse_float

    fn parse_float(s : String) -> Float

    Parse a decimal string to Float. Handles leading sign, integer part, decimal part, and optional 'e'/'E' exponent. Returns 0.0F on parse failure or empty input.

    parse_int

    fn parse_int(s : String) -> Int

    Parse a decimal string to Int. Returns 0 on parse failure.

    parse_int_list

    fn parse_int_list(text : String) -> Array[Int]

    Parse a comma-separated Int list (single line).

    parse_parity_csv

    fn parse_parity_csv(content : String) -> ParityTrace

    Load a parity reference trace from a CSV file. Format: one value per line; lines starting with # are comments; blank lines skipped. Returns ParityTrace with kind="float" (use parse_float on each line) or kind="int" (parse_int). The kind is detected from the first non-comment line — if it parses as Int without losing info, it's int; else float.

    The file path is relative to the package root (where moon.mod lives). This function does NOT touch the filesystem — the caller passes the already-loaded file content as a String.

    per_neuron_firing_rates

    fn per_neuron_firing_rates(per_neuron_spikes : Array[Array[Float]], t_start : Float, t_end : Float) -> Array[Float]

    Per-neuron firing rates (Hz) over [t_start, t_end]. Returns an array of length n_neurons.

    per_state_kl

    fn per_state_kl(pi_cur : Array[Float], pi_old : Array[Float]) -> Float

    KL divergence KL(π_θ || π_θ_old) at a single state. Returns the forward KL = Σ_a π_θ(a) · log(π_θ(a) / π_old(a)).

    periodic_distance_scalar

    fn periodic_distance_scalar(p1 : Float, p2 : Float, grid_size : Float) -> Float

    pinning_plasticity

    fn pinning_plasticity(c : PINningSparseSynapse, dt : Float, t_now : Float) -> Unit

    Plasticity (one effective update step). Mirrors Julia's plasticity! for PINningSparseSynapse: C = 1 / (1 + dot(q, rI)) for s in rowptr[j]:rowptr[j+1]: i = I[s] P[s] += -C * q[i] * q[j] W[s] += C * (f[i] - g[i]) * q[j]

    Args: c : PINningSparseSynapse dt : Float (kept for API parity with plasticity!(c, param, dt, T); the rate-mode rule doesn't actually use dt since it's event-driven on forward_pinning_synapse calls) T : Float (current time, kept for parity; not used in the pure rate-mode update)

    place_populations_e_i

    fn place_populations_e_i(n_e : Int, n_i : Int, grid_x : Float, grid_y : Float, rng : Xoshiro) -> PlacedPops

    poisson_input

    fn poisson_input(hz_rate : Float, interval : Float, dt : Float, neurons : Int, rng : Xoshiro) -> Array[Array[Bool]]

    Generate neurons × n_spikes matrix of independent Poisson spike trains. Returns Array[Array[Bool]] where outer index = neuron. Total time = interval ms.

    poisson_input_single

    fn poisson_input_single(hz_rate : Float, interval : Float, dt : Float, rng : Xoshiro) -> Array[Bool]

    Sample one spike train (1D Bool array) using exponential ISI. interval is in ms, dt is in ms. Spike count = round(interval / dt).

    policy_forward

    fn policy_forward(policy : LinearSoftmaxPolicy, x : Array[Float]) -> (Array[Float], Array[Float])

    Compute logits and numerically stable softmax probabilities.

    policy_gradient_update

    fn policy_gradient_update(policy : LinearSoftmaxPolicy, episode : Episode, returns : Array[Float], lr : Float) -> Unit

    REINFORCE policy gradient step. Modifies policy.w in place. d_logits[i] = (1{i == a_t} - π(a|s_t)) · G_t d_W[i, j] += d_logits[i] · x_t[j]

    policy_rollout

    fn policy_rollout(env : GridWorld, policy : LinearSoftmaxPolicy, max_steps : Int, rng : Xoshiro) -> Episode

    Roll out one episode under the current policy.

    polynomial_value

    fn polynomial_value(x_val : Float) -> Float

    Forward-only value of the polynomial f(x) = x³ - 2x + 1. Used as the reference for finite-difference gradient checks (finite_diff needs the value function, not the gradient).

    population_firing_rate

    fn population_firing_rate(per_neuron_spikes : Array[Array[Float]], t_start : Float, t_end : Float) -> Float

    Mean firing rate (Hz) across a population (one element per neuron). Silent neurons contribute 0 Hz. Window is [t_start, t_end].

    population_from_dend_neuron

    fn population_from_dend_neuron(param : DendNeuronParameter) -> DendNeuronParameter

    Convert a DendNeuronParameter to the corresponding concrete neuron constructor (Tripod / BallAndStick). Mirrors Julia's Population(param::DendNeuronParameter). Returns the DendNeuronParameter unchanged for downstream Tripod::new / BallAndStick::new consumption; the caller is responsible for dispatching on tree_type.

    population_indices

    fn population_indices(pops : Array[(String, Int)]) -> Array[PopIndex]

    Assign non-overlapping 1-based index ranges to a list of populations. pops is (label, neuron_count) pairs in assignment order.

    Julia source: population_indices(pops). Behaviour:

    pops = [(E, 10), (I, 5)] → [PopIndex("E", 1, 10), PopIndex("I", 11, 15)]

    The ranges are non-overlapping and the union covers 1..N_total. We work with a flat Array[(String, Int)] rather than a NamedTuple (MoonBit has no first-class NamedTuple type).

    population_total_rate

    fn population_total_rate(per_neuron_spikes : Array[Array[Float]], t_start : Float, t_end : Float) -> Float

    Total firing rate (Hz) across the population — sum of per-neuron rates. Useful for population_total_rate / n_neurons == mean.

    positional_embedding_backward

    fn positional_embedding_backward(pe : PositionalEmbedding, d_output : Array[Float], seq_len : Int) -> Array[Float]

    Backward for learnable positional embedding. d_output has shape (seq_len, d_model). Returns a fresh d_weight of shape (max_len, d_model) — only the first seq_len rows carry gradient.

    positional_embedding_forward

    fn positional_embedding_forward(pe : PositionalEmbedding, seq_len : Int) -> Array[Float]

    Slice the embedding table to the first seq_len rows. Returns a fresh row-major array of length seq_len * d_model. Caller must ensure seq_len <= max_len.

    pow_fast

    fn pow_fast(x : Float, y : Float) -> Float

    Approximate x^y via expf(y * logf(x)) for x ≥ 0. (Used for priority^(alpha).)

    ppo_collect_batch

    fn ppo_collect_batch(env : GridWorld, policy : LinearSoftmaxPolicy, n_episodes : Int, gamma : Float, max_steps : Int, seed : UInt64) -> PpoBatch

    Collect a batch of episodes under the current policy. Records old_probs[a_t | s_t] for use as the importance-ratio denominator.

    ppo_gae_collect_batch

    fn ppo_gae_collect_batch(env : GridWorld, policy : LinearSoftmaxPolicy, value_net : LinearValueNet, n_episodes : Int, max_steps : Int, seed : UInt64) -> Array[PpoGaeEpisode]

    Collect a batch of episodes recording V at every step.

    ppo_gae_update

    fn ppo_gae_update(policy : LinearSoftmaxPolicy, value_net : LinearValueNet, episodes : Array[PpoGaeEpisode], gamma : Float, gae_lambda : Float, clip_eps : Float, lr_policy : Float, lr_value : Float) -> Unit

    Apply one PPO update step using GAE advantages. Also updates the value network via simple MSE regression on the returns.

    clip_eps is the PPO clipping epsilon (typically 0.2).

    ppo_kl_collect_batch

    fn ppo_kl_collect_batch(env : GridWorld, policy : LinearSoftmaxPolicy, value_net : LinearValueNet, n_episodes : Int, gamma : Float, max_steps : Int, seed : UInt64) -> PpoKlBatch

    Collect a batch with GAE-computed advantages. Uses a value net for advantage computation but does NOT update it (this variant focuses on the KL penalty term; assumes V is held fixed or pre-trained).

    ppo_kl_update_step

    fn ppo_kl_update_step(policy : LinearSoftmaxPolicy, batch : PpoKlBatch, kl_beta : Float, lr : Float) -> Float

    One PPO-KL update step. Returns the mean per-state KL after the update (for monitoring). Modifies policy.w in place.

    ppo_update_step

    fn ppo_update_step(policy : LinearSoftmaxPolicy, batch : PpoBatch, clip_eps : Float, lr : Float) -> Unit

    Apply one PPO clipped-surrogate update step. Modifies policy.w in place. clip_eps is typically 0.2.

    prediction

    fn prediction(scores : Array[Float]) -> Int

    Predict class index = argmax(scores). Convenience wrapper.

    q_argmax

    fn q_argmax(q : Array[Float]) -> Int

    Greedy action: argmax over Q-values (first occurrence on ties).

    q_forward

    fn q_forward(net : LinearQNet, x : Array[Float]) -> Array[Float]

    Compute Q-values for state x. Returns Array[Float] of length n_actions.

    qnet_copy

    fn qnet_copy(dst : LinearQNet, src : LinearQNet) -> Unit

    Copy weights from src into dst. Used to sync target net from online net.

    quantile_float

    fn quantile_float(arr : Array[Float], q : Float) -> Float

    Compute the q quantile of a Float32 array (q in [0, 1]). Mirrors Julia's quantile(p_values, fraction). Uses insertion sort (small arrays in practice — p_values.length() == nnz).

    rand_f32

    fn rand_f32(r : Xoshiro, n : Int) -> Array[Float]

    Generate n Float32 values matching rand(Float32, n) in Julia.

    rand_f64

    fn rand_f64(r : Xoshiro, n : Int) -> Array[Double]

    Generate n Float64 values matching rand(Float64, n) in Julia.

    rand_value

    fn rand_value(n : Int, p1 : Float, p2 : Float, rng : Xoshiro) -> Array[Float]

    Generate n random Float32 values uniformly distributed between min(p1, p2) and max(p1, p2). Mirrors Julia's rand_value(N, p1, p2) = min + rand(N) * abs(p1-p2).

    If p1 == p2, all values are exactly p1 (degenerate range).

    receptor_current

    fn receptor_current(g : Array[Float], v : Array[Float], r : Receptor, nmda_dep : NMDAVoltageDependency, out : Array[Float]) -> Unit

    Compute the synaptic current contribution of one receptor across N neurons: I[i] = gsyn * g[i] * (v[i] - e_rev) * B(v[i]). For non-NMDA, B(v) = 1.

    receptors_current

    fn receptors_current(g_matrix : Array[Float], v : Array[Float], rs : Receptors, nmda_dep : NMDAVoltageDependency, out : Array[Float]) -> Unit

    Convenience: compute current for all 4 receptors of a Receptors collection. out[i] is the sum across receptors.

    record_one

    fn record_one(m : Monitor, t : Float) -> Unit

    Take one snapshot, honouring rec_step.

    record_one_adex

    fn record_one_adex(m : MonitorAdEx, t : Float) -> Unit

    record_one_sinexp

    fn record_one_sinexp(m : MonitorAdExSinExp, t : Float) -> Unit

    Take one snapshot, appending to data and times.

    record_zero

    fn record_zero(model : Model) -> Unit

    Record the initial state (t=0).

    recurrent_sac_actor_update

    fn recurrent_sac_actor_update(sac : RecurrentSac, ep : RecurrentSacEpisode, lr : Float, rng : Xoshiro) -> Unit

    Actor update via BPTT. Computes the analytic gradient of L_actor = Σ_t Σ_a π_t(a) · [α · log π_t(a) − Q1(s_t, a)] and applies SGD to the actor's w_out + LSTM cell.

    recurrent_sac_critic_update

    fn recurrent_sac_critic_update(sac : RecurrentSac, ep : RecurrentSacEpisode, gamma : Float, lr : Float, rng : Xoshiro) -> Float

    Critic MSE update via BPTT. Applies SGD on q1 and q2 (online critics only; target critics updated separately via Polyak). Returns mean squared TD error.

    recurrent_sac_get_alpha

    fn recurrent_sac_get_alpha(sac : RecurrentSac) -> Float

    Current α = exp(log_alpha).

    recurrent_sac_rollout_episode

    fn recurrent_sac_rollout_episode(env_n_cells : Int, sac : RecurrentSac, max_steps : Int, rng : Xoshiro) -> RecurrentSacEpisode

    Roll out one episode on the corridor POMDP, returning the trace.

    recurrent_sac_soft_target_seq

    fn recurrent_sac_soft_target_seq(sac : RecurrentSac, ep : RecurrentSacEpisode, gamma : Float, rng : Xoshiro) -> Array[Float]

    Compute the soft Bellman target sequence for an episode. For terminal states, target = r. For non-terminal, uses π_{t+1} from policy forward over next_obs_seq and Q̂_min from target critics.

    recurrent_sac_soft_update

    fn recurrent_sac_soft_update(sac : RecurrentSac, tau : Float) -> Unit

    Polyak soft target update for both critics.

    recurrent_sac_update_alpha

    fn recurrent_sac_update_alpha(sac : RecurrentSac, ep : RecurrentSacEpisode, rng : Xoshiro) -> Float

    Update log_alpha using the episode's empirical mean negative entropy. mean_neg_entropy = (1/T) Σ_t Σ_a π_t(a) · log π_t(a) delta = mean_neg_entropy − target_entropy log_alpha ← log_alpha + alpha_lr · delta

    relu_backward

    fn relu_backward(input : Array[Float], d_output : Array[Float]) -> Array[Float]

    ReLU backward pass.

    • input : forward input (length n)
    • d_output : upstream gradient (length n) returns : d_input (length n), new array

    relu_forward

    fn relu_forward(input : Array[Float]) -> Array[Float]

    Apply ReLU element-wise: out[i] = max(input[i], 0). Returns a new array (does not mutate the input).

    reset_balanced

    fn reset_balanced(s : BalancedStimulus) -> Unit

    Reset per-neuron state (r, noise, fire) to the constructor defaults without re-allocating the arrays. Useful between independent simulation runs against the same IF population.

    reset_heterogeneous_full

    fn reset_heterogeneous_full(m : HeterogeneousModel) -> Unit

    Reset a model fully: clear time, monitors, and all plasticity state.

    • Time is reset to zero (matches reset_time_heterogeneous).
    • Monitors are cleared via clear_records.
    • STDP entries: per-entry reset (depends on entry type).
    • STP entries: per-entry reset.

    For populations, this helper does NOT reset neural state (membrane potentials, spike histories) — that is intentionally out of scope; the user can call per-pop reset helpers if needed.

    reset_time

    fn reset_time(t : Time) -> Unit

    Reset the time to zero. Matches reset_time!(T).

    reset_time_heterogeneous

    fn reset_time_heterogeneous(m : HeterogeneousModel) -> Unit

    Reset the model's simulation clock back to 0. Does not clear monitors or weight values — only the time tracker.

    residual_block_backward

    fn residual_block_backward(b : ResidualBlock, cache : ResidualCache, d_output : Array[Float]) -> (Array[Float], ResidualGrads)

    Backward pass for a residual block. Returns (d_input, grads).

    residual_block_forward

    fn residual_block_forward(b : ResidualBlock, x : Array[Float], n : Int, c : Int, h : Int, w : Int) -> (Array[Float], ResidualCache)

    Forward pass for a residual block.

    rl_action_down

    let rl_action_down : Int

    rl_action_left

    let rl_action_left : Int

    rl_action_right

    let rl_action_right : Int

    rl_action_up

    let rl_action_up : Int

    Action encoding: 0=up, 1=down, 2=left, 3=right.

    rl_one_hot

    fn rl_one_hot(state : Int, n : Int) -> Array[Float]

    One-hot encode a state index into a length n vector.

    rmsprop_init

    fn rmsprop_init(weight_len : Int, bias_len : Int) -> RMSpropState

    Allocate zero-initialised RMSprop state.

    rmsprop_update_arrays

    fn rmsprop_update_arrays(weight : Array[Float], bias : Array[Float], d_weight : Array[Float], d_bias : Array[Float], state : RMSpropState, lr : Float, alpha : Float, eps : Float) -> (Array[Float], Array[Float], RMSpropState)

    One RMSprop update at raw-array level.

    rmsprop_update_conv

    fn rmsprop_update_conv(param : Conv2dParam, d_weight : Array[Float], d_bias : Array[Float], state : RMSpropState, lr : Float, alpha : Float, eps : Float) -> (Conv2dParam, RMSpropState)

    RMSprop wrapper for Conv2dParam.

    rmsprop_update_linear

    fn rmsprop_update_linear(param : LinearParam, d_weight : Array[Float], d_bias : Array[Float], state : RMSpropState, lr : Float, alpha : Float, eps : Float) -> (LinearParam, RMSpropState)

    RMSprop wrapper for LinearParam.

    rosenbrock_value

    fn rosenbrock_value(x_val : Float, y_val : Float) -> Float

    Forward-only value of the Rosenbrock function. Used as the reference for finite-difference gradient checks.

    route_pre_to_post

    fn route_pre_to_post(pre : IF, post : AdExSinExp, weights : Array[Float], exc : Bool, inh : Bool) -> Unit

    Route pre-synaptic spikes to post-synaptic glu/gaba buffers. weights is a dense n_pre × n_post matrix (row-major). For each pre-neuron that fires, the row's weights are added to post.glu (if exc == true) or post.gaba (if inh == true).

    row_major_get

    fn row_major_get(w : Array[Float], n : Int, i : Int, j : Int) -> Float

    let s : Float

    sac_actor_update

    fn sac_actor_update(sac : Sac, states : Array[Int], lr : Float) -> Unit

    Update the SAC actor for one batch (gradient ascent on α·log π - Q). Computes the analytic gradient of L_actor = Σ_a π(a|s) · [α · log π(a|s) - Q(s, a)] Note: signs — we want to MINIMISE -Q + α·log π, so we ASCEND on -Q + α·log π (or equivalently DESCEND on Q - α·log π).

    For each transition: ∇_W[i, j] of (Σ_a π(a|s) · Q(s, a)) = Σ_a Q(s, a) · ∇_W[i, j] π(a|s) = Σ_a Q(s, a) · (1{i==a} - π(a|s)) · x[j]

    ∇_W[i, j] of (-α · Σ_a π(a|s) · log π(a|s)) = -α · Σ_a [(log π(a|s) + 1) · ∇_W[i, j] π(a|s)] = -α · Σ_a [(log π(a|s) + 1) · (1{i==a} - π(a|s)) · x[j]]

    sac_auto_alpha_actor_update

    fn sac_auto_alpha_actor_update(sac : SacAutoAlpha, states : Array[Int], lr : Float) -> Unit

    Actor update using current α = exp(log_alpha). Same structure as v0.38.0 sac_actor_update, but α is read live.

    sac_auto_alpha_critic_update

    fn sac_auto_alpha_critic_update(sac : SacAutoAlpha, states : Array[Int], actions : Array[Int], rewards : Array[Float], next_states : Array[Int], dones : Array[Bool], gamma : Float, lr : Float) -> Float

    Critic MSE update (same as v0.38.0).

    sac_auto_alpha_get

    fn sac_auto_alpha_get(sac : SacAutoAlpha) -> Float

    Current α = exp(log_alpha).

    sac_auto_alpha_sample_action

    fn sac_auto_alpha_sample_action(policy : LinearSoftmaxPolicy, state : Int, rng : Xoshiro) -> Int

    Sample action from the softmax policy.

    sac_auto_alpha_soft_target

    fn sac_auto_alpha_soft_target(sac : SacAutoAlpha, next_state : Int, reward : Float, done : Bool, gamma : Float) -> Float

    Soft Bellman target (discrete). Identical to v0.38.0 except α is computed from log_alpha instead of the fixed alpha field.

    sac_auto_alpha_soft_update

    fn sac_auto_alpha_soft_update(sac : SacAutoAlpha, tau : Float) -> Unit

    Polyak soft target update (identical to v0.38.0).

    sac_auto_alpha_update_alpha

    fn sac_auto_alpha_update_alpha(sac : SacAutoAlpha, states : Array[Int]) -> Float

    Update log_alpha using the batch's empirical mean negative entropy. Returns the delta applied (= mean_neg_entropy − target_entropy).

    The new α reflects how far the current policy is from the target: delta > 0 → policy too deterministic → α grows delta < 0 → policy too random → α shrinks

    sac_critic_update

    fn sac_critic_update(sac : Sac, states : Array[Int], actions : Array[Int], rewards : Array[Float], next_states : Array[Int], dones : Array[Bool], gamma : Float, lr : Float) -> Float

    Update the SAC critics for one batch. Returns mean squared TD error.

    sac_sample_action

    fn sac_sample_action(policy : LinearSoftmaxPolicy, state : Int, rng : Xoshiro) -> Int

    Sample action from the softmax policy.

    sac_soft_target

    fn sac_soft_target(sac : Sac, next_state : Int, reward : Float, done : Bool, gamma : Float) -> Float

    Compute the SAC soft Bellman target for a single transition. Q_target(s, a) = r + γ · (1 - done) · Σ_a' π(a'|s') · [Q̂_min(s', a') - α · log π(a'|s')]

    sac_soft_update

    fn sac_soft_update(sac : Sac, tau : Float) -> Unit

    Soft (Polyak) update of target Q-nets: θ_target ← τ · θ + (1 - τ) · θ_target

    sample_categorical

    fn sample_categorical(probs : Array[Float], rng : Xoshiro) -> (Int, Float)

    Sample action from a categorical distribution. Returns (action, log_prob). log_prob is clamped to -20 to avoid -inf.

    sample_gaussians

    fn sample_gaussians(n : Int, rng : Xoshiro) -> Array[Float]

    Sample N standard-normals via Box-Muller (consumes 2·N draws).

    sample_julia_initial_ge_gi

    fn sample_julia_initial_ge_gi(n : Int, rng : Xoshiro) -> (Array[Float], Array[Float])
    The MoonBit Float32 normalised form: ge = (1.5 * z + 4.0) * 10.0F where z is a Box-Muller N(0, 1) draw (clamped to ≥ 0 to avoid negative conductances). Box-Muller is consumed from rng per draw — two draws per neuron (one for ge, one for gi). With Xoshiro-driven Float32 RNG, the qualitative mean/std matches Julia but the exact sequence differs.

    sample_poisson

    fn sample_poisson(rng : Xoshiro, lambda : Float) -> Int

    Per-step Poisson sampling with Knuth's algorithm. For each iteration we multiply p by a uniform draw and check whether p has dropped below exp(-λ). The number of rands consumed before the drop is the Poisson(λ) sample.

    Standard form: p = 1 k = 0 repeat: k += 1 p *= rand() until p < exp(-λ) return k - 1

    The k - 1 corrects for the off-by-one in the iteration count (k counts the number of rands drawn; the number of rands strictly greater than the threshold is k - 1).

    Safety: capped at max(100, ceil(10*λ)) iterations to prevent runaway loops in pathological cases.

    scnn_accuracy

    fn scnn_accuracy(logits_flat : Array[Float], labels : Array[Int], batch : Int, n_classes : Int) -> Float

    Compute classification accuracy on a batch.

    scnn_deterministic_forward

    fn scnn_deterministic_forward() -> Bool

    Check that running forward multiple times with the same input gives identical outputs (determinism check).

    scnn_loss_is_reasonable

    fn scnn_loss_is_reasonable() -> Bool

    Sanity check that the loss is finite, positive, and bounded.

    scnn_predict_batch

    fn scnn_predict_batch(logits_flat : Array[Float], batch : Int, n_classes : Int) -> Array[Int]

    Predict argmax class for a batch of logits (length batch * n_classes).

    scnn_single_step

    fn scnn_single_step() -> (Float, Int)

    Run a single forward + loss + backward pass. Returns (loss, total_params_with_grads).

    scnn_train_n_steps

    fn scnn_train_n_steps(n_steps : Int, lr : Float, seed : UInt64) -> (Float, Float, Float, Float)

    Run K SGD training steps on the synthetic dataset. Returns (initial_loss, final_loss, initial_acc, final_acc).

    scnn_train_step_with_sgd

    fn scnn_train_step_with_sgd(model : SimpleCNN, input : Array[Float], labels : Array[Int], batch : Int, n_classes : Int, lr : Float) -> (Float, Float)

    Run a single forward + loss + backward pass; apply SGD updates to the model's layers in place. Returns (loss, accuracy) on the batch.

    second

    let second : Float

    set_dt

    fn set_dt(t : Time, v : Float) -> Unit

    Set the integration step size dt in ms. Matches set_dt!.

    set_time

    fn set_time(t : Time, v : Float) -> Unit

    Set the current simulation time in ms. Matches Julia's set_time!(T, time) (not used in the inner loop, but available).

    set_tt

    fn set_tt(t : Time, v : Int) -> Unit

    Set the current integration timestep counter. Matches set_tt!.

    sgd_momentum_init

    fn sgd_momentum_init(weight_len : Int, bias_len : Int) -> SGDMomentumState

    Allocate zero-initialised momentum state for a parameter of given weight_len and bias_len.

    sgd_momentum_update_arrays

    fn sgd_momentum_update_arrays(weight : Array[Float], bias : Array[Float], d_weight : Array[Float], d_bias : Array[Float], state : SGDMomentumState, lr : Float, momentum : Float) -> (Array[Float], Array[Float], SGDMomentumState)

    One SGD-with-momentum step. Mutates and returns the velocity state alongside the updated (weight, bias) arrays.

    v_w <- momentum * v_w + d_weight v_b <- momentum * v_b + d_bias weight <- weight - lr * v_w bias <- bias - lr * v_b

    sgd_momentum_update_conv

    fn sgd_momentum_update_conv(param : Conv2dParam, d_weight : Array[Float], d_bias : Array[Float], state : SGDMomentumState, lr : Float, momentum : Float) -> (Conv2dParam, SGDMomentumState)

    SGD-with-momentum wrapper for Conv2dParam.

    sgd_momentum_update_linear

    fn sgd_momentum_update_linear(param : LinearParam, d_weight : Array[Float], d_bias : Array[Float], state : SGDMomentumState, lr : Float, momentum : Float) -> (LinearParam, SGDMomentumState)

    SGD-with-momentum wrapper for LinearParam.

    sgd_update_arrays

    fn sgd_update_arrays(weight : Array[Float], bias : Array[Float], d_weight : Array[Float], d_bias : Array[Float], lr : Float) -> (Array[Float], Array[Float])

    Vanilla SGD: returns a fresh (weight, bias) pair after applying weight -= lr * d_weight and bias -= lr * d_bias element-wise.

    sgd_update_conv

    fn sgd_update_conv(param : Conv2dParam, d_weight : Array[Float], d_bias : Array[Float], lr : Float) -> Conv2dParam

    SGD wrapper for Conv2dParam. Returns a fresh Conv2dParam with updated weights / biases.

    sgd_update_linear

    fn sgd_update_linear(param : LinearParam, d_weight : Array[Float], d_bias : Array[Float], lr : Float) -> LinearParam

    SGD wrapper for LinearParam. Returns a fresh LinearParam with updated weights / biases.

    siemens

    let siemens : Float

    sigmoid_f32

    fn sigmoid_f32(x : Float) -> Float

    Float32 sigmoid / logistic. Computed as 1.0 / (1.0 + expf(-x)). Matches Julia's 1 / (1 + expf(-x)) (single-precision exp).

    sim_any_pops

    fn sim_any_pops(pops : Array[AnyPop], dt : Float) -> Unit

    sim_any_pops_for

    fn sim_any_pops_for(pops : Array[AnyPop], duration : Float, dt : Float) -> Unit

    sim_for

    fn sim_for(model : Model, duration : Float) -> Unit

    Run the simulation for duration ms starting from t=0.

    simple_cnn_backward

    fn simple_cnn_backward(m : SimpleCNN, caches : Array[LayerCache], d_output : Array[Float], n : Int) -> (Array[Float], Array[LayerGrad])

    Backward pass. d_output has shape [n, 10, 1, 1].

    simple_cnn_forward

    fn simple_cnn_forward(m : SimpleCNN, input : Array[Float], n : Int) -> (Array[Float], Array[LayerCache], Int, Int, Int, Int)

    Forward pass for SimpleCNN. Input must be [n, in_c, 28, 28].

    simulate_step_if

    fn simulate_step_if(pop : IF, dt : Float) -> Unit

    Run one IF integrate step (synapse decay + synaptic current + neuron update). Convenience wrapper for the three-step IF integration.

    single_exp_synapse_step

    fn single_exp_synapse_step(vars : SingleExpSynapseVars, param : SingleExpSynapse, glu : Array[Float], gaba : Array[Float], dt : Float) -> Unit

    Update step for SingleExpSynapseVars: spike-driven conductance update + exponential decay. Caller is responsible for clearing glu / gaba buffers afterwards.

    sinusoidal_position_encoding

    fn sinusoidal_position_encoding(max_len : Int, d_model : Int) -> Array[Float]

    Sinusoidal positional encoding table (non-trainable).

    For position pos ∈ [0, max_len) and embedding index j ∈ [0, d_model):

    PE[pos, j] = sin(pos / 10000^(⌊j/2⌋·2 / d_model)) if j is even PE[pos, j] = cos(pos / 10000^(⌊j/2⌋·2 / d_model)) if j is odd

    Returns row-major flat array of length max_len * d_model.

    softmax

    fn softmax(scores : Array[Float]) -> Array[Float]

    Softmax: out[i] = exp(scores[i]) / Σ exp(scores[j]). Numerically stable — subtracts the max score before exponentiating to avoid overflow on large inputs.

    softmax_row

    fn softmax_row(x : Array[Float]) -> Array[Float]

    Numerically-stable softmax.

    solve_linear

    fn solve_linear(a : Array[Array[Float]], b : Array[Float]) -> Array[Float]

    Solve a linear system A · x = b via Gauss-Jordan elimination with partial pivoting. a is dim × dim, b is dim. Returns x. If A is singular (no pivot available), returns a zero vector.

    soma_gaba

    fn soma_gaba() -> GABAergic

    Soma GABA (GABAergic for IF / AdEx soma): GABAa(-70, 0.5, 10, 2.0)
    • GABAb(-90, 30, 400, 0.006). Matches Julia SomaGABA.

    soma_glu

    fn soma_glu() -> Glutamatergic

    Soma Glu (Glutamatergic for IF / AdEx soma): AMPA(0, 1, 6, 0.7)
    • NMDA(0, 1, 100, 0.15). Matches Julia SomaGlu.

    soma_nmda

    fn soma_nmda() -> NMDAVoltageDependency

    Soma NMDA voltage-dependency preset. mg = 1.0, b = 3.57, k = -0.062. Matches Julia SomaNMDA. (Functionally a no-op alias for the existing NMDAVoltageDependency::soma().)

    soma_receptors

    fn soma_receptors() -> Receptors

    Soma receptors: SomaGlu + SomaGABA. Matches Julia SomaReceptors.

    spike_surrogate

    fn spike_surrogate(u : Array[Float], vt : Float, beta : Float) -> SpikeSurrogate

    Compute both the hard spike and its surrogate gradient in one pass.

    spikeformer_argmax

    fn spikeformer_argmax(probs : Array[Float]) -> Int

    Argmax over a 1D Float array → index of the maximum value. On ties, returns the first (lowest) occurrence.

    spikeformer_dataset_build

    fn spikeformer_dataset_build(seed : UInt64) -> (Array[Float], Array[Int])

    Synthetic 28×28 dataset: 10 classes × 10 samples = 100 images. Each class has a distinct geometric pattern (line, half-frame, cross, etc.) + ~5% pixel flip noise.

    spikeformer_extract_patches

    fn spikeformer_extract_patches(images : Array[Float], n : Int, patch_dim : Int, patch_h : Int, patch_w : Int, img_h : Int, img_w : Int) -> Array[Float]

    Convert (N, 1, 28, 28) → (N, 49, 16) patches (NCHW row-major).

    spikeformer_one_hot

    fn spikeformer_one_hot(label : Int, n_classes : Int) -> Array[Float]

    One-hot label vector (length n_classes).

    spikeformer_softmax

    fn spikeformer_softmax(x : Array[Float]) -> Array[Float]

    Numerically-stable softmax over a 1D vector.

    spikeformer_xent

    fn spikeformer_xent(probs : Array[Float], target : Int) -> Float

    Cross-entropy: -log(prob[target]).

    spiking_attention_backward

    fn spiking_attention_backward(cache : SpikingMultiHeadAttnCache, d_output : Array[Float], sa : SpikingMultiHeadAttention) -> (Array[Float], MHAGrad)

    Backward-compat: backward signature unchanged.

    spiking_attention_forward

    fn spiking_attention_forward(x : Array[Float], sa : SpikingMultiHeadAttention, mask : Array[Float]) -> (Array[Float], SpikingMultiHeadAttnCache)

    Backward-compat: forward signature unchanged.

    spiking_connect

    fn spiking_connect(c : SpikingSynapse, pre : Int, post : Int, w : Float) -> Unit

    Add (or replace) a single connection pre -> post with weight w. Matches Julia's connect!(c, j, i, w) (1-based post, 1-based pre) but our API is 1-based pre, 1-based post to match SNN's connect!(EE, n, n+1, 50) chain.jl usage.

    spiking_cross_attention_backward

    fn spiking_cross_attention_backward(cache : SpikingCrossAttnCache, d_output : Array[Float], sa : SpikingCrossAttention) -> (Array[Float], Array[Float], MHAGrad)

    Cross-attention backward. Returns (d_x_q, d_x_kv, MHAGrad). Q and KV paths share the MHAGrad bundle since the projection structure is identical.

    spiking_cross_attention_forward

    fn spiking_cross_attention_forward(x_q : Array[Float], x_kv : Array[Float], sa : SpikingCrossAttention, mask : Array[Float]) -> (Array[Float], SpikingCrossAttnCache)

    Cross-attention forward.

    x_q : (seq_len_q, d_model) x_kv : (seq_len_kv, d_model) mask : (num_heads, seq_len_q, seq_len_kv) additive, or empty. returns : out of shape (seq_len_q, d_model).

    spiking_multi_head_attention_backward

    fn spiking_multi_head_attention_backward(cache : SpikingMultiHeadAttnCache, d_output : Array[Float], sa : SpikingMultiHeadAttention) -> (Array[Float], MHAGrad)

    Multi-head self-attention backward.

    spiking_multi_head_attention_forward

    fn spiking_multi_head_attention_forward(x : Array[Float], sa : SpikingMultiHeadAttention, mask : Array[Float]) -> (Array[Float], SpikingMultiHeadAttnCache)

    Multi-head self-attention forward. mask is the same additive mask as standard MHA (pass empty array to skip).

    spiking_self_attention_new

    fn spiking_self_attention_new(d_model : Int, beta : Float, seed : UInt64) -> SpikingMultiHeadAttention

    Convenience: build a single-head spiking self-attention by calling SpikingMultiHeadAttention::new(d_model, 1, beta, seed). No separate struct — single-head is just multi-head with num_heads=1.

    spiking_transformer_block_backward

    fn spiking_transformer_block_backward(cache : SpikingTransformerBlockCache, d_output : Array[Float], block : SpikingTransformerBlock) -> (Array[Float], SpikingTransformerBlockGrad)

    Backward pass.

    spiking_transformer_block_forward

    fn spiking_transformer_block_forward(x : Array[Float], block : SpikingTransformerBlock, mask : Array[Float]) -> (Array[Float], SpikingTransformerBlockCache)

    Forward pass.

    sqrtf

    fn sqrtf(x : Float) -> Float

    Float32 sqrt via libm. We already have math_sqrt_f32 exposed in

    srnn_backward

    fn srnn_backward(cache : SRNNCache, srnn : SRNN) -> (Array[Float], Array[Float], Array[Float], Array[Float], Array[Float])

    Backward pass. Returns grads as 5-tuple of arrays.

    srnn_dataset_build

    fn srnn_dataset_build(seed : UInt64) -> (Array[Float], Array[Int])

    Synthetic 2-class dataset: 16 samples (8 per class). Class 0: x[0] in [0.7, 1.0]. Class 1: x[0] in [0, 0.3].

    srnn_eval_loss

    fn srnn_eval_loss(images : Array[Float], labels : Array[Int], n_steps : Int, beta : Float, srnn : SRNN, seed : UInt64) -> Float

    Compute mean loss over the full dataset (no param updates).

    srnn_forward

    fn srnn_forward(x : Array[Float], n_steps : Int, target : Int, srnn : SRNN, beta : Float) -> (Float, SRNNCache)

    Forward pass. Returns (loss, cache).

    srnn_poisson_encode

    fn srnn_poisson_encode(input : Array[Float], n_steps : Int, dt : Float, max_rate : Float, rng : Xoshiro) -> Array[Float]

    Poisson-encode a static "image" vector into a (n_steps, n_in) spike train. Spike probability per (t, i) = min(1, input[i] * max_rate * dt).

    srnn_train_step

    fn srnn_train_step(image : Array[Float], label : Int, n_steps : Int, lr : Float, beta : Float, srnn : SRNN, rng : Xoshiro) -> Float

    One training step: forward + BPTT + param update (SGD with clip=1.0).

    st_image_concat_t

    fn st_image_concat_t(a : STImage, b : STImage) -> STImage raise Failure

    Concatenate two STImages along the time axis. Shapes must agree on (B, C, H, W).

    st_image_repeat_t

    fn st_image_repeat_t(img : Image, t : Int) -> STImage

    Repeat a single 4D [N=1, C, H, W] image t times along a new time axis. Result is [T, 1, C, H, W] with identical frames.

    stacked_lstm_backward

    fn stacked_lstm_backward(hs : Array[Array[Array[Float]]], target : Array[Array[Float]], cache : StackedLstmCache, _h0s : Array[Array[Float]], _c0s : Array[Array[Float]], param : StackedLstmParam) -> (Array[Array[Float]], Array[Array[Float]], Array[Array[Float]], Array[LstmCellGrad])

    BPTT backward for the stacked LSTM. Returns (d_xs, d_h0s, d_c0s, grads). The grads array has one LstmCellGrad per layer.

    stacked_lstm_forward

    fn stacked_lstm_forward(xs : Array[Array[Float]], h0s : Array[Array[Float]], c0s : Array[Array[Float]], param : StackedLstmParam) -> (Array[Array[Array[Float]]], Array[Array[Array[Float]]], StackedLstmCache)

    Run the stacked LSTM forward over an input sequence. Returns (hs, cs, cache) where hs[t][l] is the hidden state at time t of layer l, and cs[t][l] is the cell state.

    stacked_lstm_identity_dataset

    fn stacked_lstm_identity_dataset(seq_len : Int, d_x : Int, d_target : Int, seed : UInt64) -> (Array[Array[Float]], Array[Array[Float]])

    Generate a synthetic identity-shift task. xs has dim d_x; target has dim d_target. For the simple demo, use d_x = d_h = d_target so the loss is on a target matching the top layer's hidden dim.

    stacked_lstm_sequence_loss

    fn stacked_lstm_sequence_loss(hs : Array[Array[Array[Float]]], target : Array[Array[Float]]) -> Float

    Mean-squared-error loss on the top layer's hidden states vs target sequence. Returns -1 on shape mismatch.

    stacked_lstm_sgd_step

    fn stacked_lstm_sgd_step(param : StackedLstmParam, grads : Array[LstmCellGrad], lr : Float) -> Unit

    Apply SGD to all layers using per-layer grads.

    stacked_lstm_train_n_steps

    fn stacked_lstm_train_n_steps(xs : Array[Array[Float]], target : Array[Array[Float]], h0s : Array[Array[Float]], c0s : Array[Array[Float]], param : StackedLstmParam, n_steps : Int, lr : Float, clip : Float) -> Float

    Run K-step SGD training on a single input/target sequence. Optionally clips grads to [-clip, clip] per layer.

    stacked_lstm_zero_init

    fn stacked_lstm_zero_init(n_layers : Int, d_h : Int) -> (Array[Array[Float]], Array[Array[Float]])

    Build zero initial hidden/cell states for a stacked LSTM.

    start_interval

    fn start_interval(x : Float, intervals : Array[Array[Float]]) -> Float

    Returns the start of the interval containing x, or -1.0F if none.

    stdp_antisymmetric_plot

    fn stdp_antisymmetric_plot(param : STDPAntiSymmetric, y_max? : Float, width? : Int, height? : Int) -> Unit

    Plot the STDPAntiSymmetric weight update as a function of post-synaptic trace to_y[i]. Shows dW = αpre - (A_y / τ_y) * to_y[i] over a sweep of to_y values from 0 to y_max (default 5.0).

    stdp_antisymmetric_step

    fn stdp_antisymmetric_step(w : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool], colptr : Array[Int], rowptr : Array[Int], vars : STDPAntiSymmetricVariables, param : STDPAntiSymmetric, dt : Float) -> Unit

    Apply one step of STDPAntiSymmetric. Mirrors Julia's plasticity! for STDPAntiSymmetric (STDP_structured.jl).

    stdp_confavreux_step

    fn stdp_confavreux_step(w : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool], colptr : Array[Int], rowptr : Array[Int], vars : STDPVariables, param : STDPConfavreux2025, t_now : Float, dt : Float) -> Unit

    One step of the Confavreux 2025 STDP rule. Mirrors the loop structure of stdp_step (Gerstner): continuous decay of tpre/tpost traces + spike bump, then a single pass over all stored connections that applies both the pre-fire and post-fire contributions using the post index recovered from colptr[s]. Weights are clamped to [w_min, w_max] after each connection update.

    CSR layout (matches the rest of the SNN port):
    • rowptr[j]..rowptr[j+1] lists the non-zero positions for row j (pre-neuron j). Each connection s in that range has colptr[s] = i (post-neuron).

    Trace model: vars.tpre[j] and vars.tpost[i] are continuously decayed each step with exp(-dt/tau_pre) and exp(-dt/tau_post), then bumped by 1.0F on a spike. This is mathematically equivalent to Julia's time-since-last-spike formulation: Δpre[j] after spike at t_1 and dt later is tpre[j] * exp(-dt/tau_pre), matching tpre_0 * exp(-(t_2 - t_1)/tau_pre) + 1f0 * exp(-(t_3 - t_2)/tau_pre).

    stdp_confraveux2025_step

    fn stdp_confraveux2025_step(state : StdpConfavreux2025State, param : StdpConfavreux2025Param, fire_pre : Array[Bool], fire_post : Array[Bool], w : Array[Float], now : Float) -> Unit

    stdp_gerstner_step

    fn stdp_gerstner_step(state : StdpGerstnerState, param : StdpGerstnerParam, fire_pre : Array[Bool], fire_post : Array[Bool], w : Array[Float], now : Float) -> Unit

    One step of STDP Gerstner plasticity. now is the current simulation time (ms). fire_pre[j] / fire_post[i] are boolean spike indicators. w is the dense weight matrix indexed by (i*n_pre + j) for post i, pre j; updated in-place.

    stdp_kernel_plot

    fn stdp_kernel_plot(param : STDPGerstner, t_max? : Float, width? : Int, height? : Int) -> Unit

    Plot the Gerstner STDP kernel to stdout using ASCII art. Uses gerstner_kernel to compute ΔW(Δt) at each column. Δt sweeps from -t_max to +t_max (default 100 ms), stepping through width columns (default 60). Uses the y-axis to show ΔW values and the x-axis to show Δt in ms.

    stdp_mexican_hat_plot

    fn stdp_mexican_hat_plot(param : STDPMexicanHat, x_max? : Float, width? : Int, height? : Int) -> Unit

    Plot the STDPMexicanHat kernel as a function of x = (ln(tpre/tpost))^2. Sweeps x from 0 to x_max (default 5.0) and shows the resulting ΔW. The kernel is (1 - x) * exp(-x / sqrt(2)) which starts at 1 at x=0, crosses zero at x=1, and decays to small negative values.

    stdp_mexican_hat_step

    fn stdp_mexican_hat_step(w : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool], colptr : Array[Int], rowptr : Array[Int], tpre : Array[Float], tpost : Array[Float], param : STDPMexicanHat, dt : Float) -> Unit

    Apply one step of STDPMexicanHat. Mirrors Julia's plasticity! for STDPMexicanHat (STDP_traces.jl).

    w is the CSR sparse weight buffer (vals), pre_fire[j] and post_fire[i] are the firing booleans, colptr[s] is the post-syn neuron for connection s, rowptr[j]..rowptr[j+1] are the connections from pre-syn neuron j. tpre[j] and tpost[i] are exponentially decaying traces (advanced in-place). t_now is the current sim time.

    stdp_step

    fn stdp_step(w : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool], colptr : Array[Int], rowptr : Array[Int], vars : STDPVariables, param : STDPGerstner, t_now : Float, dt : Float) -> Unit

    Apply one step of Gerstner STDP. Mutates w in place based on fire_pre[j] (pre-synaptic neuron j fired this step) and fire_post[i] (post-synaptic neuron i fired this step). t_now is the current simulation time (ms).

    w is laid out in the same row-major CSR format as SparseMatrixCSR.vals: w[s] is the weight for the s-th connection. To map s to (j, i), the caller can use the SpikingSynapse's matrix.colptr / .rowptr.

    stdp_symmetric_step

    fn stdp_symmetric_step(w : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool], colptr : Array[Int], rowptr : Array[Int], vars : STDPSymmetricVariables, param : STDPSymmetric, t_now : Float, dt : Float) -> Unit

    One step of STDPSymmetric.

    Trace model (continuous-time Euler): tr_x[j] += dt * (-tr_x[j]) / tau_x if fireJ[j]: bump tr_x[j] tr_y[j] += dt * (-tr_y[j]) / tau_y if fireJ[j]: bump tr_y[j] to_x[i] += dt * (-to_x[i]) / tau_x if fireI[i]: bump to_x[i] to_y[i] += dt * (-to_y[i]) / tau_y if fireI[i]: bump to_y[i]

    Weight update per stored connection (s = (j -> post_idx)): if pre_fire[j]: w[s] += alpha_pre + (a_x / (2tau_x) * to_x[post_idx] - a_y / (2tau_y) * to_y[post_idx]) if post_fire[post_idx]: w[s] += alpha_post + (a_x / (2tau_x) * tr_x[j] - a_y / (2tau_y) * tr_y[j]) Clamp w[s] to [w_min, w_max].

    CSR layout: rowptr[j]..rowptr[j+1] lists non-zero positions for row j (pre-neuron j); colptr[s] = i (post-neuron). We walk rowptr[j] once per j and apply both pre-fire and post-fire contributions in the same loop (same fused pattern as the other STDP variants).

    stdp_weight_decorrelated

    fn stdp_weight_decorrelated(param : STDPGerstner) -> Float

    Compute the mean weight change for STDP under uncorrelated Poisson pre/post spike trains (decorrelated regime). For exponentially-decaying kernels (Gerstner), the integral of the LTP side equals (A_pre * τ_pre - A_post * τ_post) when post fires before pre (LTD).

    This is the MoonBit equivalent of Julia's SNN.stdp_weight_decorrelated(stdp_param) which computes the mean ΔW analytically for an uncorrelated pre/post Poisson regime.

    Formula (from Rubinov et al 2011; assumes symmetric bounds): = A_pre * τ_pre - A_post * τ_post

    Returns the scalar mean ΔW.

    step_adex

    fn step_adex(p : AdEx, dt : Float) -> Unit

    Update the AdEx neuron state for one step. Bit-exact port of update_neuron! for AdEx{Float32}.

    step_adex_het

    fn step_adex_het(p : AdExHet, dt : Float) -> Unit

    Update the heterogeneous AdEx neuron state for one step. Bit-exact port of Julia's update_neuron! for AdEx{Float32} = Vector{Float32}: v[i] = ifelse(fire[i], vr[i], v[i]) tabs[i] -= 1; if tabs[i] > 0 continue w[i] += dt * (a[i] * (v[i] - el[i]) - w[i]) / τw[i] v[i] += dt * (-(v[i] - el[i]) + ΔT[i]*exp((v[i]-θ[i])/ΔT[i]) - R[i] * (syn_curr[i] + w[i]) + R[i] * I[i]) / τm[i] θ[i] += dt * (Vt[i] - θ[i]) / τA (τA scalar from spike) fire[i] = v[i] >= 0 ...

    step_adex_model

    fn step_adex_model(model : AdExModel, time : Time) -> Unit

    step_adex_sinexp

    fn step_adex_sinexp(p : AdExSinExp, dt : Float) -> Unit

    Update the AdExSinExp neuron state for one step. Bit-exact port of step_adex (AdEx integration is identical; only the synapse step function differs).

    step_ballandstick

    fn step_ballandstick(p : BallAndStick, dt : Float) -> Unit

    Update BallAndStick for one timestep. Mirrors Julia's BallAndStick integrate! order bit-exactly:
    1. update_synapses! (soma + dendrite)
    2. synaptic_current! (soma + dendrite)
    3. Heun: predictor pass → save dv → corrector pass → save dv_temp
    4. for each neuron:
      • decrement tabs, update threshold
      • if tabs > τabs/dt (backprop): v_s=AP, v_d += dt*axial
      • elsif tabs > 0 (abs refractory): v_s=Vr, v_d += dt*axial
      • else (active):
        • detect fire (predictive): v_s + corrector_dv*dt >= -10mV
        • if fire: v_s=AP, w+=b, θ+=At, tabs=tabs_steps, continue (skip Heun apply)
        • else: apply Heun correction to v_s, v_d, w_s

    Julia writes to Δv in-place during both Heun passes; after the loop, Δv holds the corrector's values. Spike detection uses the corrector's Δv_s. The corrector's exp_term can explode when v_s is near or above threshold; Julia's fire && continue skips the bogus v_d update. We must do the same to avoid v_d runaway.

    tabs_steps = round(Int, (up + τabs) / dt) — Julia sets the full backprop + refractory duration. We must use both up and tabs_const (τabs in Julia) — using τabs alone gives half the refractory window.

    step_heterogeneous

    fn step_heterogeneous(m : HeterogeneousModel, dt : Float) -> Unit

    Drive a heterogeneous model for one timestep. Order matches SNN's sim! inner loop:
    1. stimulate! all input sources
    2. forward! all connections (propagate pre-synaptic spikes)
    3. apply STDP! (mutate weights for plasticity-bearing synapses)
    4. integrate! each population
    5. record! all monitors
    6. update_time!

    step_heterogeneous_with_record

    fn step_heterogeneous_with_record(m : HeterogeneousModel, dt : Float, record : (Float) -> Unit) -> Unit

    step_heterogeneous with an extra callback after the step. The callback is invoked with the model's current time (Float). Useful for hooking in custom diagnostics or logging.

    step_hetrec

    fn step_hetrec(p : HetRec, dt : Float) -> Unit

    Integrate one Euler step for HetRec. Bit-exact port of Julia's integrate!(p::HetRec, param, dt):

    1. dendritic Euler step: v_d += dt * (-v_d - is) / τd
    2. soma input from dendrites: for each synapse s of soma i: v_s[i] += (W[s] * v_d[I[s]] - v_s[i]) * dt / τm
    3. decrement tabs; if tabs > 0, skip firing logic
    4. update trace: trace += dt * (-trace / τrate) if not refractory: trace += (v_s[i] - trace) / τrate
    5. stochastic fire: if randcache[i] < r[i] * sigmoid(steepness * (v_s - trace)) * dt then fire[i] = true; tabs[i] = τabs/dt; trace[i] += 1

    The caller is expected to:
    • inject synaptic currents into p.is_ before calling step (e.g., via SpikingSynapse forward to the :is target)
    • call hetrec_refresh_random(p, rng) once per step to refresh p.randcache (matches Julia's rand!(randcache)).

    step_hh

    fn step_hh(p : HH, dt : Float) -> Unit

    Update the HH neuron state for one step. Bit-exact port of integrate!(p::HH, param, dt).

    step_inhomogeneous_poisson

    fn step_inhomogeneous_poisson(p : InhomogeneousPoisson, dt : Float, rng : Xoshiro) -> Unit

    Integrate one time-step of Inhomogeneous Poisson process.

    Steps (matches Julia's integrate!):
    1. Re-draw uniform noise cache.
    2. Reset fire[i] = false for all i.
    3. For each neuron i: a. re = randcache_beta[i] - 0.5 b. noise[i] = (noise[i] - re) * (1 - dt/τ) + re (Ornstein-Uhlenbeck smoothing toward 0) c. Erate = max(r0/2 * max(noise[i] * β, 1.0) + r[i], 0.0) d. r[i] += (r0 - Erate) / rate_timescale * dt e. p_spike = 1 - exp(-Erate * dt) f. fire[i] = rand_uniform() < p_spike

    step_iz

    fn step_iz(p : IZ, dt : Float) -> Unit

    Update the IZ neuron state for one step. Bit-exact port of integrate!(p::IZ, param, dt).

    step_iz_synapses

    fn step_iz_synapses(p : IZ, dt : Float) -> Unit

    Update only the IZ synaptic conductances for one step: ge[i] += dt * -ge[i] / τe gi[i] += dt * -gi[i] / τi Useful for separating synapse decay from membrane integration.

    step_iz_synapses_postspike

    fn step_iz_synapses_postspike(p : IZ, dt : Float) -> Unit

    Update only the IZ synaptic conductances for one step, with postspike handling: the tabs countdown is decremented per neuron, and the synapse decay continues even during refractory (matches Julia's step_synapses! for IF/AdEx on refractory neurons).

    step_iz_with_postspike

    fn step_iz_with_postspike(p : IZ, dt : Float) -> Unit

    Update the IZ neuron state for one step WITH postspike refractory handling. Per-neuron tabs countdown is decremented; while tabs > 0, the membrane update (loops 2-3) is skipped but the synaptic decay (loop 1) still runs. On spike, tabs is reset to tabs_const (via step_iz's fire detection + this loop's reset). If p.tabs_const == 0 (e.g. from IZ::new), behaviour is identical to step_iz.

    step_ml

    fn step_ml(p : MorrisLecar, dt : Float) -> Unit

    Update the MorrisLecar neuron state for one step. Bit-exact port of integrate!(p::MorrisLecar, param, dt).

    step_model

    fn step_model(model : Model, time : Time) -> Unit

    Run one simulation step.

    step_neuron

    fn step_neuron(p : IF, dt : Float) -> Unit

    Update the IF membrane potential for one step.

    step_neuron_id

    fn step_neuron_id(p : Identity, dt : Float) -> Unit

    Integrate Identity for one timestep. Fire[i] is true if g[i] > 0.

    step_poisson

    fn step_poisson(p : Poisson, dt : Float) -> Unit

    Update the Poisson population state for one step. Bit-exact port of integrate!(p::Poisson, param, dt).

    step_receptor

    fn step_receptor(g : Array[Float], h : Array[Float], target : Array[Float], r : Receptor, dt : Float) -> Unit

    Update one receptor's 2-state ODE (g, h) for one time step. target[i] is the input pulse added to h[i] (multiplied by α). Post-condition: target is consumed (caller resets to 0).

    step_receptors_tripod_synapse

    fn step_receptors_tripod_synapse(s : ReceptorSynapseTripod, dt : Float) -> Unit

    Run the 2-state ODE for each receptor on the target compartment. Populates s.ge_out and s.gi_out (sum across receptors in the same compartment).

    Consumes the input buffer (glu_X or gaba_X) — it is reset to 0 after step_receptor consumes it.

    step_synapses

    fn step_synapses(p : IF, dt : Float) -> Unit

    Update the synaptic state variables (he, hi, ge, gi) for one step. Bit-exact port of DoubleExpSynapse.update_synapses!.

    step_tripod

    fn step_tripod(p : Tripod, dt : Float) -> Unit

    Update Tripod for one timestep. Mirrors Julia's Tripod integrate! order bit-exactly:
    1. update_synapses! (soma + both dendrites)
    2. synaptic_current! (soma + both dendrites)
    3. Heun: predictor → save → corrector → save
    4. for each neuron:
      • decrement tabs, update threshold
      • if tabs > 0 (refractory): v_s=Vr, v_d1,v_d2 += dt*axial
      • else (active):
        • detect fire (predictive): v_s + corrector_dv*dt >= -10mV
        • if fire: v_s=AP, w+=b, θ+=At, tabs=tabs_steps, continue
        • else: apply Heun correction to v_s, v_d1, v_d2, w_s

    See step_ballandstick doc for why we must skip v_d apply on fire (corrector exp_term can explode when v_s is near/above threshold; Julia's fire && continue prevents v_d runaway).

    tabs_steps = round(Int, (up + τabs) / dt) — Julia's full backprop + refractory duration. Using only τabs gives half.

    step_tripod_het

    fn step_tripod_het(p : TripodHet, dt : Float) -> Unit

    Update TripodHet for one timestep. Same Julia integrate! order as step_tripod, but reads per-neuron AdEx params.

    step_wc

    fn step_wc(p : WilsonCowan, dt : Float) -> Unit

    Integrate one Euler step. Resets g to 0 after using it (matches Julia's fill!(g, zero(eltype(g))) which is uncommented in some versions of RateSynapse's forward! — required to prevent g from accumulating over time).

    stimulate_any

    fn stimulate_any(s : AnyStim, t : Time, dt : Float) -> Unit

    Dispatch one stimulate! call based on the enum variant.

    stimulate_balanced

    fn stimulate_balanced(s : BalancedStimulus, time : Float, dt : Float) -> Unit

    One stimulation step. Updates fire, r, noise, and adds samples to ge / gi arrays. See file header for the algorithm.

    stimulate_current_array

    fn stimulate_current_array(s : CurrentStimulusArray) -> Unit

    Apply the array-targeted current stimulus for one step.

    stimulate_current_if

    fn stimulate_current_if(s : CurrentStimulusIF) -> Unit

    Apply the current stimulus for one step.

    stimulate_empty

    fn stimulate_empty(_stim : EmptyStimulus, _param : EmptyParam, _t : Float, _dt : Float) -> Unit

    stimulate_empty — no-op (matches Julia's empty body).

    stimulate_if

    fn stimulate_if(s : PoissonStimulusIF, time : Float, dt : Float) -> Unit

    Apply the Poisson stimulus for one time step. time is the current simulation time (ms), unused for PoissonFixed but kept for compatibility with PoissonInterval / PoissonVariable.

    stimulate_layer

    fn stimulate_layer(s : PoissonLayerStimulus, time : Float, dt : Float) -> Unit

    One-step stimulation: draw Poisson for each active source, and if it fires, add the (pre, post) weight to the post-synaptic receptor (glu for "ge", gaba for "gi").

    time is the current simulation time (ms); unused for fixed-rate Poisson but kept for API symmetry with time-varying variants.

    stimulate_layer_ball

    fn stimulate_layer_ball(s : PoissonLayerStimulusBallAndStick, time : Float, dt : Float) -> Unit

    One-step stimulation: draw Poisson for each active source, and if it fires, add the (pre, post) weight to the post-synaptic compartment buffer.

    stimulate_layer_tripod

    fn stimulate_layer_tripod(s : PoissonLayerStimulusTripod, time : Float, dt : Float) -> Unit

    One-step stimulation: draw Poisson for each active source, and if it fires, add the (pre, post) weight to the post-synaptic compartment buffer (glu or gaba, :soma/:d1/:d2).

    stimulate_noise

    fn stimulate_noise(param : CurrentNoise, i_target : Array[Float], rng : Xoshiro) -> Unit

    stimulate_noise — apply the current-noise step to an external current buffer.

    .I[t+1] = (I_base + N(0, noise_sigma)) * (1 - α) + I[t] * α

    Caller supplies the target i_target : Array[Float] (the population's I field). Random noise is drawn from the Xoshiro RNG via Box-Muller.

    stimulate_spiketime

    fn stimulate_spiketime(s : SpikeTimeStimulus, t : Float, w : Float) -> Unit

    Advance the SpikeTimeStimulus by one step. If the current time has reached the next spike time, deposit weight into g[post_idx]] and advance the pointer. Mutates fire[j] to mark the pre-synaptic neuron that fired.

    str_name

    fn str_name(pre : String, post : String, k : String) -> String

    Generate a String name for a connection between populations: "pre_to_post" or "pre_to_post_k" if k is provided. Mirrors Julia's str_name(pre, post, k=nothing).

    str_name_single

    fn str_name_single(pre : String, k : String) -> String

    One-argument form: str_name(pre, k=nothing) → "pre" or "pre_k". Mirrors Julia's str_name(pre::String, k=nothing).

    sttc_matrix

    fn sttc_matrix(trains : Array[Array[Float]], dt : Float, istart : Float, iend : Float) -> Array[Array[Float]]

    STTC matrix for N spike trains. Returns a N×N Float matrix (symmetric, diagonal = 1.0). Each (i, j) entry is sttc_pair for the i-th and j-th trains.

    sttc_pair

    fn sttc_pair(a : Array[Float], b : Array[Float], dt : Float, istart : Float, iend : Float) -> Float

    STTC between two spike trains. Sorts the inputs (in-place copies) before computing.

    sttc_pair_sorted

    fn sttc_pair_sorted(a : Array[Float], b : Array[Float], ta : Float, tb : Float, dt : Float) -> Float

    Compute the STTC value between two spike trains (must be pre-sorted ascending; use the wrapper sttc_pair which sorts for you).

    synaptic_current

    fn synaptic_current(p : IF) -> Unit

    Compute synaptic current into each neuron.

    synaptic_current_adex_multi

    fn synaptic_current_adex_multi(p : AdExMultiTimescale, param : AdExMultiTimescaleParameter) -> Unit

    synaptic_current! — sum g[i, n] * (v[i] - E_rev) over all receptors.

    Receptors indexed by glu_receptors use E_e; gaba_receptors use E_i. Result stored in p.syn_curr[i].

    synaptic_current_confraveux2025

    fn synaptic_current_confraveux2025(vars : Confavreux2025SynapseVars, param : Confavreux2025Parameter, v : Array[Float], syn_curr : Array[Float]) -> Unit

    synaptic_current! — outputs the weighted AMPA + NMDA + GABA current.

    syn_curr[i] = (α * gAMPA[i] + (1-α) * gNMDA[i]) * (v[i] - E_e) + gGABA[i] * (v[i] - E_i)

    synaptic_current_double_exp

    fn synaptic_current_double_exp(vars : DoubleExpCurrentSynapseVars, syn_curr : Array[Float]) -> Unit

    synaptic_current! — computes syn_curr[i] = -(ge[i] - gi[i]).

    Matches Julia's syncurr[i] = -(ge[i] - gi[i]) exactly. Note the sign convention: positive ge is excitatory (pushes v up), so -(ge - gi) > 0 for excitatory drive; opposite for inhibition.

    synaptic_current_single_exp

    fn synaptic_current_single_exp(vars : SingleExpSynapseVars, param : SingleExpParameter, v : Array[Float], syn_curr : Array[Float]) -> Unit

    synaptic_current! — conductance-based current.

    syn_curr[i] = gsyn_e * ge[i] * (v[i] - E_e) + gsyn_i * gi[i] * (v[i] - E_i)

    synaptic_receptors_default

    fn synaptic_receptors_default(n : Int) -> Array[Float]

    synaptic_receptors_default — returns a (glu, gaba) tuple of per-neuron zero buffers of length N.

    Mirrors Julia's default synaptic_receptors(synapse, N): (glu = zeros(N), gaba = zeros(N))

    synaptic_receptors_pair

    fn synaptic_receptors_pair(n : Int) -> Array[Float]

    Allocate the default (glu, gaba) receptor buffer pair (length N each).

    synaptic_turnover

    fn synaptic_turnover(syn : SpikingSynapse, p_rewire? : Float, mu? : Float, p_values? : Array[Float]) -> Unit

    Rewire connections below the probability threshold. Mirrors Julia's synaptic_turnover!. For each pre neuron j:
    1. Identify candidates s where p_values[s] > p_rewire (Julia: p_values[s] > p_rewire && continue skips these).
    2. The set of "all post" minus "current post" is plausible_post.
    3. Replace each candidate with a new post; new weight is rand(Normal(μ, sqrt(μ))).

    Note: MoonBit doesn't have first-class function references, so the p_new callback is currently ignored — new targets are sampled uniformly from the plausible_post set.

    synaptic_variables_for

    fn synaptic_variables_for(synapse_tag : String, n : Int) -> Array[Float]

    Dispatch on synapse type tag to allocate matching vars buffer. synapse_tag is one of "Delta", "SingleExp", "DoubleExp", "Current", "DoubleExpCurrent", "Confraveux2025". Returns None (empty Array) for unknown tags.

    synthetic_mnist_build

    fn synthetic_mnist_build() -> (Array[Array[Float]], Array[Int])

    Build a 10-class synthetic 28x28 dataset. Each class has a fixed binary pattern plus ~5% pixel-flip noise.

    t5_backward

    fn t5_backward(rp : T5RelativePosition, d_bias : Array[Float], seq_len : Int) -> Array[Float]

    Backward: returns d_weight of shape (num_heads, 2*max_distance+1).

    d_bias[h, i, j] is the upstream gradient on the bias entry at (h, i, j). Sum over (i, j) bucketed by bucket(j - i):

    d_weight[h, b] = sum_{i, j: bucket(j-i)==b} d_bias[h, i, j]

    t5_compute_bias

    fn t5_compute_bias(rp : T5RelativePosition, seq_len : Int) -> Array[Float]

    Compute the per-head bias matrix of shape (num_heads × seq_len × seq_len). bias[h, i, j] is indexed by the clamped relative offset (j - i).

    tanhf

    fn tanhf(x : Float) -> Float

    Float32 hyperbolic tangent: matches Julia's tanh(Float32, x) and the C tanhf function.

    tensor_add

    fn tensor_add(a : Tensor, b : Tensor) -> Tensor

    Tensor add (with broadcasting).

    tensor_div

    fn tensor_div(a : Tensor, b : Tensor) -> Tensor

    Tensor divide (with broadcasting).

    tensor_log_softmax

    fn tensor_log_softmax(x : Tensor) -> Tensor

    Log-softmax along the last axis: out[i, j] = x[i, j] - max_j(x) - log(sum_k exp(...)). Numerically stable.

    tensor_matmul

    fn tensor_matmul(a : Tensor, b : Tensor) -> Tensor

    2D matrix multiply: c[m, n] = sum_k a[m, k] * b[k, n]. Inputs a is shape [m, k], b is shape [k, n]. Returns a fresh Tensor of shape [m, n]. Naive O(mnk) loop.

    tensor_mul

    fn tensor_mul(a : Tensor, b : Tensor) -> Tensor

    Tensor multiply (with broadcasting).

    tensor_softmax

    fn tensor_softmax(x : Tensor) -> Tensor

    Softmax along the last axis of a 2D Tensor. Numerically stable: subtracts max(x) before exp. out[i, j] = exp(x[i, j] - max_j(x[i, j])) / sum_j exp(...)

    tensor_sub

    fn tensor_sub(a : Tensor, b : Tensor) -> Tensor

    Tensor subtract (with broadcasting).

    tile_fraction

    fn tile_fraction(spiketrain : Array[Float], dt : Float, istart : Float, iend : Float) -> Float

    Tile fraction for a sorted spike train in [istart, iend] with window ±dt. The total length covered by union of [t-dt, t+dt] intervals around each spike, divided by (iend - istart + 2*dt). Mirrors Julia's _tile_fraction(spiketrain, Δt, istart, iend).

    time_in_interval

    fn time_in_interval(x : Float, intervals : Array[Array[Float]]) -> Bool

    Returns true iff x is in any interval (start ≤ x ≤ end). intervals : Array[Array[Float]] where each inner array has 2 entries (start, end). Empty interval list → false.

    top_k

    fn top_k(scores : Array[Float], k : Int) -> Array[Int]

    Top-k indices from scores, sorted by score in descending order. Returns the first k (or fewer if scores has fewer than k elements). Ties: insertion order wins (stable).

    top_k_prediction

    fn top_k_prediction(scores : Array[Float], k : Int) -> Array[Int]

    Predict top-k class indices. Convenience wrapper.

    train_actor_critic

    fn train_actor_critic(env : GridWorld, policy : LinearSoftmaxPolicy, value_net : LinearValueNet, n_episodes : Int, gamma : Float, lr_policy : Float, lr_value : Float, max_steps : Int, seed : UInt64) -> Float

    Train A2C for n_episodes. Returns the mean undiscounted episode return.

    train_double_dqn

    fn train_double_dqn(env : GridWorld, q_net : LinearQNet, target : LinearQNet, n_episodes : Int, gamma : Float, lr : Float, epsilon_start : Float, epsilon_end : Float, max_steps : Int, buffer_capacity : Int, warmup_episodes : Int, sync_every : Int, batch_size : Int, seed : UInt64) -> Float

    Train Double DQN for n_episodes. Same loop structure as train_dqn but uses Double DQN update rule. Returns mean episode return over all episodes.

    train_dqn

    fn train_dqn(env : GridWorld, q_net : LinearQNet, target : LinearQNet, n_episodes : Int, gamma : Float, lr : Float, epsilon_start : Float, epsilon_end : Float, max_steps : Int, buffer_capacity : Int, warmup_episodes : Int, sync_every : Int, batch_size : Int, seed : UInt64) -> Float

    Train DQN for n_episodes episodes. Each episode:
    1. Roll out under ε-greedy policy with linear ε-decay.
    2. Store transitions in replay buffer.
    3. After warmup, sample mini-batches and apply gradient step.
    4. Periodically copy online → target.

    Returns the mean undiscounted episode return over the last 10 episodes.

    train_dqn_n_step

    fn train_dqn_n_step(env : GridWorld, q_net : LinearQNet, target : LinearQNet, n_episodes : Int, gamma : Float, lr : Float, epsilon_start : Float, epsilon_end : Float, max_steps : Int, buffer_capacity : Int, warmup_episodes : Int, sync_every : Int, batch_size : Int, n_step : Int, seed : UInt64) -> Float

    N-step variant of train_dqn. Maintains an NStepBuffer of the last n transitions; whenever the buffer is full (or the episode ends), the corresponding n-step transition is flushed into the replay buffer.

    Returns the mean undiscounted episode return over the last 10 episodes.

    train_dueling_dqn

    fn train_dueling_dqn(env : GridWorld, q_net : DuelingQNet, target : DuelingQNet, n_episodes : Int, gamma : Float, lr : Float, epsilon_start : Float, epsilon_end : Float, max_steps : Int, buffer_capacity : Int, warmup_episodes : Int, sync_every : Int, batch_size : Int, seed : UInt64) -> Float

    Train Dueling DQN. Returns mean episode return.

    train_gru_reinforce

    fn train_gru_reinforce(env_n_cells : Int, policy : GruPolicy, n_episodes : Int, gamma : Float, lr : Float, max_steps : Int, seed : UInt64) -> Float

    Train REINFORCE-GRU on the corridor POMDP for n_episodes.

    train_lstm_ppo

    fn train_lstm_ppo(env_n_cells : Int, policy : LstmPolicy, n_iters : Int, n_episodes_per_iter : Int, gamma : Float, clip_eps : Float, lr : Float, max_steps : Int, seed : UInt64) -> Float

    Train recurrent PPO for n_iters. Returns the last mean per-step advantage.

    train_lstm_reinforce

    fn train_lstm_reinforce(env_n_cells : Int, policy : LstmPolicy, n_episodes : Int, gamma : Float, lr : Float, max_steps : Int, seed : UInt64) -> Float

    Train REINFORCE-LSTM on the corridor POMDP for n_episodes. Each episode randomly sets goal_side to 0 or 1, and starts the agent at the centre cell. The observation sequence uses a fixed length (max_steps); all observations use the same starting position but include the indicator at every step, so the LSTM can attend to it across the sequence. Returns the mean episode return.

    train_noisy_dqn

    fn train_noisy_dqn(env : GridWorld, online : NoisyQNet, target : NoisyQNet, n_episodes : Int, gamma : Float, lr : Float, max_steps : Int, buffer_capacity : Int, warmup_episodes : Int, sync_every : Int, batch_size : Int, seed : UInt64) -> Float

    Train NoisyDQN for n_episodes episodes. No ε-greedy schedule — exploration is purely from the noise added in noisy_q_forward. Returns the mean undiscounted episode return over the last 10 episodes (eval-mode argmax, μ weights).

    train_ppo

    fn train_ppo(env : GridWorld, policy : LinearSoftmaxPolicy, n_iters : Int, n_episodes_per_iter : Int, gamma : Float, clip_eps : Float, lr : Float, max_steps : Int, seed : UInt64) -> Float

    Train PPO for n_iters iterations, each collecting a batch of n_episodes_per_iter episodes and applying one PPO update. Returns the mean episode return over the last iteration.

    train_ppo_gae

    fn train_ppo_gae(env : GridWorld, policy : LinearSoftmaxPolicy, value_net : LinearValueNet, n_iters : Int, n_episodes_per_iter : Int, gamma : Float, gae_lambda : Float, clip_eps : Float, lr_policy : Float, lr_value : Float, max_steps : Int, seed : UInt64) -> Float

    Train PPO-GAE for n_iters. Returns the mean episode return over the last iteration's batch.

    train_ppo_kl

    fn train_ppo_kl(env : GridWorld, policy : LinearSoftmaxPolicy, value_net : LinearValueNet, n_iters : Int, n_episodes_per_iter : Int, gamma : Float, kl_beta : Float, lr : Float, max_steps : Int, seed : UInt64) -> Float

    Train PPO-KL for n_iters. Returns the last mean episode return.

    train_recurrent_sac

    fn train_recurrent_sac(env_n_cells : Int, sac : RecurrentSac, n_episodes : Int, gamma : Float, lr_critic : Float, lr_actor : Float, tau : Float, max_steps : Int, seed : UInt64) -> Float

    Full training loop. Runs n_episodes episodes on the corridor POMDP. After each episode, applies critic, actor, α, and target critic updates. Returns the mean episode return.

    train_reinforce

    fn train_reinforce(env : GridWorld, policy : LinearSoftmaxPolicy, n_episodes : Int, gamma : Float, lr : Float, max_steps : Int, seed : UInt64) -> Float

    Train REINFORCE for n_episodes episodes. Returns the mean episode return (undiscounted).

    train_sac

    fn train_sac(env : GridWorld, sac : Sac, n_episodes : Int, gamma : Float, lr_critic : Float, lr_actor : Float, tau : Float, max_steps : Int, buffer_capacity : Int, warmup_episodes : Int, batch_size : Int, seed : UInt64) -> Float

    Train SAC for n_episodes episodes. Returns the mean episode return over the last n_episodes runs.

    train_sac_auto_alpha

    fn train_sac_auto_alpha(env : GridWorld, sac : SacAutoAlpha, n_episodes : Int, gamma : Float, lr_critic : Float, lr_actor : Float, tau : Float, max_steps : Int, buffer_capacity : Int, warmup_episodes : Int, batch_size : Int, seed : UInt64) -> Float

    Full training loop. Same structure as v0.38.0 train_sac, with the addition of sac_auto_alpha_update_alpha(...) after each actor step.

    transformer_block_backward

    fn transformer_block_backward(cache : TransformerBlockCache, d_output : Array[Float], block : TransformerBlock) -> (Array[Float], TransformerBlockGrad)

    Backward pass. Returns (d_input, TransformerBlockGrad).

    transformer_block_forward

    fn transformer_block_forward(x : Array[Float], block : TransformerBlock, mask : Array[Float]) -> (Array[Float], TransformerBlockCache)

    Forward pass. mask is forwarded to MHA (pass empty array to skip).

    tripod_dend_receptors

    fn tripod_dend_receptors() -> Receptors

    Tripod dendrite receptors: EyalGluDend + MilesGabaDend. Matches Julia TripodDendReceptors.

    tripod_dend_step_synapses

    fn tripod_dend_step_synapses(p : Tripod, dt : Float) -> Unit

    Single-exp synapse step for both dendrites.

    tripod_het_dend_step_synapses

    fn tripod_het_dend_step_synapses(p : TripodHet, dt : Float) -> Unit

    Single-exp synapse step for both dendrites (same as Tripod).

    tripod_het_soma_step_synapses

    fn tripod_het_soma_step_synapses(p : TripodHet, dt : Float) -> Unit

    Single-exp synapse step for the soma (same as Tripod).

    tripod_het_syn_curr_dends

    fn tripod_het_syn_curr_dends(p : TripodHet) -> Unit

    Synaptic current into both dendrites (passive dendrite with synapse).

    tripod_het_syn_curr_soma

    fn tripod_het_syn_curr_soma(p : TripodHet) -> Unit

    Synaptic current into soma (single-exp synapse with reversal potentials).

    tripod_param_new

    fn tripod_param_new() -> DendNeuronParameter

    TripodParameter factory — 2 dendrites (default ds=(200μm, 400μm) each, human physiology, s→d1 + s→d2).

    tripod_soma_receptors

    fn tripod_soma_receptors() -> Receptors

    Tripod soma receptors (4-receptor collection). Julia equivalent: Receptors(DuarteGluSoma, MilesGabaSoma) — but only AMPA + GABAa slots populated; NMDA + GABAb slots set to "duplicate" presets since the soma routing typically uses glu_receptors=[1] (AMPA-only).

    Build pattern: ampa=DuarteGluSoma, nmda=DuarteGluSoma (placeholder), gabaa=MilesGabaSoma, gabab=MilesGabaSoma (placeholder).

    tripod_soma_step_synapses

    fn tripod_soma_step_synapses(p : Tripod, dt : Float) -> Unit

    Single-exp synapse step for the soma.

    tripod_syn_curr_dends

    fn tripod_syn_curr_dends(p : Tripod) -> Unit

    Synaptic current: dendrite1 and dendrite2.

    tripod_syn_curr_soma

    fn tripod_syn_curr_soma(p : Tripod) -> Unit

    Synaptic current: soma = ge*(v-E_e)gsyn + gi(v-E_i)*gsyn.

    ts_acf

    fn ts_acf(x : Array[Float], max_lag : Int) -> Array[Float]

    Autocorrelation function (ACF) at lags 0..max_lag. Returns an array of length max_lag + 1 where out[0] = 1 always. Uses the biased estimator: ρ(k) = Σ(x[i]-μ)(x[i+k]-μ) / Σ(x[i]-μ)², i.e. the denominator is fixed at the lag-0 value (rather than n - k).

    ts_cumsum

    fn ts_cumsum(x : Array[Float]) -> Array[Float]

    Cumulative sum: out[0] = x[0], out[i] = out[i-1] + x[i]. Returns an empty array for empty input. The inverse of differencing (modulo the leading value): if d = ts_diff(x) then x can be reconstructed by [x[0]] ++ (x[0] .+ ts_cumsum(d)).

    ts_diff

    fn ts_diff(x : Array[Float]) -> Array[Float]

    First-order differencing: d[i] = x[i+1] - x[i]. Returns an array of length n - 1; returns an empty array for inputs of length 0 or 1.

    ts_diff_n

    fn ts_diff_n(x : Array[Float], d : Int) -> Array[Float]

    d-order differencing — equivalent to applying ts_diff d times in sequence. For d = 0 returns x unchanged; for d ≥ 1 returns an array of length n - d. This is the "I(d)" component of ARIMA(p, d, q).

    ts_mean

    fn ts_mean(x : Array[Float]) -> Float

    1D arithmetic mean. Returns 0.0 for an empty input.

    ts_pacf

    fn ts_pacf(x : Array[Float], max_lag : Int) -> Array[Float]

    Partial autocorrelation function (PACF) at lags 0..max_lag. Computed via the Levinson-Durbin recursion on top of ts_acf. Returns an array of length max_lag + 1 where out[0] = 1. The PACF value at lag k is the correlation between x[t] and x[t-k] after removing the linear effect of the intermediate x[t-1], ..., x[t-k+1]. For a pure AR(p) process the PACF cuts off after lag p, which is how ARIMA(p, 'p') selects its order. For a constant series (zero variance), every PACF lag is 1.0 (matching the all-1 behaviour of ACF on the same edge case).

    ts_std

    fn ts_std(x : Array[Float]) -> Float

    1D standard deviation (sqrt of population variance).

    ts_var

    fn ts_var(x : Array[Float]) -> Float

    1D variance with population divisor n (matches Julia's Statistics.var(...; corrected=false)). Returns 0.0 for empty input.

    turnover_plasticity

    fn turnover_plasticity(c : Turnover, step_count : Int, dt : Float) -> Unit

    Periodic variant — runs the activity-trace update and the structural-plasticity rewiring every τ / dt steps. Mirrors Julia's outer plasticity!(c, param::ActivityDependentTurnover,dt, T) which gates on ((tt) % round(Int, τ / dt)) < dt.
    let uA : Float

    let uF : Float

    let uM : Float

    let um : Float

    um2

    let um2 : Float

    update_neuron_extended_if

    fn update_neuron_extended_if(p : ExtendedIF, param : ExtendedIFParameter, dt : Float) -> Unit

    update_neuron! — Euler step with multi-receptor synaptic term
    • optional dendritic interaction.

    Julia's update order preserved bit-exactly:
    1. tabs countdown (skip if refractory)
    2. compute dv (synaptic term + dendritic α-gating)
    3. v += dt * dv
    4. fire = v > Vt
    5. v = ifelse(fire, Vr, v)
    6. tabs = ifelse(fire, round(τabs/dt), tabs)

    update_neuron_gif

    fn update_neuron_gif(p : GIF, param : GIFParameter, dt : Float) -> Unit

    update_neuron! — IF-style Euler + adaptation (only if τw > 0).

    Julia's if fire: w += b and w += dt * (a*(v - El) - w) / τw run regardless of τw when the if-guard hits. We replicate the exact form.

    update_neuron_ifcanahp

    fn update_neuron_ifcanahp(p : IFCANAHP, param : IFCANAHParameter, dt : Float) -> Unit

    update_neuron! — Ca drive + CAN-AHP gating + membrane update.

    Julia's Ca[i] = (Ca0 - Ca[i]) / τCa is a drive-toward-Ca0 step (not a standard Euler decay); we preserve the exact form so the unit test matches Julia's reference trajectory.

    update_soma_adex_multi

    fn update_soma_adex_multi(p : AdExMultiTimescale, param : AdExMultiTimescaleParameter, dt : Float) -> Unit

    update_soma! — AdEx membrane with dynamic spike threshold.

    Julia's update order:
    1. refractory countdown (skip)
    2. v += dt/tm * (-(v - El) + R*(-w + I) - R*syn_curr) (AdEx exponential term ΔT is implicit in update_soma!; we use the linear form to match our existing AdEx port.)
    3. fire = v > θ
    4. v = ifelse(fire, Vr, v)
    5. tabs = ifelse(fire, round(τabs/dt), tabs)
    6. theta: if fire: theta += At; theta += dt*(Vt - theta)/τt
    7. w += b on fire; w += dt*(a*(v - El) - w)/τw

    update_spike_ifcanahp

    fn update_spike_ifcanahp(p : IFCANAHP, param : IFCANAHParameter, dt : Float) -> Unit

    update_spike! — fire detection, reset, Ca bump, refractory countdown.

    Run AFTER update_neuron! to avoid interfering with the membrane update in the same step.

    update_synapses_adex_multi

    fn update_synapses_adex_multi(p : AdExMultiTimescale, param : AdExMultiTimescaleParameter, dt : Float) -> Unit

    update_synapses! — per-receptor 2-state ODE (rise h, decay g).

    Mirrors Julia's g[i, n] = exp(-dt * τd⁻) * (g[i, n] + dt * h[n][i]) h[n][i] = exp(-dt * τr⁻) * h[n][i] Note: Julia's exp64 is a Float64 approximation helper. Our expf (libm FFI, Float32) is bit-exact with Julia's exp(Float32, x).

    update_synapses_confraveux2025

    fn update_synapses_confraveux2025(vars : Confavreux2025SynapseVars, param : Confavreux2025Parameter, glu : Array[Float], gaba : Array[Float], dt : Float) -> Unit

    update_synapses! — 1-state ODE for AMPA/GABA, coupled ODE for NMDA.

    Julia's update order preserved bit-exactly: gAMPA[i] += dt * (-gAMPA[i] / τAMPA + glu[i]) gGABA[i] += dt * (-gGABA[i] / τGABA + gaba[i]) gNMDA[i] += dt * (gAMPA[i] - gNMDA[i]) / τNMDA then glu, gaba are reset to zero (consumed as spike inputs).

    update_synapses_double_exp_current

    fn update_synapses_double_exp_current(vars : DoubleExpCurrentSynapseVars, param : DoubleExpCurrentParameter, glu : Array[Float], gaba : Array[Float], dt : Float) -> Unit

    update_synapses! — Euler forward 2-state ODE per neuron. Per Julia: he[i] += glu[i] hi[i] += gaba[i] ge[i] += dt * (-ge[i]/τde + he[i]) he[i] += dt * (-he[i]/τre) gi[i] += dt * (-gi[i]/τdi + hi[i]) hi[i] += dt * (-hi[i]/τri) then glu, gaba are reset to zero (consumed as spike inputs).

    update_synapses_extended_if

    fn update_synapses_extended_if(p : ExtendedIF, param : ExtendedIFParameter, dt : Float) -> Unit

    update_synapses! — exponential decay of the 3 conductance buffers.

    update_synapses_single_exp

    fn update_synapses_single_exp(vars : SingleExpSynapseVars, param : SingleExpParameter, glu : Array[Float], gaba : Array[Float], dt : Float) -> Unit

    update_synapses! — 1-state exponential decay.

    Julia's form: ge[i] += glu[i] (spike input, added directly) gi[i] += gaba[i] ge[i] += dt * (-ge[i] / τe) gi[i] += dt * (-gi[i] / τi) then glu, gaba are reset to 0 (consume spike inputs).

    update_time

    fn update_time(t : Time, dt : Float) -> Unit

    Advance the simulation by one timestep. Matches Julia's update_time!(T, dt): T.t[1] += dt T.tt[1] += 1

    update_weight

    fn update_weight(pre_pop_neurons : Array[Int], post_pop_neurons : Array[Int], factor : Float, synapse : SpikingSynapse) -> Unit

    Multiply every weight in the synapse matrix whose pre ∈ pre_pop_neurons AND post ∈ post_pop_neurons by factor. Mutates synapse.matrix.vals in place. Mirrors Julia's update_weight! from refs/SNNUtils.jl/src/analysis/performance.jl (default factor 1.2).

    value_forward

    fn value_forward(net : LinearValueNet, x : Array[Float]) -> Float

    Forward: V(s) = w · x.

    voltage

    let voltage : Float

    vstdp_clopath_step

    fn vstdp_clopath_step(state : VStdpState, param : VStdpParam, fire_pre : Array[Bool], fire_post : Array[Bool], w : Array[Float], dt : Float) -> Unit

    One step of voltage-dependent STDP. v_post carries the current membrane potential of each post-synaptic neuron. Spike indicators drive the trace pair; the post voltage is what gates LTP vs LTD.

    vstdp_plot

    fn vstdp_plot(param : VstdpParameter, width? : Int, height? : Int) -> Unit

    Visualise the vSTDP voltage rule: a 2D grid showing which (v_pre, v_post) quadrants produce LTD / LTP / no-change.

    vstdp_step

    fn vstdp_step(vars : VstdpVariables, param : VstdpParameter, pre_v : Array[Float], post_v : Array[Float], pre_fire : Array[Bool], post_fire : Array[Bool]) -> Unit

    One step of vSTDP: scan pre-fire and post-fire arrays; apply LTD on pre-fires (if v_post > θ_LTD) and LTP on post-fires (if v_pre > θ_LTP). Clamps weights to [w_min, w_max].

    weights_indices

    fn weights_indices(pre_pop_neurons : Array[Int], post_pop_neurons : Array[Int], synapse : SpikingSynapse) -> Array[Int]

    Return the 0-based edge indices (into mat.vals / mat.colptr) of all synapse weights whose pre ∈ pre_pop_neurons AND post ∈ post_pop_neurons. Mirrors Julia's weights_indices from refs/SNNUtils.jl/src/analysis/performance.jl.

    Source Files