stdp_synapse#

class brainpy.state.stdp_synapse(weight=1.0, delay=Quantity(1., 'ms'), receptor_type=0, tau_plus=Quantity(20., 'ms'), tau_minus=Quantity(20., 'ms'), lambda_=0.01, alpha=1.0, mu_plus=1.0, mu_minus=1.0, Wmax=100.0, Kplus=0.0, post=None, name=None)#

NEST-compatible stdp_synapse connection model.

stdp_synapse implements pair-based spike-timing dependent plasticity (STDP) following Guetig et al. (2003) and the NEST reference implementation from models/stdp_synapse.h. The model supports asymmetric Hebbian learning with configurable weight-dependent potentiation and depression exponents.

The synapse maintains three dynamic state variables per connection:

  • weight: current synaptic efficacy (plastic, updated on each presynaptic spike)

  • Kplus: presynaptic eligibility trace (exponentially decays with time constant tau_plus)

  • t_lastspike: timestamp of the most recent presynaptic spike

Postsynaptic spike history is stored internally with time constant tau_minus. In NEST, tau_minus is a postsynaptic neuron parameter (ArchivingNode); here it is stored on the synapse for standalone compatibility, enabling STDP simulation without requiring postsynaptic neurons to implement archiving APIs.

1. Mathematical Model

State Variables

  • w: Synaptic weight (plastic, bounded to \([0, W_{\max}]\) or \([W_{\max}, 0]\))

  • K^+: Presynaptic eligibility trace (decays with \(\tau_+\))

  • K^-: Postsynaptic eligibility trace (decays with \(\tau_-\))

Continuous-time dynamics (between spikes):

\[ \begin{align}\begin{aligned}\frac{dK^+}{dt} = -\frac{K^+}{\tau_+}\\\frac{dK^-}{dt} = -\frac{dK^-}{\tau_-}\end{aligned}\end{align} \]

Upon presynaptic spike at time \(t_{\text{pre}}\):

Let \(d\) denote the dendritic (synaptic) delay. The NEST stdp_synapse::send method performs the following sequence:

Step 1: Facilitation (potentiation) from past postsynaptic spikes

For each postsynaptic spike \(t_{\text{post}}\) in the window \((t_{\text{last}} - d,\, t_{\text{pre}} - d]\), where \(t_{\text{last}}\) is the timestamp of the previous presynaptic spike:

\[ \begin{align}\begin{aligned}\Delta t = (t_{\text{post}} + d) - t_{\text{last}}\\K^+_{\text{eff}} = K^+ \cdot e^{(t_{\text{last}} - (t_{\text{post}} + d)) / \tau_+}\\\hat{w} \leftarrow \hat{w} + \lambda (1 - \hat{w})^{\mu_+} K^+_{\text{eff}}\end{aligned}\end{align} \]

where \(\hat{w} = w / W_{\max}\) is the normalized weight.

Step 2: Depression from current presynaptic spike

Retrieve postsynaptic trace \(K^-\) at time \(t_{\text{pre}} - d\):

\[ \begin{align}\begin{aligned}K^-_{\text{eff}} = K^-(t_{\text{pre}} - d)\\\hat{w} \leftarrow \hat{w} - \alpha \lambda \hat{w}^{\mu_-} K^-_{\text{eff}}\end{aligned}\end{align} \]

Step 3: Deliver spike event

Send event with updated weight w to the postsynaptic receiver.

Step 4: Update presynaptic trace

\[ \begin{align}\begin{aligned}K^+ \leftarrow K^+ \cdot e^{(t_{\text{last}} - t_{\text{pre}}) / \tau_+} + 1\\t_{\text{last}} \leftarrow t_{\text{pre}}\end{aligned}\end{align} \]

Upon postsynaptic spike at time \(t_{\text{post}}\):

\[ \begin{align}\begin{aligned}\begin{split}K^- \\leftarrow K^- \\cdot e^{(t_{\\text{last\\_post}} - t_{\\text{post}}) / \\tau_-} + 1\end{split}\\\begin{split}t_{\\text{last\\_post}} \\leftarrow t_{\\text{post}}\end{split}\end{aligned}\end{align} \]

Weight Update Functions:

Potentiation (post-before-pre, \(\Delta t > 0\)):

