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.84.0
    License
    MIT
    Last updated
    yesterday
    Downloads
    24

    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.84.0, 2026-10-02)

    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: 1791 (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., v0.54.0 +15 DDPG tests (1721 → 1736) — mbt/ddpg.mbt (DeterministicPolicy + QNetworkContinuous + DDPG agent + train_ddpg closure for env_step) + mbt/ddpg_update.mbt (ContinuousReplayBuffer + ddpg_update_critic + ddpg_update_actor with closed-form critic gradient and sign(ReLU_out) gate) + mbt/ddpg_test.mbt (15 tests covering ContinuousReplayBuffer overflow wrap, twin-critic stability, target policy smoothness implicit via twin critics, deterministic action + tanh rescale, Gaussian noise exploration, Polyak averaging on all 5 networks)., v0.55.0 +8 TD3 tests (1736 → 1744) — mbt/td3.mbt (TD3Agent with twin target critics + target actor + 5 Polyak-averaged target nets, target policy smoothing clip(clip(standard_normal, -c, c), -clip_range, clip_range) with c=0.2, clip_range=0.5, twin-critic TD target r + γ·min(q1', q2'), delayed actor update every policy_delay=2 grad steps) + mbt/td3_test.mbt (8 tests including the policy_delay-skip-actor-update verification)., v0.56.0 +12 Serialize tests (1744 → 1756) — mbt/serialize.mbt (pure-functional String round-trip for LinearQNet / QNetworkContinuous / DeterministicPolicy / AdamState + combined QNetCheckpoint). Lightweight SNNCKPT v1 header with key=value lines; 1D arrays encoded [v0;v1;...], 2D arrays [r0c0;r0c1|r1c0;r1c1;...] (semicolons + pipes never appear in Float::to_string). Float encoding uses Float::reinterpret_as_int + reinterpret_from_int for bit-exact round-trip (initial Float::to_string attempt failed 4 tests on ULP drift because MoonBit 0.1.20260920's default Float printer truncates to ~6 digits)., v0.57.0 +9 SequenceReplayBuffer tests (1756 → 1765) — mbt/sequence_replay_buffer.mbt (per-step continuous replay buffer with T-step rollout sampling for recurrent LSTM/GRU actor-critic RL; flat row-major Float storage; sample_seq_batch(batch_size, seq_len, rng) returns 7 parallel arrays including terminal_mask for done-boundary handling and zero-padded windows when buffer size < seq_len; FIFO wrap on overflow; reset() clears size+cursor without releasing storage)., v0.58.0 +8 GRUDeterministicPolicy tests (1765 → 1773) — mbt/gru_deterministic_policy.mbt (recurrent deterministic actor for POMDPs; MLP w1 (state→hidden) → ReLU → GRU cell (over time) → MLP w2 (hidden→action) → tanh squash to [action_low, action_high]; reuses existing GruCellParam from gru_cell.mbt; Xavier-normal init for MLP weights He-style sqrtf(2/fan_in) for ReLU)., v0.59.0 +8 GRUQNetworkContinuous tests (1773 → 1781) — mbt/gru_qnetwork_continuous.mbt (recurrent continuous-action Q-network for POMDPs; MLP w1 (concat(state, action)→hidden) → ReLU → GRU cell (over time) → MLP w2 (hidden→scalar Q); 1×hidden weight matrix + scalar bias for the Q-value output)., v0.60.0 +10 DDPG_GRU tests (1781 → 1791) — mbt/gru_ddpg.mbt (DDPG agent with GRUDeterministicPolicy actor + twin GRUQNetworkContinuous critics + 5 Polyak-averaged target networks; ddpg_gru_select_action(noise_std=0) for deterministic inference or with Gaussian exploration noise clipped to action range; ddpg_gru_compute_td_target_seq for twin-critic TD forward (no parameter update yet — BPTT deferred to a follow-up because it requires matvec_backward over T steps + gru_cell_backward over T steps + sign(ReLU_out) gate per timestep); Polyak soft_update that covers both the MLP weights + biases AND the GRU sub-parameters (w_z, w_r, w_n, b_z, b_r, b_n)). 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.

    #Recent additions (v0.54–v0.56, 2026-10-01) — Batch B (continuous-control RL + serialization)

    #v0.54.0 — DDPG (Deep Deterministic Policy Gradient, Lillicrap 2015)

    • mbt/ddpg.mbt (NEW) — DeterministicPolicy (state → continuous action via tanh-squash to [action_low, action_high]) + QNetworkContinuous ([state, action] → scalar Q with one ReLU hidden layer) + DDPG agent struct (deterministic actor + twin critics + twin target critics + target actor + 5 Polyak-averaged target nets + γ/τ/exploration_noise).
    • mbt/ddpg_update.mbt (NEW) — ContinuousReplayBuffer (FIFO wrap-around with (s, a, r, s', done) transitions) + ddpg_update_critic (analytic twin-critic TD gradient update) + ddpg_update_actor (closed-form critic gradient through sign(ReLU_out) gate, avoids backward pass through actor ReLU).
    • mbt/ddpg_test.mbt (NEW) — 15 tests: ddpg_select_action_shape, ddpg_select_action_no_grad_shape, ddpg_update_critic_runs_without_crash (NaN guard), ddpg_update_actor_modifies_w2 (mutates w2 in place), ddpg_twin_critic_does_not_collape_on_seeded_inputs, ddpg_soft_update_polyak, continuous_replay_buffer_push_and_len, continuous_replay_buffer_overflow_wraps, deterministic_policy_tanh_squash_clips, deterministic_policy_action_range, deterministic_policy_random_init, qnet_continuous_forward_shape, qnet_continuous_zero_input_zero_weight, ddpg_train_smoke_pendulum_like_env (end-to-end 4-step smoke train), ddpg_select_action_is_deterministic.

    #v0.55.0 — TD3 (Twin Delayed DDPG, Fujimoto 2018)

    • mbt/td3.mbt (NEW) — TD3Agent with actor + twin Q-critics (q1/q2) + twin target Q-critics + target actor + 5 Polyak-averaged target nets + Adam optimizers. Adds policy_delay=2, target_noise_std=0.2, target_noise_clip=0.5 to the DDPG base.
    • td3_smoothed_target_action(action, noise_std, clip_range, rng) — clip(action + clip(standard_normal, -c, c), -clip_range, clip_range) where c = noise_std, applied to the target-actor action in the twin-critic TD target.
    • td3_actor_update(actor, critic1, critic2, target_q1, target_q2, batch, lr) — extracted from ddpg_update_actor (same body, separate name for dispatching clarity); closed-form critic gradient with sign(ReLU_out) gate.
    • td3_soft_update(target_net, source_net, tau) — Polyak averaging over all 5 networks (actor, q1, q2, target_actor, target_q1, target_q2).
    • td3_update_step(agent, batch, lr_actor, lr_critic) — twin-critic TD target y = r + γ·min(q1', q2'), critic update every step, actor + target-net soft update only when step % policy_delay == 0.
    • td3_select_action(state, actor, exploration_noise_std, action_max, rng) — action + Gaussian noise clipped to [-action_max, action_max].
    • train_td3(agent, env_step, n_episodes, max_steps, ...) -> Array[Float] — closure-based env_step callback (state, action) -> (next_state, reward, done), returns per-episode total rewards.
    • 8 tests: td3_select_action_shape, td3_select_action_no_grad_shape, td3_smoothed_target_action_clip, td3_smoothed_target_action_noise, td3_soft_update_polyak, td3_update_step_twin_critic_td_target, td3_update_step_delayed_actor_update_skips_when_count_lt_delay (verifies the skip path), td3_train_smoke_pendulum_like_env.

    #v0.56.0 — Model + optimizer checkpoint serialization

    • mbt/serialize.mbt (NEW) — pure-functional String round-trip for DQN / DDPG / TD3 model parameters + Adam optimizer state. Pure-functional API (no file I/O coupling; caller handles persistence via MoonBit @fs or piping to moon run).
    • Lightweight SNNCKPT v1 header format with key=value lines, no nested objects. Each model is one block; combined checkpoint inlines model + optimizer sub-blocks via escape sequences (\n → \\n, \ → \\).
    • 1D arrays encoded as [v0;v1;...] (semicolon-separated); 2D arrays as [r0c0;r0c1|r1c0;r1c1;...] (semicolons inside rows, pipes between rows). Both separators are non-numeric and never appear in Float::to_string output, so the format is safe to embed inside the key=value\n line-oriented header.
    • Float encoding is bit-exact via Float::reinterpret_as_int + Float::reinterpret_from_int (NOT Float::to_string / parse_float — initial attempt failed 4 tests on ULP drift because MoonBit 0.1.20260920's default Float printer truncates to ~6 digits, which loses last-bit precision for values like 0.1F, 0.01F, -3.14159F). Single-Float fields (QNetworkContinuous.b2, action_low, action_high) also use the same bit-exact Int encoding.
    • Public API: serialize_linear_qnet / deserialize_linear_qnet, serialize_qnetwork_continuous / deserialize_qnetwork_continuous, serialize_deterministic_policy / deserialize_deterministic_policy, serialize_adam_state / deserialize_adam_state, serialize_qnet_checkpoint / deserialize_qnet_checkpoint (returns (LinearQNet, AdamState, Int) tuple).
    • Deep-copy invariant: the deserialized model has freshly allocated arrays, so subsequent mutation of either side is independent (verified in serialize_linear_qnet_independent_copy + serialize_qnet_checkpoint_independent_copies).
    • 12 tests: round-trip for each model + AdamState (zero state + post-update state with step > 0) + combined checkpoint + header-tag presence + action range preservation + negative-float preservation. b2 mutation tests skipped because QNetworkContinuous.b2 is mut b2 and read-only from outside the defining module in blackbox tests; the default-value round-trip still covers the encode/decode path.
    • AdamState fields are not pub, so tests use adam_init / adam_update_arrays / adam_next_step (the public API) to construct states with step > 0, and let opt1 = decode_adam_state(s) to get the deserialized state without mutating it.

    #Recent additions (v0.57–v0.60, 2026-10-01) — Batch C (recurrent / POMDP RL)

    #v0.57.0 — SequenceReplayBuffer for recurrent RL

    • mbt/sequence_replay_buffer.mbt (NEW) — per-step continuous replay buffer with T-step rollout sampling for LSTM/GRU actor-critic RL. Stores (state, action, reward, next_state, done) in flat row-major Float arrays.
    • sample_seq_batch(batch_size, seq_len, rng) returns 7 parallel arrays: obs_seq [batch × seq_len × state_dim], action_seq [batch × seq_len × action_dim], reward_seq [batch × seq_len], next_obs_seq [batch × seq_len × state_dim], done_seq [batch × seq_len], terminal_mask [batch × seq_len] (1.0F at slots following a done boundary so caller can break BPTT), hidden_init [batch] (zeros — caller supplies hidden dim).
    • Padding behaviour: window crossing a done boundary zero-fills the rest and sets terminal_mask. Empty buffer (size < seq_len) returns all-zero windows with terminal_mask set everywhere (no panic).
    • 9 tests: new/len, push, overflow wrap, reset, sample shape, empty-buffer zero-pad, window-crosses-done terminal mask, reproducible under seed, dones propagate in window.

    #v0.58.0 — GRUDeterministicPolicy (recurrent actor for POMDPs)

    • mbt/gru_deterministic_policy.mbt (NEW) — recurrent deterministic actor for partially-observable continuous control. Architecture: MLP w1 (state→hidden) → ReLU → GRU cell (over time) → MLP w2 (hidden→action) → tanh squash to [action_low, action_high]. GRU hidden dim = MLP hidden dim so the ReLU-projected state and the GRU input/output match.
    • GRUDeterministicPolicy::new(state_dim, action_dim, gru_hidden, action_low, action_high, seed) — Xavier-normal init for MLPs (He-style sqrtf(2/fan_in) for ReLU), reuses GruCellParam::new(gru_hidden, gru_hidden, seed+4UL) for the recurrent cell, zero biases.
    • gru_deterministic_policy_step(policy, obs, hidden) → (action, hidden_next) — single-step inference. gru_deterministic_policy_seq_forward(policy, obs_seq, seq_len, hidden_init) → (action_seq, final_hidden) — flat row-major [seq_len × action_dim] output.
    • 8 tests: constructor shape, action in squash range, deterministic under seed, different seeds differ, hidden state evolves, seq_forward shape, seq_forward matches single-step chain, deterministic under seed at seq level.

    #v0.59.0 — GRUQNetworkContinuous (recurrent critic for POMDPs)

    • mbt/gru_qnetwork_continuous.mbt (NEW) — recurrent continuous-action Q-network for partially-observable environments. Architecture: MLP w1 (concat(state, action)→hidden) → ReLU → GRU cell → MLP w2 (hidden→scalar Q).
    • GRUQNetworkContinuous::new(state_dim, action_dim, hidden, seed) — Xavier-normal init for MLPs, GRU via GruCellParam::new(hidden, hidden, seed+4UL). mlp_w2 is 1×hidden (scalar Q output) + scalar mut mlp_b2 : Float bias.
    • gru_qnetwork_continuous_step(qnet, state, action, hidden) → (q, hidden_next) — single-step inference (returns Float, not array). gru_qnetwork_continuous_seq_forward(qnet, state_seq, action_seq, seq_len, hidden_init) → (q_seq, final_hidden) — flat row-major [seq_len] output.
    • 8 tests: shape, deterministic under seed, different seeds differ, hidden state evolves, zero-zero-zero → 0.0F, seq_forward shape + deterministic, seq_forward matches single-step chain, scalar return type.

    #v0.60.0 — DDPG_GRU agent (recurrent actor + twin recurrent critics)

    • mbt/gru_ddpg.mbt (NEW) — DDPG agent with GRUDeterministicPolicy actor + twin GRUQNetworkContinuous critics + 5 Polyak-averaged target networks (actor_target, critic1_target, critic2_target). Recurrent hidden state threaded through the twin-critic TD update.
    • DDPG_GRU::new(state_dim, action_dim, hidden, action_low, action_high, gamma, tau, exploration_noise, seed) — each of the 6 networks gets a distinct seed (sources at seed/seed+1/seed+2, targets at seed+100/seed+101/seed+102) so Polyak-averaged targets start far from sources.
    • ddpg_gru_select_action(agent, obs, hidden, noise_std, rng) — single-step inference with optional Gaussian exploration noise clipped to [action_low, action_high]. noise_std=0 recovers the deterministic policy output.
    • ddpg_gru_compute_td_target_seq(agent, next_obs_seq, next_act_seq, reward_seq, done_seq, seq_len) — twin-critic TD target forward: y_t = r_t + γ·(1 - done_t)·min(q1_target(next), q2_target(next)). No parameter update yet — BPTT-driven update (matvec_backward over T steps + gru_cell_backward over T steps + sign(ReLU_out) gate) is deferred to a follow-up because it's a significant incremental body of work.
    • gru_deterministic_policy_soft_update(target, source, tau) + gru_qnetwork_continuous_soft_update(target, source, tau) — Polyak averaging that updates the MLP weights + biases AND the GRU sub-parameters (w_z, w_r, w_n, b_z, b_r, b_n). ddpg_gru_soft_update(agent) calls all three.
    • pub(all) struct DDPG_GRU with mut tau : Float — required for cross-file record-update { ..agent, tau: 1.0F } (MoonBit's blackbox test rule: mut field is only mutable if the containing struct is pub(all)).
    • 10 tests: 6 networks built + hyperparameters stored, source/target networks start distinct, deterministic with noise_std=0, noise clipped to action range, hidden state evolves, TD target shape + finiteness, zero rewards + γ=0 → zero target, tau=1 → actor_target exactly matches actor, tau=0 → actor_target unchanged, tau=1 → critics also soft-updated.

    #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.

    #Recent additions (v0.58.0 - v0.84.0, 2026-10-02)

    Six batches shipped between v0.57.0 and v0.83.0 (v0.84.0 is the release-version bump — no new code), all moon check clean (0 errors, 0 diagnostics on new files). Tests blocked by Windows CreateProcessW 32K cmdline limit on moon test past v0.61.0; existing 1801 test baseline verified working through v0.61.0.

    #Batch C follow-up (v0.58.0 - v0.61.0): BPTT boundary handling

    • sample_seq_batch_at helper for arbitrary-window sequence replay buffer sampling with terminal_mask for proper hidden-state zeroing at episode boundaries.
    • 10 BPTT boundary tests added at v0.61.0 (1801 → 1811 tests).

    #Batch D (v0.61.0 - v0.63.0): Recurrent deterministic actor-critic (LSTM)

    • v0.61.0 LSTMDeterministicPolicy: recurrent actor for POMDPs. state → MLP w1 → ReLU → LSTM cell → MLP w2 → tanh squash → action.
    • v0.62.0 LSTMQNetworkContinuous: recurrent critic. [state; action] → MLP w1 → ReLU → LSTM cell → MLP w2 → scalar Q.
    • v0.63.0 LSTM_DDPG: full DDPG agent wrapping actor + twin critics + 5 Polyak targets. 6 distinct seeds (offset by +100UL for targets).

    #Batch E (v0.64.0 - v0.67.0): BPTT-driven recurrent critic/actor updates

    • v0.64.0 gru_ddpg_update_critic_seq: GRU critic T-step BPTT.
    • v0.65.0 gru_ddpg_update_actor_seq: GRU actor BPTT (finite-difference ∂Q/∂a with eps=1e-3).
    • v0.66.0 lstm_ddpg_update_critic_seq: LSTM critic T-step BPTT.
    • v0.67.0 lstm_ddpg_update_actor_seq: LSTM actor BPTT.

    #Batch F (v0.68.0 - v0.71.0): Recurrent SAC (stochastic actor + twin critics)

    • v0.68.0 GRUSACActor: stochastic actor — state → MLP w1 → ReLU → GRU cell → two MLP branches (mean + log_std) → tanh squash + Gaussian sampling. log_std clamped [-20, 2] (Haarnoja 2018).
    • v0.69.0 LSTMSACActor: parallel LSTM variant.
    • v0.70.0 SAC_GRU: stochastic actor + twin recurrent critics + auto-alpha
      • 5 Polyak targets. target_entropy = -action_dim default.
    • v0.71.0 SAC_LSTM: parallel.

    #Batch G (v0.72.0 - v0.75.0): Gated Transformer-XL (GTrXL) recurrent memory

    • v0.72.0 GTrXLBlock: gated FFN residual primitive. y = sigmoid(W_gate · x) ⊙ FFN(x) + x (Parisotto et al. 2020). Gate init std scaled by 0.1 so initial gate ≈ sigmoid(0) ≈ 0.5.
    • v0.73.0 GTrXLDeterministicPolicy: recurrent actor with GTrXL block as memory.
    • v0.74.0 GTrXLQNetworkContinuous + GTrXL_DDPG: twin critics + 5 Polyak targets; 9 tensors per network, 45 total.
    • v0.75.0 GTrXLSACActor + SAC_GTrXL: stochastic SAC + auto-alpha; 12 tensors per target, 60 total.

    #Batch H (v0.76.0 - v0.79.0): Decision Transformer (offline RL via sequence modeling)

    • v0.76.0 DTEmbeddings: 3-modality tokenization (rtg/state/action)
      • timestep lookup. Per-token = Linear_rtg(R_t) + Linear_state(s_t) + Linear_action(a_t) + Embed_ts(t).
    • v0.77.0 DecisionTransformer: full DT = DTEmbeddings + GTrXLBlock backbone + action head.
    • v0.78.0 TrajectoryBuffer: offline RL replay + returns-to-go (R_t = r_t + γ·R_{t+1}, terminal zeros bootstrap) + sample_trajectory_window for window batching.
    • v0.79.0 DecisionTransformerTrainer: MSE loss + per-element gradient
      • SGD step on action head only. Embeddings + backbone frozen (BPTT through GTrXL deferred).

    #Batch I (v0.80.0 - v0.83.0): Trajectory Transformer (planning via conditional sequence modeling)

    • v0.80.0 TTEmbeddings + TrajectoryTransformer: 4-modality (state/action/reward/done) + 4 prediction heads (next_state, reward, done, value) on GTrXL backbone.
    • v0.81.0 BeamSearchPlanner: K-beam expansion via TT dynamics + uniform-random action sampling + value scoring + top-K selection over horizon H. Score = accumulated discounted reward + γ^t · value(t).
    • v0.82.0 TrajectoryTransformerTrainer: total MSE = MSE(next_state) + MSE(reward) + MSE(done) + MSE(value); SGD on all 4 heads.
    • v0.83.0 TrajectoryTransformerAgent: full pipeline — append transition → sliding history → beam_search_plan → return best first action.

    #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/.

    ⚠️ Package is too large to be displayed

    This package contains over 1093 items.

    Source Files