vogels_sprekeler_synapse#

class brainpy.state.vogels_sprekeler_synapse(weight=0.5, delay=1.0, delay_steps=1, tau=20.0, alpha=0.12, eta=0.001, Wmax=1.0, Kplus=0.0, t_last_spike_ms=0.0, name=None)#

NEST-compatible vogels_sprekeler_synapse connection model.

This class reproduces connection-level semantics of NEST models/vogels_sprekeler_synapse.{h,cpp}, implementing inhibitory spike-timing-dependent plasticity (iSTDP) following Vogels & Sprekeler (2011). The rule combines symmetric STDP (no depression for post-before-pre timing) with constant presynaptic depression, designed to maintain excitatory-inhibitory balance in recurrent networks.

1. Mathematical Model

The learning rule modifies synaptic weight \(w\) based on spike timing:

\[\begin{split}\Delta w = \begin{cases} \eta \cdot K_- & \text{(pre-post pairing)} \\ -\alpha \eta & \text{(constant depression per pre spike)} \end{cases}\end{split}\]

where:

  • \(K_-\) is the postsynaptic trace (decaying exponentially with time constant \(\tau\))

  • \(\eta\) is the learning rate

  • \(\alpha\) is the constant depression factor

Traces evolve as:

\[ \begin{align}\begin{aligned}\frac{dK_+}{dt} = -\frac{K_+}{\tau} + \sum_i \delta(t - t_i^{\text{pre}})\\\frac{dK_-}{dt} = -\frac{K_-}{\tau} + \sum_j \delta(t - t_j^{\text{post}})\end{aligned}\end{align} \]

2. NEST Send-Order Update Sequence

For one presynaptic spike at time \(t\) with dendritic delay \(d\):

  1. History lookup: Retrieve postsynaptic spikes in \((t_{\text{last}} - d, t - d]\)

  2. Pairwise facilitation: For each postsynaptic spike \(t_j\) in history:

    \[w \leftarrow \operatorname{facilitate}\!\left( w, K_+ \exp\left(\frac{t_{\text{last}} - (t_j + d)}{\tau}\right)\right)\]
  3. Current postsynaptic trace facilitation:

    \[w \leftarrow \operatorname{facilitate}(w, K_-(t - d))\]
  4. Constant depression:

    \[w \leftarrow \operatorname{depress}(w)\]
  5. Emit spike event using updated weight

  6. Update presynaptic trace:

    \[K_+ \leftarrow K_+ \exp\left(\frac{t_{\text{last}} - t}{\tau}\right) + 1\]
  7. Update timestamp: \(t_{\text{last}} \leftarrow t\)

3. Weight Clipping Rules

Facilitate and depress operations are sign-aware via \(W_{\max}\):

\[\operatorname{facilitate}(w, k) = \operatorname{copysign}\left(\min(|w| + \eta k,\ |W_{\max}|), W_{\max}\right)\]
\[\operatorname{depress}(w) = \operatorname{copysign}\left(\max(|w| - \alpha\eta,\ 0), W_{\max}\right)\]

This ensures weights saturate at \(\pm |W_{\max}|\) while preserving sign.

4. Biological Motivation

This rule implements the iSTDP mechanism proposed by Vogels & Sprekeler for inhibitory synapses. The constant depression term \(\alpha\) causes weight decay independent of post-pre timing, while facilitation occurs for pre-post pairings. This asymmetry drives inhibitory weights to track excitatory activity, maintaining balanced network states without fine-tuned parameters.