\[\hat{w} \leftarrow \hat{w} + \lambda (1 - \hat{w})^{\mu_+} K^+\]

Depression (pre-before-post, \(\Delta t < 0\)):

\[\hat{w} \leftarrow \hat{w} - \alpha \lambda \hat{w}^{\mu_-} K^-\]

Final weight is clipped to \([0, W_{\max}]\) for positive weights, or \([W_{\max}, 0]\) for negative weights (inhibitory synapses).

2. Update Ordering and NEST Compatibility

This implementation replicates the exact update sequence from NEST models/stdp_synapse.h::send():

  1. Query postsynaptic spike history in window \((t_{\text{last}} - d,\, t_{\text{pre}} - d]\)

  2. Apply facilitation for each retrieved postsynaptic spike

  3. Compute postsynaptic trace \(K^-\) at \(t_{\text{pre}} - d\)

  4. Apply depression based on \(K^-\)

  5. Schedule weighted spike event for delivery after delay \(d\)

  6. Update presynaptic trace \(K^+\) and timestamp \(t_{\text{last}}\)

3. Event Timing Semantics

As in NEST, this model uses on-grid spike timestamps and ignores precise sub-step offsets. Spike times are discretized to simulation time steps:

  • Presynaptic spike detected at step n → stamped at \(t_{\text{spike}} = t + dt\)

  • Postsynaptic spike recorded at step n → stamped at \(t_{\text{spike}} = t + dt\)

  • Inter-spike intervals computed from discrete timestamps

This differs from continuous-time STDP but matches NEST’s default behavior.

4. Stability Constraints and Computational Implications

Parameter Constraints:

  • \(\tau_+ > 0\), \(\tau_- > 0\) (time constants must be positive)

  • \(\lambda > 0\) (learning rate; negative values would invert plasticity)

  • \(\alpha \geq 0\) (depression scaling; typically 1.0)

  • \(\mu_+ \geq 0\), \(\mu_- \geq 0\) (exponents; 1.0 for linear, 0.0 for additive)

  • \(W_{\max} \neq 0\) and \(\text{sign}(w) = \text{sign}(W_{\max})\)

  • \(K^+ \geq 0\) (trace must be non-negative)

Numerical Considerations

  • All state variables stored as Python float (float64 precision)

  • Exponential decays computed using math.exp() for numerical stability

  • Power functions use math.pow() (may degrade for large exponents)

  • Per-spike cost: \(O(N_{\text{post}})\) where \(N_{\text{post}}\) is the number of postsynaptic spikes in the facilitation window

Behavioral Regimes:

  • Symmetric STDP (\(\alpha = 1\), \(\mu_+ = \mu_- = 1\)): Classical pair-based rule (Song et al., 2000)

  • Additive STDP (\(\mu_+ = \mu_- = 0\)): Weight-independent updates (van Rossum et al., 2000)

  • Multiplicative STDP (\(\mu_+ = \mu_- = 1\)): Soft bounds stabilize weight distributions (Guetig et al., 2003)

  • Asymmetric depression (\(\alpha > 1\)): Stronger depression relative to potentiation

Failure Modes

  • weight and Wmax must have the same sign; otherwise ValueError on init or set

  • Kplus must be non-negative; otherwise ValueError on init or set

  • Postsynaptic spike history grows unbounded if not cleared; use clear_post_history() periodically for long simulations

