cont_delay_synapse#
- class brainpy.state.cont_delay_synapse(weight=1.0, delay=Quantity(1., 'ms'), receptor_type=0, post=None, event_type='spike', name=None)#
NEST-compatible static synapse with continuous (off-grid) delays.
This synapse model extends
static_synapseto support precise spike timing by decomposing transmission delays into integer delay steps and fractional sub-timestep offsets. It mirrors the NESTcont_delay_synapsemodel and implements the exact subthreshold integration method described in Morrison et al. (2007).- Parameters:
weight (
ArrayLike, optional) – Synaptic weight multiplier applied to incoming spike events. Dimensionless scalar or array. Default:1.0.delay (
ArrayLike, optional) – Total synaptic transmission delay. Must be>= dt(simulation timestep). Decomposed internally into integer steps and fractional offset. Unit: millisecond. Default:1.0 * u.ms.receptor_type (
int, optional) – Target receptor port index on the postsynaptic neuron. Used to differentiate excitatory/inhibitory or multiple receptor types. Default:0.post (
brainstate.nn.Module, optional) – Default postsynaptic target object. Must implement eitherhandle_cont_delay_synapse_event(value, receptor_type, event_type, offset)oradd_precise_spike_event(key, value, offset, label)for off-grid event delivery. On-grid events fall back to standardstatic_synapsedelivery viaadd_current_inputoradd_delta_input. Default:None.event_type (
str, optional) – Event transmission mode. Supported values:'spike'(discrete delta events),'rate'(continuous rate signals),'current'(arbitrary current injection). Default:'spike'.name (
str, optional) – Unique identifier for this synapse instance. Used for debugging and event tracking. Default:None(auto-generated).
Mathematical Model
1. Delay Decomposition
Given a continuous delay \(d\) and simulation timestep \(\Delta t\), the model decomposes \(d\) into:
Integer delay steps: \(d_{\text{steps}} \in \mathbb{N}\)
Fractional offset: \(d_{\text{offset}} \in [0, \Delta t)\) (in ms)
such that the effective delay satisfies:
\[d_{\text{eff}} = d_{\text{steps}} \cdot \Delta t - d_{\text{offset}}\]The decomposition algorithm follows NEST conventions:
Case 1: On-grid delay (\(d / \Delta t \in \mathbb{N}\)):
\[\begin{split}d_{\text{steps}} &= \frac{d}{\Delta t} \\ d_{\text{offset}} &= 0\end{split}\]Case 2: Off-grid delay (\(d / \Delta t \notin \mathbb{N}\)):
\[\begin{split}d_{\text{steps}} &= \lfloor d / \Delta t \rfloor + 1 \\ d_{\text{offset}} &= \Delta t \cdot \left(1 - \text{frac}(d / \Delta t)\right)\end{split}\]where \(\text{frac}(x) = x - \lfloor x \rfloor\) is the fractional part.
Constraint: \(d \geq \Delta t\) must hold. Violations raise
ValueErrorduring initialization or timestep changes.2. Event Scheduling with Source Offsets
When a presynaptic spike arrives with source offset \(o_{\text{src}}\) (measured from the right edge of the current timestep), the effective event offset becomes:
\[o_{\text{total}} = o_{\text{src}} + d_{\text{offset}}\]Carry handling: If \(o_{\text{total}} \geq \Delta t\), the event “carries over” to the next timestep:
\[\begin{split}d_{\text{steps}}^{\text{adj}} &= d_{\text{steps}} - 1 \\ o_{\text{event}} &= o_{\text{total}} - \Delta t\end{split}\]Otherwise:
\[\begin{split}d_{\text{steps}}^{\text{adj}} &= d_{\text{steps}} \\ o_{\text{event}} &= o_{\text{total}}\end{split}\]The adjusted delay steps determine when the event is delivered, and \(o_{\text{event}}\) specifies the sub-timestep delivery time.
3. Event Delivery
Events are queued at timestep \(t_{\text{deliver}} = t_{\text{current}} + d_{\text{steps}}^{\text{adj}}\) and delivered with offset \(o_{\text{event}}\). The delivery mechanism depends on the receiver’s capabilities:
Off-grid delivery: Calls
receiver.handle_cont_delay_synapse_event(value, receptor_type, event_type, offset)if available. The receiver integrates the event at precise time \(t_{\text{step}} - o_{\text{event}}\) (measured from right edge).On-grid fallback: If \(o_{\text{event}} \approx 0\), uses standard
static_synapsedelivery (add_current_inputoradd_delta_input).Precise spike API: For spike events, optionally calls
receiver.add_precise_spike_event(key, value, offset, label)ifhandle_cont_delay_synapse_eventis unavailable.
4. Weight Multiplication
Spike multiplicity is scaled by the synaptic weight:
\[\text{payload} = m \cdot w\]where \(m\) is the incoming spike count and \(w\) is the weight.
Parameter Mapping
NEST Parameter
brainpy.state Parameter
Notes
weightweightDimensionless multiplier
delaydelayTotal delay (ms)
delay_steps_delay_steps(internal)Integer steps (auto)
delay_offset_delay_offset_ms(internal)Fractional offset (auto)
receptor_typereceptor_typeReceptor port index
Internal state
_delay_stepsand_delay_offset_msare computed automatically during initialization and when the simulation timestep changes. They are exposed viaget()for inspection.Computational Properties
Time complexity: \(O(1)\) per event (queue insertion/retrieval uses dict lookups).
Space complexity: \(O(E \cdot D)\) where \(E\) is the number of pending events and \(D\) is the maximum delay in timesteps.
Numerical precision: Offset arithmetic uses double precision. Offsets within \(10^{-15}\) ms of zero are treated as on-grid for numerical stability.
Exact integration: When combined with compatible neuron models (e.g., NEST’s
*_psvariants), achieves machine-precision spike timing independent of timestep choice (within numerical limits).
Failure Modes
ValueError: Raised if
delay < dtordt <= 0during initialization or_refresh_delay_cache.TypeError: Raised if receiver does not implement off-grid event delivery API when required (offset > 0).
ValueError: Raised if source offsets violate
0 <= offset <= dt.
See also
static_synapseBase class for fixed-weight synapses
iaf_psc_exp_psNEST neuron model with precise spike timing support
Notes
Event Format: The
updatemethod accepts precise spike events via thespike_eventsparameter in two formats:Tuple format:
(offset, multiplicity)whereoffsetis in milliseconds.Dict format:
{'offset': value, 'multiplicity': value}for explicit labeling.Sequence format: List of tuples or dicts for multiple events in one timestep.
Offset Convention: All offsets follow NEST’s precise-time convention: measured backward from the right edge of the current timestep. An offset of
0means delivery at the end of the step;dtmeans delivery at the start. This matches the continuous-time interpretation where time increases left-to-right within the discrete step.Timestep Changes: When the simulation timestep changes (e.g., via
brainstate.environ.context(dt=...)), the model automatically recomputes_delay_stepsand_delay_offset_msto maintain the requested delay value. This may alter the effective delay within machine precision bounds.Warnings: If
delayis specified in NEST-styleConnectcalls (viacheck_synapse_params), a warning is issued because connect-time delays are rounded to integer timesteps. For precise delays, define them in the synapse model itself (e.g., via NEST’sCopyModel).References
Examples
Basic usage with on-grid delay:
>>> import brainpy.state as bst >>> import brainstate as bst >>> import saiunit as u >>> with bst.environ.context(dt=0.1 * u.ms): ... syn = bst.nest.cont_delay_synapse( ... weight=2.5, ... delay=1.0 * u.ms, # 10 steps at dt=0.1 ms ... ) ... print(syn.get()) {'weight': 2.5, 'delay': 1.0, 'delay_offset': 0.0, 'receptor_type': 0, 'synapse_model': 'cont_delay_synapse'}
Off-grid delay with fractional offset:
>>> with bst.environ.context(dt=0.1 * u.ms): ... syn = bst.nest.cont_delay_synapse( ... weight=1.0, ... delay=1.23 * u.ms, # Not a multiple of 0.1 ms ... ) ... params = syn.get() ... print(f"delay_steps: {syn._delay_steps}, " ... f"delay_offset: {params['delay_offset']:.4f} ms") delay_steps: 13, delay_offset: 0.0700 ms # Effective delay: 13 * 0.1 - 0.07 = 1.23 ms
Sending events with source offsets:
>>> import jax.numpy as jnp >>> with bst.environ.context(dt=0.1 * u.ms): ... neuron = bst.nn.LIF(1, V_rest=-70 * u.mV) ... syn = bst.nest.cont_delay_synapse( ... weight=5.0, ... delay=0.5 * u.ms, ... post=neuron, ... ) ... syn.init_all_states() ... neuron.init_all_states() ... # Send spike with 0.03 ms offset from step edge ... syn.send(multiplicity=1.0, source_offset=0.03 * u.ms)
Processing multiple precise events per timestep:
>>> with bst.environ.context(dt=0.1 * u.ms): ... syn = bst.nest.cont_delay_synapse(weight=1.0, delay=0.5 * u.ms) ... syn.init_all_states() ... # Pass list of (offset, multiplicity) tuples ... events = [ ... (0.02 * u.ms, 1.0), # Early spike ... (0.08 * u.ms, 2.0), # Late spike (double) ... ] ... delivered = syn.update(spike_events=events)
Checking delay decomposition for validation:
>>> with bst.environ.context(dt=0.1 * u.ms): ... syn = bst.nest.cont_delay_synapse(delay=0.37 * u.ms) ... syn.init_all_states() ... params = syn.get() ... d_eff = syn._delay_steps * 0.1 - params['delay_offset'] ... print(f"Requested: 0.37 ms, Effective: {d_eff:.2f} ms") Requested: 0.37 ms, Effective: 0.37 ms
- check_synapse_params(syn_spec)[source]#
Validate and warn about connect-time synapse specifications.
Issues a warning if
delayis specified in connect-time synapse dictionaries (e.g., in NEST-styleConnectcalls), because such delays are rounded to integer multiples of the simulation timestep rather than using the continuous-delay decomposition.- Parameters:
syn_spec (
dictorNone) – Synapse specification dictionary, typically from NEST-style connection APIs. Expected keys:'delay','weight', etc.- Warns:
UserWarning – If
'delay'key is present insyn_spec. The warning message advises defining delays in the synapse model definition (e.g., via NEST’sCopyModel) to preserve precise sub-timestep offsets.
Notes
This method mirrors NEST’s
cont_delay_synapsebehavior, which prints a similar warning when delays are supplied at connection time. To avoid rounding, instantiate the synapse with the desireddelayparameter directly rather than passing it via connection specs.
- get()[source]#
Return current public parameters including delay decomposition.
- Returns:
Parameter dictionary with keys:
'weight': float — Synaptic weight multiplier.'delay': float — Effective total delay in milliseconds (computed as \(d_{\text{steps}} \cdot \Delta t - d_{\text{offset}}\)).'delay_offset': float — Fractional sub-timestep offset in milliseconds (range[0, dt)). Zero for on-grid delays.'receptor_type': int — Target receptor port index.'synapse_model': str — Always'cont_delay_synapse'.
- Return type:
Notes
The
delay_offsetfield is specific to this model and not present in the basestatic_synapse. Integer delay steps (_delay_steps) are not exposed but can be accessed via the internal attribute for debugging.
- send(multiplicity=1.0, *, source_offset=Quantity(0., 'ms'), post=None, receptor_type=None, event_type=None)[source]#
Schedule an outgoing synaptic event with continuous-delay offset.
Implements the NEST
cont_delay_synapseevent scheduling algorithm: combines the source spike offset with the synapse’s fractional delay offset, handles carry-over if the sum exceeds the timestep, and queues the event for delivery at the appropriate future timestep.- Parameters:
multiplicity (
ArrayLike, optional) – Spike count or event magnitude. Multiplied byself.weightto compute the delivered payload. Must be non-negative scalar or array. Default:1.0(single spike).source_offset (
ArrayLike, optional) – Sub-timestep offset of the source spike, measured backward from the right edge of the current timestep. Must satisfy0 <= source_offset <= dt. Unit: millisecond. Default:0.0 * u.ms(spike occurred at step boundary).post (
brainstate.nn.Module, optional) – Override the default postsynaptic target for this event. IfNone, usesself.post. Default:None.receptor_type (
ArrayLike, optional) – Override the default receptor port index for this event. IfNone, usesself.receptor_type. Must be integer-valued. Default:None.event_type (
str, optional) – Override the default event transmission type for this event. IfNone, usesself.event_type. Supported values:'spike','rate','current'. Default:None.
- Returns:
Trueif the event was scheduled (or delivered immediately),Falseif the event was skipped due to zero multiplicity.- Return type:
- Raises:
ValueError – If
source_offsetviolates the constraint0 <= source_offset <= dt.TypeError – If the receiver does not implement the required off-grid event delivery API when the effective offset is non-zero.
Notes
Offset Arithmetic:
The effective event offset is computed as:
\[o_{\text{total}} = o_{\text{src}} + d_{\text{offset}}\]If \(o_{\text{total}} \geq \Delta t\), a carry occurs:
Adjusted delay steps – \(d_{\text{steps}} - 1\)
Final event offset – \(o_{\text{total}} - \Delta t\)
Otherwise, no carry:
Adjusted delay steps – \(d_{\text{steps}}\)
Final event offset – \(o_{\text{total}}\)
Immediate Delivery: If the carry reduces delay steps to zero, the event is delivered immediately via
_deliver_event_with_offsetrather than being queued.Queue Structure: Events are stored in
self._queue(a defaultdict mapping delivery timestep to event list). Each queued entry is a tuple:(receiver, payload, receptor_type, event_type, offset).Example:
>>> # Synapse with 1.23 ms delay (13 steps, 0.07 ms offset at dt=0.1) >>> # Source spike at 0.05 ms offset >>> # Total: 0.05 + 0.07 = 0.12 ms >= 0.1 ms → carry >>> # Adjusted: 12 steps, 0.02 ms offset >>> syn.send(multiplicity=1.0, source_offset=0.05 * u.ms) True
- update(pre_spike=0.0, *, spike_events=None, post=None, receptor_type=None, event_type=None)[source]#
Process one simulation timestep: deliver queued events and schedule new ones.
This method implements the standard synapse update cycle:
Deliver due events: Retrieve and deliver all events scheduled for the current timestep from the internal queue.
Schedule on-grid events: Sum presynaptic input from
pre_spikeand registered current/delta inputs, then schedule with zero offset.Schedule precise events: Process each event in
spike_eventswith its specified sub-timestep offset.
- Parameters:
pre_spike (
ArrayLike, optional) – On-grid presynaptic spike count or rate. Treated as occurring at the right edge of the timestep (offset = 0). Scalar or array. Default:0.0(no on-grid input).spike_events (
listoftuples/dicts,tuple,dict, orNone, optional) –Precise spike events with sub-timestep timing. Supported formats:
Single tuple:
(offset, multiplicity)Single dict:
{'offset': value, 'multiplicity': value}List of tuples/dicts: Multiple events in one step
Each
offsetmust satisfy0 <= offset <= dt(in ms). Default:None(no precise events).post (
brainstate.nn.Module, optional) – Override the default postsynaptic target. Default:None(useself.post).receptor_type (
ArrayLike, optional) – Override the default receptor port index. Default:None(useself.receptor_type).event_type (
str, optional) – Override the default event type. Supported:'spike','rate','current'. Default:None(useself.event_type).
- Returns:
Number of events delivered during this timestep (from the queue). Does not include newly scheduled events.
- Return type:
- Raises:
ValueError – If any event offset in
spike_eventsviolates0 <= offset <= dt.ValueError – If event dicts are missing required keys
'offset'or'multiplicity'.
Notes
Processing Order: The three-step sequence (deliver → on-grid → precise) ensures deterministic behavior when events from previous timesteps arrive simultaneously with new inputs. Queued events are always processed before new scheduling occurs.
Input Aggregation: On-grid inputs are summed across all sources:
Explicit
pre_spikeargumentInputs registered via
add_current_input(label, value)Inputs registered via
add_delta_input(label, value)
Event Format Examples:
# Single tuple syn.update(spike_events=(0.05 * u.ms, 2.0)) # Single dict syn.update(spike_events={'offset': 0.05 * u.ms, 'multiplicity': 2.0}) # Multiple events syn.update(spike_events=[ (0.02 * u.ms, 1.0), (0.08 * u.ms, 3.0), ])
Return Value: Only counts delivered events. Newly scheduled events (from
pre_spikeorspike_events) will be counted in future timesteps when they are delivered.See also
sendSchedule a single event with offset