Parameters:
  • weight (float, array-like, optional) – Synaptic weight (unitless). Can be positive (excitatory) or negative (inhibitory). Must have same sign as Wmax if non-zero. Default: 0.5.

  • delay (float, array-like, optional) – Dendritic delay in milliseconds used for spike history lookup. Must be positive. Determines time window for postsynaptic spike detection. Default: 1.0 ms.

  • delay_steps (int, array-like, optional) – Event delivery delay in integer simulation steps (≥1). Controls when spike arrives at postsynaptic target after emission. Default: 1.

  • tau (float, array-like, optional) – STDP time constant in milliseconds (>0). Governs exponential decay of presynaptic (\(K_+\)) and postsynaptic (\(K_-\)) traces. Typical range: 10-50 ms. Default: 20.0 ms.

  • alpha (float, array-like, optional) – Constant depression factor (unitless). Scales the per-spike weight reduction: \(\Delta w = -\alpha \eta\). Setting \(\alpha = 0\) disables constant depression (pure Hebbian STDP). Default: 0.12.

  • eta (float, array-like, optional) – Learning rate (unitless). Scales both facilitation and depression. Smaller values (≪1) ensure gradual weight changes. Default: 0.001.

  • Wmax (float, array-like, optional) – Signed maximum absolute weight (unitless). Defines saturation bounds \([-|W_{\max}|, +|W_{\max}|]\) and determines sign of weight dynamics. Must have same sign as weight (if weight != 0). Default: 1.0.

  • Kplus (float, array-like, optional) – Initial presynaptic STDP trace value (unitless, ≥0). Represents accumulated presynaptic activity. Typically initialized to 0 before simulation. Default: 0.0.

  • t_last_spike_ms (float, array-like, optional) – Timestamp of last presynaptic spike in milliseconds. Used for trace decay calculations. Initialize to simulation start time or 0. Default: 0.0 ms.

  • name (str, optional) – Model instance name for identification. Default: None.

See also

stdp_synapse

Classical asymmetric STDP rule

stdp_dopamine_synapse

Reward-modulated STDP

Parameter Mapping

NEST Parameter

brainpy.state

Unit

weight

weight

unitless

delay

delay

ms

delay_steps

delay_steps

steps

tau

tau

ms

alpha

alpha

unitless

eta

eta

unitless

Wmax

Wmax

unitless

Kplus

Kplus

unitless

t_lastspike

t_last_spike_ms

ms

Target Interface Requirements

The send() method requires postsynaptic targets to implement:

  • get_history(t1, t2) – Returns spike history entries in time window (t1, t2] (exclusive-inclusive). Entries must expose spike time via attribute t_ or t, dict key 't_' or 't', or first tuple element.

  • get_K_value(t) or get_k_value(t) – Returns postsynaptic STDP trace \(K_-\) at time t (in ms). Must return float.

  1. Dendritic delay semantics: Unlike axonal delays (which shift spike arrival time), the delay parameter here controls the temporal window for history lookup: \((t_{\text{last}} - d, t - d]\). This implements NEST’s dendritic delay convention.

  2. Sign constraints: If weight != 0, both weight and Wmax must have the same sign. Attempting to set opposite signs raises ValueError. This preserves synapse type (excitatory/inhibitory) throughout learning.

  3. Trace positivity: Kplus must remain non-negative. Negative values raise ValueError during initialization or set_status().

  4. Sub-step timing: As in NEST, precise spike times within a time step (e.g., off-grid timestamps) are ignored for plasticity calculations. All updates use coarse time step boundaries.

  5. Event multiplicity: The multiplicity parameter in send() is validated but not explicitly used in weight updates (reserved for future multi-spike events).

References

Examples

Basic synapse creation with default parameters:

>>> import brainpy.state as bp
>>> syn = bp.vogels_sprekeler_synapse()
>>> syn.get_status()
{'weight': 0.5, 'tau': 20.0, 'alpha': 0.12, 'eta': 0.001, ...}

Configure for inhibitory synapse with stronger depression:

>>> syn = bp.vogels_sprekeler_synapse(
...     weight=-0.8,
...     Wmax=-2.0,
...     alpha=0.2,
...     eta=0.005,
...     tau=30.0
... )
>>> syn.weight
-0.8

Update parameters after creation:

>>> syn.set_status({'alpha': 0.15, 'eta': 0.002})
>>> syn.alpha
0.15

Process presynaptic spike with mock postsynaptic target:

>>> class MockNeuron:
...     def get_history(self, t1, t2):
...         # Return spike at t=10.5 ms
...         return [{'t_': 10.5}]
...     def get_K_value(self, t):
...         return 0.3  # Current postsynaptic trace
>>> target = MockNeuron()
>>> event = syn.send(t_spike_ms=15.0, target=target)
>>> event['weight']  # Updated weight after plasticity
0.506...
>>> syn.Kplus  # Updated presynaptic trace
1.0

Simulate spike train:

>>> pre_times = [10.0, 20.0, 30.0, 40.0]
>>> events = syn.simulate_pre_spike_train(
...     pre_spike_times_ms=pre_times,
...     target=target
... )
>>> len(events)
4
>>> [e['weight'] for e in events]  # Weight evolution
[0.512..., 0.518..., 0.524..., 0.530...]
get(key='status')[source]#

Retrieve specific parameter or full status dictionary.

Parameters:

key (str, optional) – Parameter name or 'status' for full dictionary. Valid keys: 'weight', 'delay', 'delay_steps', 'tau', 'alpha', 'eta', 'Wmax', 'Kplus', 't_last_spike_ms', 'size_of', 'has_delay', 'is_primary', 'supports_hpc', 'supports_lbl', 'supports_wfr'. Default: 'status'.

Returns:

If key == 'status', returns full status dictionary (see get_status()). Otherwise, returns the requested parameter value.

Return type:

Any

Raises:

KeyError – If key is not recognized.

Examples

>>> syn = bp.vogels_sprekeler_synapse(weight=0.6, tau=25.0)
>>> syn.get('weight')
0.6
>>> syn.get('tau')
25.0
>>> status = syn.get('status')
>>> 'weight' in status
True
get_status()[source]#

Retrieve current parameter and state values.

Returns all synapse parameters, STDP trace state, and model properties as a dictionary. Follows NEST GetStatus semantics.

Returns:

Dictionary containing: - 'weight' (float): Current synaptic weight (unitless). - 'delay' (float): Dendritic delay (ms). - 'delay_steps' (int): Event delivery delay (steps). - 'tau' (float): STDP time constant (ms). - 'alpha' (float): Constant depression factor (unitless). - 'eta' (float): Learning rate (unitless). - 'Wmax' (float): Signed maximum weight (unitless). - 'Kplus' (float): Presynaptic STDP trace (unitless). - 't_last_spike_ms' (float): Last presynaptic spike time (ms). - 'size_of' (int): Memory footprint in bytes. - 'has_delay' (bool): Delay support flag. - 'is_primary' (bool): Primary connection flag. - 'supports_hpc' (bool): HPC compatibility flag. - 'supports_lbl' (bool): LBL compatibility flag. - 'supports_wfr' (bool): WFR compatibility flag.

Return type:

dict[str, Any]

Examples

>>> syn = bp.vogels_sprekeler_synapse(weight=0.7, tau=25.0)
>>> status = syn.get_status()
>>> status['weight']
0.7
>>> status['tau']
25.0
property properties: dict[str, Any]#

Return model properties dictionary.

Returns:

Dictionary with keys: - 'has_delay' (bool): True (model supports synaptic delays). - 'is_primary' (bool): True (model is a primary connection). - 'supports_hpc' (bool): True (hybrid parallel computing compatible). - 'supports_lbl' (bool): True (supports local backward learning). - 'supports_wfr' (bool): True (supports waveform relaxation).

Return type:

dict[str, Any]