Parameters:
  • weight (float, array-like, or Quantity, optional) – Initial synaptic weight \(w\). Scalar float, dimensionless or with receiver-specific units (e.g., pA, nS). Must have the same sign as Wmax. Default: 1.0 (dimensionless).

  • delay (float, array-like, or Quantity, optional) – Synaptic transmission delay \(d\) in milliseconds. Must be > 0. Quantized to integer time steps per static_synapse conventions. Default: 1.0 * u.ms.

  • receptor_type (int, optional) – Receptor port identifier on the postsynaptic neuron. Non-negative integer specifying which input channel receives the event. Default: 0.

  • tau_plus (float, array-like, or Quantity, optional) – Presynaptic trace time constant \(\tau_+\) in milliseconds. Must be > 0. Controls the width of the potentiation (post-before-pre) window. Default: 20.0 * u.ms.

  • tau_minus (float, array-like, or Quantity, optional) – Postsynaptic trace time constant \(\tau_-\) in milliseconds. Must be > 0. In NEST, this is a postsynaptic neuron parameter; here it is stored on the synapse for standalone compatibility. Controls the width of the depression (pre-before-post) window. Default: 20.0 * u.ms.

  • lambda (float, array-like, or Quantity, optional) – Learning rate parameter \(\lambda\) (dimensionless). Scales both potentiation and depression updates. Typical values: 0.001–0.1. Default: 0.01.

  • alpha (float, array-like, or Quantity, optional) – Asymmetry parameter \(\alpha\) (dimensionless). Scales depression relative to potentiation. \(\alpha = 1.0\) yields symmetric STDP; \(\alpha > 1.0\) strengthens depression. Default: 1.0.

  • mu_plus (float, array-like, or Quantity, optional) – Potentiation exponent \(\mu_+\) (dimensionless). Controls weight dependence of potentiation. \(\mu_+ = 0\): additive; \(\mu_+ = 1\): multiplicative (soft upper bound). Default: 1.0.

  • mu_minus (float, array-like, or Quantity, optional) – Depression exponent \(\mu_-\) (dimensionless). Controls weight dependence of depression. \(\mu_- = 0\): additive; \(\mu_- = 1\): multiplicative (soft lower bound). Default: 1.0.

  • Wmax (float, array-like, or Quantity, optional) – Maximum weight bound \(W_{\max}\) (same units as weight). Weights are clipped to \([0, W_{\max}]\) for excitatory synapses or \([W_{\max}, 0]\) for inhibitory synapses. Must have the same sign as weight. Default: 100.0 (dimensionless).

  • Kplus (float, array-like, or Quantity, optional) – Initial presynaptic trace value \(K^+\) (dimensionless). Must be non-negative. Typically initialized to 0.0 (no presynaptic history). Default: 0.0.

  • post (Dynamics, optional) – Default postsynaptic receiver object. If provided, send() and update() will target this receiver unless overridden. Must implement either add_delta_input or add_current_input methods. Default: None (must provide receiver explicitly in method calls).

  • name (str, optional) – Unique identifier for this synapse instance. Default: auto-generated.

Parameter Mapping

NEST stdp_synapse parameters map to this implementation as follows:

NEST Parameter

brainpy.state Param

Notes

weight

weight

Plastic, updated on each pre-spike

delay

delay

Converted to ms, discretized to steps

receptor_type

receptor_type

Integer ≥ 0

tau_plus

tau_plus

Pre-synaptic trace decay (ms)

tau_minus

(neuron param)

Here: synapse param tau_minus (ms)

lambda

lambda_

Learning rate (underscore to avoid keyword)

alpha

alpha

Depression asymmetry factor

mu_plus

mu_plus

Potentiation exponent

mu_minus

mu_minus

Depression exponent

Wmax

Wmax

Weight upper bound (or lower for inhib.)

Kplus

Kplus

Pre-synaptic trace state variable

weight#

Current synaptic weight (plastic, updated during simulation).

Type:

float

Kplus#

Current presynaptic trace value.

Type:

float

t_lastspike#

Timestamp (ms) of the most recent presynaptic spike.

Type:

float

tau_plus#

Presynaptic trace time constant (ms).

Type:

float

tau_minus#

Postsynaptic trace time constant (ms).

Type:

float

lambda_#

Learning rate.

Type:

float

alpha#

Depression asymmetry factor.

Type:

float

mu_plus#

Potentiation exponent.

Type:

float

mu_minus#

Depression exponent.

Type:

float

Wmax#

Maximum weight bound.

Type:

float

See also

static_synapse

Base class for non-plastic synapses

tsodyks_synapse

Short-term plasticity (depression/facilitation)

stdp_synapse_hom

Homogeneous-weight variant with shared weight across connections

Notes

  • The model transmits spike-like events only (event_type='spike').

  • update(pre_spike=..., post_spike=...) accepts both presynaptic and postsynaptic spike multiplicities for standalone STDP simulation.

  • record_post_spike(...) can be used to manually feed postsynaptic spikes when the postsynaptic model does not expose NEST archiving APIs.

  • Postsynaptic spike history grows unbounded; call clear_post_history() periodically in long simulations to prevent memory issues.

References

Examples

Basic STDP synapse with default parameters:

