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_synapseconnection 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\):
History lookup: Retrieve postsynaptic spikes in \((t_{\text{last}} - d, t - d]\)
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)\]Current postsynaptic trace facilitation:
\[w \leftarrow \operatorname{facilitate}(w, K_-(t - d))\]Constant depression:
\[w \leftarrow \operatorname{depress}(w)\]Emit spike event using updated weight
Update presynaptic trace:
\[K_+ \leftarrow K_+ \exp\left(\frac{t_{\text{last}} - t}{\tau}\right) + 1\]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 asWmaxif 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.0ms.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.0ms.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 asweight(ifweight != 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.0ms.name (
str, optional) – Model instance name for identification. Default:None.
See also
stdp_synapseClassical asymmetric STDP rule
stdp_dopamine_synapseReward-modulated STDP
Parameter Mapping
NEST Parameter
brainpy.state
Unit
weightweightunitless
delaydelayms
delay_stepsdelay_stepssteps
tautaums
alphaalphaunitless
etaetaunitless
WmaxWmaxunitless
KplusKplusunitless
t_lastspiket_last_spike_msms
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 attributet_ort, dict key't_'or't', or first tuple element.get_K_value(t)orget_k_value(t)– Returns postsynaptic STDP trace \(K_-\) at timet(in ms). Must return float.
Dendritic delay semantics: Unlike axonal delays (which shift spike arrival time), the
delayparameter here controls the temporal window for history lookup: \((t_{\text{last}} - d, t - d]\). This implements NEST’s dendritic delay convention.Sign constraints: If
weight != 0, bothweightandWmaxmust have the same sign. Attempting to set opposite signs raisesValueError. This preserves synapse type (excitatory/inhibitory) throughout learning.Trace positivity:
Kplusmust remain non-negative. Negative values raiseValueErrorduring initialization orset_status().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.
Event multiplicity: The
multiplicityparameter insend()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 (seeget_status()). Otherwise, returns the requested parameter value.- Return type:
Any- Raises:
KeyError – If
keyis 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
GetStatussemantics.- 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 attributet_/t, dict key't_'/'t', or first tuple element.get_K_value(t)orget_k_value(t)– float (postsynaptic trace \(K_-\) at timet).
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). IfNone, usesself.delay. Default:None.delay_steps (
int,array-like, optional) – Override event delivery delay (steps, ≥1). IfNone, usesself.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 att_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
targetdoes not implement required methodsget_history()andget_K_value()/get_k_value().TypeError – If history entries do not expose spike time via expected attributes/keys.
Notes
State updates are persistent: This method modifies
self.weight,self.Kplus, andself.t_last_spike_msin place.History lookup window: Retrieves postsynaptic spikes in \((t_{\text{last}} - d, t_{\text{spike}} - d]\), where \(d\) is the dendritic delay.
Trace decay calculation: Presynaptic trace decays as \(K_+ \leftarrow K_+ \exp((t_{\text{last}} - t) / \tau) + 1\), ensuring continuous exponential decay between spikes.
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
SetStatussemantics. 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
statusdict (kwargs take precedence).
- Raises:
ValueError – If
delay ≤ 0,tau ≤ 0,delay_steps < 1,Kplus < 0, orweightandWmaxhave opposite signs (whenweight != 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 (seesend()for interface requirements).receptor_type (
int,array-like, optional) – Postsynaptic receptor port (seesend()). Default:0.multiplicity (
float,array-like, optional) – Event multiplicity (seesend()). Default:1.0.delay (
float,array-like, optional) – Dendritic delay override (ms, seesend()). Default:None.delay_steps (
int,array-like, optional) – Delivery delay override (steps, seesend()). 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. Eventidepends on events0throughi-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
sendPrimary spike processing method (full documentation).