send(t_spike_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#

Process one presynaptic spike and return emitted SpikeEvent payload.

Implements the complete Vogels-Sprekeler STDP update sequence for a single presynaptic spike: retrieves postsynaptic spike history, applies pairwise facilitation, facilitates with current postsynaptic trace, applies constant depression, updates presynaptic trace, and emits spike event.

Parameters:
  • t_spike_ms (float, array-like) – Presynaptic spike time in milliseconds (scalar).

  • target (Any) –

    Postsynaptic target object. Must implement:

    • get_history(t1, t2) – iterable of spike entries in (t1, t2]. Each entry must expose spike time via attribute t_/t, dict key 't_'/'t', or first tuple element.

    • get_K_value(t) or get_k_value(t) – float (postsynaptic trace \(K_-\) at time t).

  • receptor_type (int, array-like, optional) – Postsynaptic receptor port index (≥0). Included in returned event dictionary for routing. Default: 0.

  • multiplicity (float, array-like, optional) – Spike event multiplicity (≥0). Validated but not used in weight updates. Default: 1.0.

  • delay (float, array-like, optional) – Override dendritic delay (ms, >0). If None, uses self.delay. Default: None.

  • delay_steps (int, array-like, optional) – Override event delivery delay (steps, ≥1). If None, uses self.delay_steps. Default: None.

Returns:

Spike event dictionary with keys: - 'weight' (float): Updated synaptic weight after plasticity. - 'delay' (float): Effective dendritic delay (ms). - 'delay_steps' (int): Event delivery delay (steps). - 'receptor_type' (int): Postsynaptic receptor index. - 'multiplicity' (float): Event multiplicity. - 't_spike_ms' (float): Presynaptic spike time (ms). - 'Kminus' (float): Postsynaptic trace value at t_spike - delay. - 'Kplus_pre' (float): Presynaptic trace before update. - 'Kplus_post' (float): Presynaptic trace after update.

Return type:

dict[str, Any]

Raises:
  • ValueError – If delay 0, delay_steps < 1, multiplicity < 0, or any parameter is non-scalar/non-finite.

  • AttributeError – If target does not implement required methods get_history() and get_K_value()/get_k_value().

  • TypeError – If history entries do not expose spike time via expected attributes/keys.

Notes

  1. State updates are persistent: This method modifies self.weight, self.Kplus, and self.t_last_spike_ms in place.

  2. History lookup window: Retrieves postsynaptic spikes in \((t_{\text{last}} - d, t_{\text{spike}} - d]\), where \(d\) is the dendritic delay.

  3. Trace decay calculation: Presynaptic trace decays as \(K_+ \leftarrow K_+ \exp((t_{\text{last}} - t) / \tau) + 1\), ensuring continuous exponential decay between spikes.

  4. Weight clipping: Facilitation and depression operations automatically clip weights to \(\pm |W_{\max}|\) while preserving sign.

Examples

Process single spike with mock target:

>>> import brainpy.state as bp
>>> class MockTarget:
...     def get_history(self, t1, t2):
...         return [{'t_': 12.0}]  # One post spike at 12 ms
...     def get_K_value(self, t):
...         return 0.4
>>> syn = bp.vogels_sprekeler_synapse(weight=0.5, tau=20.0, eta=0.01)
>>> target = MockTarget()
>>> event = syn.send(t_spike_ms=15.0, target=target)
>>> event['weight']  # Facilitated then depressed
0.502...
>>> event['Kplus_post']  # Updated presynaptic trace
1.0

Override delay for specific spike:

>>> event = syn.send(
...     t_spike_ms=20.0,
...     target=target,
...     delay=2.5,
...     delay_steps=3
... )
>>> event['delay']
2.5
>>> event['delay_steps']
3

Access postsynaptic trace from event:

>>> event['Kminus']  # Postsynaptic trace at spike time - delay
0.4
set_status(status=None, **kwargs)[source]#

Update synapse parameters and state variables.

Modifies synapse configuration following NEST SetStatus semantics. Validates all updates and enforces constraints (positive delays/tau, non-negative Kplus, matching weight/Wmax signs).

Parameters:
  • status (dict[str, Any], optional) – Dictionary of parameter updates. Valid keys: 'weight', 'delay', 'delay_steps', 'tau', 'alpha', 'eta', 'Wmax', 'Kplus', 't_last_spike_ms'.

  • **kwargs – Additional parameter updates as keyword arguments. Merged with status dict (kwargs take precedence).

Raises:
  • ValueError – If delay 0, tau 0, delay_steps < 1, Kplus < 0, or weight and Wmax have opposite signs (when weight != 0).

  • TypeError – If parameter values are not scalar or convertible to required numeric type.

Notes

Constraint checking runs after all updates are applied, so transient inconsistent states (e.g., setting weight before Wmax) are allowed within a single call.

Examples

Update single parameter:

>>> syn = bp.vogels_sprekeler_synapse()
>>> syn.set_status({'alpha': 0.15})
>>> syn.alpha
0.15

Update multiple parameters at once:

>>> syn.set_status({'eta': 0.002, 'tau': 30.0})
>>> syn.eta, syn.tau
(0.002, 30.0)

Use keyword arguments:

>>> syn.set_status(weight=0.8, Wmax=2.0)
>>> syn.weight, syn.Wmax
(0.8, 2.0)

Invalid updates raise errors:

>>> syn.set_status({'tau': -5.0})
ValueError: tau must be > 0.
>>> syn.set_status({'Kplus': -0.1})
ValueError: State Kplus must be positive.
simulate_pre_spike_train(pre_spike_times_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#

Process a sequence of presynaptic spikes and return event list.

Iteratively calls send() for each spike time, accumulating weight updates and trace dynamics across the entire spike train. Useful for simulating synapse evolution under controlled input patterns.

Parameters:
  • pre_spike_times_ms (array-like) – Presynaptic spike times in milliseconds. Can be 1D array, list, or scalar. Automatically flattened to 1D.

  • target (Any) – Postsynaptic target (see send() for interface requirements).

  • receptor_type (int, array-like, optional) – Postsynaptic receptor port (see send()). Default: 0.

  • multiplicity (float, array-like, optional) – Event multiplicity (see send()). Default: 1.0.

  • delay (float, array-like, optional) – Dendritic delay override (ms, see send()). Default: None.

  • delay_steps (int, array-like, optional) – Delivery delay override (steps, see send()). Default: None.

Returns:

List of spike event dictionaries (one per input spike), in temporal order. Each dict has same structure as send() return value.

Return type:

list[dict[str, Any]]

Notes

State evolution: Because send() modifies synapse state (weight, Kplus, t_last_spike_ms), the returned events reflect cumulative plasticity. Event i depends on events 0 through i-1.

Examples

Simulate regular spike train:

>>> import numpy as np
>>> import brainpy.state as bp
>>> class MockTarget:
...     def get_history(self, t1, t2):
...         # Postsynaptic spikes at 10, 30, 50 ms
...         return [{'t_': t} for t in [10, 30, 50] if t1 < t <= t2]
...     def get_K_value(self, t):
...         return 0.3
>>> syn = bp.vogels_sprekeler_synapse(weight=0.5, eta=0.01)
>>> target = MockTarget()
>>> pre_times = np.arange(5, 60, 10)  # Spikes at 5, 15, 25, 35, 45, 55 ms
>>> events = syn.simulate_pre_spike_train(pre_times, target)
>>> len(events)
6
>>> [e['weight'] for e in events]  # Weight trajectory
[0.498..., 0.502..., 0.505..., 0.509..., 0.512..., 0.515...]

Extract presynaptic trace evolution:

>>> kplus_trajectory = [e['Kplus_post'] for e in events]
>>> kplus_trajectory[0]  # After first spike
1.0
>>> kplus_trajectory[-1]  # After last spike
1.0

Weight evolution with strong depression:

>>> syn2 = bp.vogels_sprekeler_synapse(
...     weight=1.0,
...     alpha=0.5,
...     eta=0.02
... )
>>> events2 = syn2.simulate_pre_spike_train([10, 20, 30], target)
>>> [e['weight'] for e in events2]  # Depression dominates
[0.996..., 0.992..., 0.988...]
to_spike_event(t_spike_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#

Alias for send() method.

Identical to send() with the same parameters and return value. Provided for API compatibility with alternative naming conventions.

See also

send

Primary spike processing method (full documentation).

Return type:

dict[str, Any]