>>> import brainpy.state as bst
>>> import saiunit as u
>>> syn = bst.stdp_synapse(weight=0.5, delay=1.0 * u.ms)
>>> syn.get()
{'weight': 0.5, 'delay': 1.0, 'receptor_type': 0, 'tau_plus': 20.0,
 'tau_minus': 20.0, 'lambda': 0.01, 'alpha': 1.0, 'mu_plus': 1.0,
 'mu_minus': 1.0, 'Wmax': 100.0, 'Kplus': 0.0, 'synapse_model': 'stdp_synapse'}

Asymmetric STDP (stronger depression):

>>> syn = bst.stdp_synapse(
...     weight=1.0,
...     tau_plus=16.8 * u.ms,
...     tau_minus=33.7 * u.ms,
...     lambda_=0.005,
...     alpha=1.05,  # 5% stronger depression
...     Wmax=2.0,
... )

Additive STDP (weight-independent updates):

>>> syn = bst.stdp_synapse(
...     weight=0.5,
...     mu_plus=0.0,  # additive potentiation
...     mu_minus=0.0,  # additive depression
...     lambda_=0.001,
...     Wmax=1.0,
... )

Manual postsynaptic spike recording:

>>> import brainstate
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     syn = bst.stdp_synapse(weight=1.0)
...     syn.init_state()
...     # Simulate postsynaptic spike at t=5.0 ms
...     syn.record_post_spike(multiplicity=1, t_spike_ms=5.0)
...     # Simulate presynaptic spike at t=10.0 ms (after post-spike)
...     # This should potentiate the weight
...     syn.send(multiplicity=1)  # uses on-grid stamp t + dt
...     print(f"Updated weight: {syn.weight:.6f}")  # > 1.0 (potentiated)
clear_post_history()[source]#

Clear internal postsynaptic spike history and reset trace state.

Resets all postsynaptic STDP state to initial conditions:

  • Clears spike history buffer (timestamps and trace values)

  • Resets postsynaptic trace K^- to zero

  • Resets last postsynaptic spike timestamp to -1.0

This method should be called periodically in long simulations to prevent unbounded growth of the spike history buffer. Typical usage: clear history at the start of each trial or after weight convergence phases.

See also

init_state

Reinitialize all synapse state including weights and traces

record_post_spike

Record postsynaptic spikes into the history buffer

get()[source]#

Return current public parameters and mutable state.

Retrieves all NEST-compatible parameters and dynamic state variables in a dictionary format suitable for inspection, logging, or serialization. Includes both inherited parameters from static_synapse (weight, delay, receptor_type) and STDP-specific parameters.

Returns:

Dictionary containing:

  • 'weight' : float – Current synaptic weight (plastic)

  • 'delay' : float – Transmission delay (ms)

  • 'receptor_type' : int – Postsynaptic receptor port

  • 'tau_plus' : float – Presynaptic trace time constant (ms)

  • 'tau_minus' : float – Postsynaptic trace time constant (ms)

  • 'lambda' : float – Learning rate (key name without underscore)

  • 'alpha' : float – Depression asymmetry factor

  • 'mu_plus' : float – Potentiation exponent

  • 'mu_minus' : float – Depression exponent

  • 'Wmax' : float – Maximum weight bound

  • 'Kplus' : float – Current presynaptic trace value

  • 'synapse_model' : str – Always 'stdp_synapse' (NEST identifier)

Return type:

dict

See also

set

Update parameters and state

init_state

Reinitialize state to defaults

Examples

>>> syn = bst.stdp_synapse(weight=0.5, lambda_=0.01)
>>> params = syn.get()
>>> params['weight']
0.5
>>> params['lambda']
0.01
>>> params['synapse_model']
'stdp_synapse'
init_state(batch_size=None, **kwargs)[source]#

Initialize synapse state for simulation.

Resets all dynamic state variables to their initial values:

  • Kplus: presynaptic trace → initial value (default 0.0)

  • t_lastspike: last presynaptic spike time → 0.0 ms

  • Postsynaptic spike history and trace → cleared

This method should be called before starting a new simulation or trial. Inherits delay queue initialization from static_synapse.

Parameters:
  • batch_size (int, optional) – Ignored (provided for API compatibility with batched models).

  • **kwargs – Ignored (provided for API compatibility).

See also

clear_post_history

Clear only postsynaptic history without resetting other state

set

Update parameters without reinitializing state

record_post_spike(multiplicity=1.0, *, t_spike_ms=None)[source]#

Record postsynaptic spikes into the internal STDP history buffer.

This method updates the postsynaptic eligibility trace \(K^-\) and stores the spike timestamp for later use by send() when processing presynaptic spikes. Each recorded spike increments \(K^-\) by 1.0 after exponential decay from the previous postsynaptic spike.

The trace update follows:

\[\begin{split}K^- \\leftarrow K^- \\cdot e^{(t_{\\text{last\\_post}} - t_{\\text{spike}}) / \\tau_-} + 1\end{split}\]

where \(t_{\\text{last\\_post}}\) is the timestamp of the previous postsynaptic spike and \(\\tau_-\) is the postsynaptic trace time constant.

Multiple spikes can be recorded by setting multiplicity > 1. This is equivalent to calling the method multiplicity times at the same timestamp.

Parameters:
  • multiplicity (float, array-like, or Quantity, optional) – Number of postsynaptic spikes to record at this timestamp. Must be a non-negative integer-valued scalar (fractional values will be rejected). Use multiplicity=0 to skip recording (returns immediately). Default: 1 (single spike).

  • t_spike_ms (float, array-like, or Quantity, optional) – Spike timestamp in milliseconds. Must be a scalar float with or without time units. If None, uses the current on-grid spike stamp \(t + dt\) where \(t\) is the current simulation time and \(dt\) is the simulation time step. Default: None (on-grid).

Returns:

Number of spikes successfully recorded (equal to multiplicity).

Return type:

int

Raises:

ValueError

  • If multiplicity is negative or not integer-valued. - If t_spike_ms is not a finite scalar.

See also

clear_post_history

Clear all postsynaptic spike history

send

Process presynaptic spike and apply STDP weight updates

Examples

Record single postsynaptic spike at current time:

>>> import brainstate
>>> import saiunit as u
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     syn = bst.stdp_synapse(weight=1.0)
...     syn.init_state()
...     count = syn.record_post_spike()  # uses t + dt
...     print(count)
1

Record postsynaptic spike at explicit timestamp:

>>> syn.record_post_spike(multiplicity=1, t_spike_ms=5.0)
1

Record burst of 3 postsynaptic spikes:

>>> syn.record_post_spike(multiplicity=3)
3
send(multiplicity=1.0, *, post=None, receptor_type=None)[source]#

Schedule one outgoing event with NEST stdp_synapse dynamics.

Processes a presynaptic spike, applies STDP weight updates (facilitation from past postsynaptic spikes and depression from current spike), then schedules the event for delayed delivery to the postsynaptic receiver. This method replicates the exact update sequence from NEST models/stdp_synapse.h::send().

Update sequence:

  1. Compute inter-spike interval \(h = t_{\text{spike}} - t_{\text{last}}\)

  2. Retrieve postsynaptic spikes in window \((t_{\\text{last}} - d,\\, t_{\\text{spike}} - d]\)

  3. Apply facilitation for each retrieved postsynaptic spike (post-before-pre)

  4. Compute postsynaptic trace \(K^-\) at \(t_{\text{spike}} - d\)

  5. Apply depression based on \(K^-\) (pre-before-post)

  6. Schedule weighted event for delivery at \(t_{\text{spike}} + \text{delay}\)

  7. Update presynaptic trace: \(K^+ \\leftarrow K^+ \\cdot e^{-h/\\tau_+} + 1\)

  8. Update last spike timestamp: \(t_{\\text{last}} \\leftarrow t_{\\text{spike}}\)

The final delivered weight is \(w_{\\text{eff}} = w \\cdot \\text{multiplicity}\) where \(w\) is the plasticity-updated weight.

Parameters:
  • multiplicity (float, array-like, or Quantity, optional) – Presynaptic spike multiplicity (event magnitude). Scalar value, typically 1.0 for a single spike or 0.0 to skip transmission. The delivered payload is scaled by this factor. Default: 1.0.

  • post (Dynamics, optional) – Postsynaptic receiver object for this event. If None, uses the default receiver specified during initialization. Must implement add_delta_input or handle spike events. Default: None (use default receiver).

  • receptor_type (int, optional) – Receptor port override for this event. If None, uses the synapse’s default receptor_type. Default: None (use default receptor).

Returns:

True if an event was scheduled (multiplicity != 0), False otherwise.

Return type:

bool

Raises:

ValueError

  • If multiplicity is not a finite scalar. - If receptor_type is negative or not an integer. - If no postsynaptic receiver is available (neither post argument nor default receiver specified).

See also

update

High-level method combining event delivery, post-spike recording, and sending

record_post_spike

Manually record postsynaptic spikes for STDP

Notes

  • Spike timestamp uses on-grid time \(t + dt\) (NEST convention).

  • Dendritic delay \(d\) shifts the STDP causality window backward in time.

  • Postsynaptic spike history is never cleared by this method; call clear_post_history() periodically to prevent memory growth.

Examples

Send single presynaptic spike:

>>> import brainstate
>>> import saiunit as u
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     post_neuron = bst.LIF(1)
...     syn = bst.stdp_synapse(weight=1.0, post=post_neuron)
...     syn.init_state()
...     post_neuron.init_state()
...     success = syn.send(multiplicity=1.0)
...     print(success)
True

Send presynaptic spike with receptor override:

>>> syn.send(multiplicity=1.0, receptor_type=1)  # target receptor port 1
True

Skip transmission (zero multiplicity):

>>> syn.send(multiplicity=0.0)
False
set(*, weight=<object object>, delay=<object object>, receptor_type=<object object>, tau_plus=<object object>, tau_minus=<object object>, lambda_=<object object>, alpha=<object object>, mu_plus=<object object>, mu_minus=<object object>, Wmax=<object object>, Kplus=<object object>, post=<object object>)[source]#

Set NEST-style public parameters and mutable state.

Updates one or more synapse parameters and state variables without reinitializing the full simulation state. Mimics NEST’s SetStatus API. All parameters are validated before assignment to ensure consistency (e.g., weight and Wmax must have the same sign).

Only specified parameters are updated; unspecified parameters retain their current values. To reset all state to initial conditions, use init_state() instead.

Parameters:
  • weight (float, array-like, or Quantity, optional) – New synaptic weight. Must have the same sign as Wmax (or new Wmax if both are specified). Validated before assignment. Default: unchanged.

  • delay (float, array-like, or Quantity, optional) – New transmission delay (ms). Must be positive. Will be discretized to integer time steps on next usage. Default: unchanged.

  • receptor_type (int, optional) – New receptor port identifier. Must be non-negative. Default: unchanged.

  • tau_plus (float, array-like, or Quantity, optional) – New presynaptic trace time constant (ms). Must be positive. Default: unchanged.

  • tau_minus (float, array-like, or Quantity, optional) – New postsynaptic trace time constant (ms). Must be positive. Default: unchanged.

  • lambda (float, array-like, or Quantity, optional) – New learning rate. Typically positive. Default: unchanged.

  • alpha (float, array-like, or Quantity, optional) – New depression asymmetry factor. Typically non-negative. Default: unchanged.

  • mu_plus (float, array-like, or Quantity, optional) – New potentiation exponent. Must be non-negative. Default: unchanged.

  • mu_minus (float, array-like, or Quantity, optional) – New depression exponent. Must be non-negative. Default: unchanged.

  • Wmax (float, array-like, or Quantity, optional) – New maximum weight bound. Must have the same sign as weight (or new weight if both are specified). Default: unchanged.

  • Kplus (float, array-like, or Quantity, optional) – New presynaptic trace value. Must be non-negative. Typically used to restore saved state rather than manipulate during simulation. Default: unchanged.

  • post (Dynamics, optional) – New default postsynaptic receiver. Default: unchanged.

Raises:

ValueError

  • If weight and Wmax have different signs. - If Kplus is negative. - If any parameter has non-finite values or incorrect shape.

See also

get

Retrieve current parameters and state

init_state

Reinitialize all state to defaults

Examples

Update learning rate during simulation:

>>> syn = bst.stdp_synapse(weight=1.0, lambda_=0.01)
>>> syn.set(lambda_=0.001)  # reduce learning rate
>>> syn.get()['lambda']
0.001

Update multiple parameters atomically:

>>> syn.set(
...     weight=0.5,
...     Wmax=2.0,
...     alpha=1.1,
... )

Restore saved state:

>>> saved_params = syn.get()
>>> # ... simulation ...
>>> syn.set(**{k: v for k, v in saved_params.items()
...            if k != 'synapse_model'})  # restore all except model name
update(pre_spike=0.0, *, post_spike=0.0, post=None, receptor_type=None)[source]#

Deliver due events, update post history, then process pre spikes.

High-level update method combining all STDP synapse operations for a single simulation time step. This method is typically called once per time step in network simulations and handles:

  1. Delivery of delayed events from previous time steps

  2. Recording of postsynaptic spikes into the STDP history buffer

  3. Aggregation of presynaptic inputs (from current_inputs and delta_inputs)

  4. STDP weight update and event scheduling via send()

The update order ensures correct causality: delayed events are delivered before processing new spikes, and postsynaptic spikes are recorded before presynaptic spikes are processed (allowing immediate STDP updates if delay is minimal).

Update sequence:

Step 1: Deliver due events

Check the internal delay queue and deliver all events scheduled for the current simulation step to their target receivers.

Step 2: Record postsynaptic spikes

If post_spike > 0, record post_spike postsynaptic spikes at timestamp \(t + dt\) into the STDP history buffer. This updates the postsynaptic trace \(K^-\).

Step 3: Aggregate presynaptic inputs

Sum inputs from: - pre_spike argument (explicit input) - current_inputs dict (accumulated continuous inputs) - delta_inputs dict (accumulated spike inputs)

Step 4: Process presynaptic spike

If aggregated input is non-zero, call send() to apply STDP weight updates and schedule a new delayed event.

Parameters:
  • pre_spike (float, array-like, or Quantity, optional) – Presynaptic spike multiplicity (explicit input). Added to accumulated inputs from current_inputs and delta_inputs. Typically 0.0 (no explicit input) or 1.0 (single spike). Default: 0.0.

  • post_spike (float, array-like, or Quantity, optional) – Postsynaptic spike multiplicity to record. Must be a non-negative integer-valued scalar. If > 0, records the specified number of postsynaptic spikes at the current on-grid timestamp \(t + dt\). Default: 0.0 (no postsynaptic spikes).

  • post (Dynamics, optional) – Postsynaptic receiver object for event delivery. If None, uses the default receiver specified during initialization. Default: None.

  • receptor_type (int, optional) – Receptor port override for event delivery. If None, uses the synapse’s default receptor_type. Default: None.

Returns:

Number of events delivered during this time step (from the delay queue). Does not include the newly scheduled event from this time step’s presynaptic spike (that event will be counted in a future time step).

Return type:

int

Raises:

ValueError

  • If post_spike is negative or not integer-valued. - If pre_spike or aggregated inputs are not finite scalars.

See also

send

Low-level method for processing a single presynaptic spike

record_post_spike

Record postsynaptic spikes without other update operations

Notes

  • This method modifies synapse state (weight, Kplus, t_lastspike, postsynaptic history) and should be called exactly once per time step.

  • The returned delivery count reflects past events, not the current time step’s transmission.

  • For standalone STDP testing without a network, manually call record_post_spike() and send() instead of relying on update().

Examples

Typical usage in network simulation loop:

>>> import brainstate
>>> import saiunit as u
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     pre = bst.LIF(1)
...     post = bst.LIF(1)
...     syn = bst.stdp_synapse(weight=1.0, post=post)
...     pre.init_state()
...     post.init_state()
...     syn.init_state()
...     # Simulation step: presynaptic spike, no postsynaptic spike
...     delivered = syn.update(pre_spike=1.0, post_spike=0.0)
...     # Simulation step: no presynaptic spike, postsynaptic spike
...     delivered = syn.update(pre_spike=0.0, post_spike=1.0)

Standalone STDP test with explicit spike times:

>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     syn = bst.stdp_synapse(weight=1.0, tau_plus=20.0*u.ms, tau_minus=20.0*u.ms)
...     syn.init_state()
...     # Post-before-pre: potentiation expected
...     syn.record_post_spike(multiplicity=1, t_spike_ms=5.0)
...     syn.send(multiplicity=1)  # pre-spike at t + dt (uses on-grid time)
...     print(f"Weight after potentiation: {syn.weight:.6f}")  # > 1.0