clopath_synapse#

class brainpy.state.clopath_synapse(weight=1.0, delay=1.0, delay_steps=1, x_bar=0.0, tau_x=15.0, Wmin=0.0, Wmax=100.0, t_last_spike_ms=0.0, name=None)#

NEST-compatible voltage-based STDP synapse following the Clopath plasticity rule.

This synapse model implements the voltage-based spike-timing-dependent plasticity (STDP) rule described by Clopath et al. (2010). Unlike traditional pair-based STDP, weight updates depend on postsynaptic membrane voltage traces archived by the target neuron, enabling voltage-dependent learning rules that capture homeostatic regulation and triplet interactions.

1. Model Overview

The Clopath synapse is a connection-level model that modulates synaptic weights based on:

  • Presynaptic spike times and a presynaptic trace \(\bar{x}(t)\)

  • Postsynaptic voltage-derived traces for long-term potentiation (LTP) and depression (LTD)

  • Hard bounds \([W_\text{min}, W_\text{max}]\) on synaptic weight

The model requires the postsynaptic neuron to maintain voltage-dependent history buffers (e.g., aeif_psc_delta_clopath in NEST).

2. State Variables

Each connection maintains:

  • \(w\) – Current synaptic weight

  • \(\bar{x}\) – Presynaptic trace (low-pass filtered spike train)

  • \(t_\text{last}\) – Timestamp of most recent presynaptic spike (milliseconds)

  • \(\tau_x\) – Time constant for presynaptic trace decay (milliseconds)

  • \(W_\text{min}, W_\text{max}\) – Hard lower/upper weight bounds

3. Plasticity Update Sequence

On each presynaptic spike at time \(t\) (with dendritic delay \(d\) and previous spike time \(t_\text{last}\)), the following steps are executed in order:

(a) Long-Term Potentiation (LTP):

Retrieve all LTP history entries from the postsynaptic neuron in the interval \((t_\text{last} - d,\, t - d]\). For each entry with timestamp \(t_i\) and amplitude \(\text{dw}_i\):

\[w \leftarrow \min\left(W_\text{max},\, w + \text{dw}_i \cdot \bar{x} \exp\left(\frac{t_\text{last} - (t_i + d)}{\tau_x}\right)\right)\]

This facilitates the weight proportional to the presynaptic trace at the effective time \(t_i + d\), accounting for exponential decay from \(t_\text{last}\).

(b) Long-Term Depression (LTD):

Query the postsynaptic LTD value at the effective time \(t - d\):

\[w \leftarrow \max(W_\text{min},\, w - \text{dw}_\text{LTD}(t - d))\]

This depresses the weight by the current LTD trace magnitude, clamped to \(W_\text{min}\).

(c) Event Emission:

A spike event is generated with the updated weight and delay parameters.

(d) Presynaptic Trace Update:

The presynaptic trace is updated to account for the new spike:

\[\bar{x} \leftarrow \bar{x} \exp\left(\frac{t_\text{last} - t}{\tau_x}\right) + \frac{1}{\tau_x}\]

(e) Timestamp Update:

The last spike time is updated: \(t_\text{last} \leftarrow t\).

4. Mathematical Foundations

The presynaptic trace \(\bar{x}(t)\) is a low-pass filter of the presynaptic spike train \(S_\text{pre}(t) = \sum_k \delta(t - t_k)\):

\[\tau_x \frac{d\bar{x}}{dt} = -\bar{x} + S_\text{pre}(t)\]

At each spike time \(t_k\), the exact solution yields the jump condition:

\[\bar{x}(t_k^+) = \bar{x}(t_k^-) e^{-(t_k - t_{k-1})/\tau_x} + \frac{1}{\tau_x}\]

This exact event-driven update is implemented in step (d).

5. Postsynaptic Interface Requirements

The target neuron must provide:

  • get_LTP_history(t1, t2) or get_ltp_history(t1, t2): Returns iterable of LTP events in \((t_1, t_2]\)

  • get_LTD_value(t) or get_ltd_value(t): Returns scalar LTD amplitude at time \(t\)

Each LTP history entry must support extraction of:

  • Time field: t_, t, time_ms, or time

  • Weight change field: dw_, dw, delta_w, or weight_change

Supported entry formats:

  • Object with attributes t_ and dw_

  • Object with attributes t and dw

  • Dictionary with keys 't'/'t_' and 'dw'/'dw_'

  • 2-tuple (t, dw)

Parameters:
  • weight (float, optional) – Initial synaptic weight (dimensionless). Must satisfy sign consistency with Wmin and Wmax. Default: 1.0.

  • delay (float, optional) – Dendritic propagation delay in milliseconds. Must be positive. Default: 1.0.

  • delay_steps (int, optional) – Integer delay in simulation time steps for event delivery. Must be >= 1. Default: 1.

  • x_bar (float, optional) – Initial presynaptic trace value (dimensionless). Typically initialized to 0.0 before any spikes. Default: 0.0.

  • tau_x (float, optional) – Time constant for presynaptic trace exponential decay (milliseconds). Must be positive and non-zero. Controls the temporal window of LTP. Typical values: 10-20 ms. Default: 15.0.

  • Wmin (float, optional) – Hard lower bound on synaptic weight (dimensionless). Must have same sign as weight according to NEST’s internal sign checks. Default: 0.0.

  • Wmax (float, optional) – Hard upper bound on synaptic weight (dimensionless). Must have same sign as weight according to NEST’s internal sign checks. Default: 100.0.

  • t_last_spike_ms (float, optional) – Timestamp of the most recent presynaptic spike (milliseconds). Initialized to 0.0 before the first spike. Default: 0.0.

  • name (str or None, optional) – Optional identifier for this connection instance. Default: None.

Parameter Mapping

This table shows the correspondence between brainpy.state and NEST parameter names:

brainpy.state

NEST

Unit

Description

weight

weight

(unitless)

Synaptic weight

delay

delay

ms

Dendritic delay

delay_steps

(runtime)

steps

Event delivery delay

x_bar

x_bar

(unitless)

Presynaptic trace

tau_x

tau_x

ms

Presynaptic time constant

Wmin

Wmin

(unitless)

Minimum weight

Wmax

Wmax

(unitless)

Maximum weight

t_last_spike_ms

(internal state)

ms

Last spike timestamp

HAS_DELAY#

Connection supports propagation delay. Always True.

Type:

bool

IS_PRIMARY#

Connection is a primary connection type. Always True.

Type:

bool

REQUIRES_CLOPATH_ARCHIVING#

Connection requires voltage trace archiving from postsynaptic neuron. Always True.

Type:

bool

SUPPORTS_HPC#

Model supports high-performance computing infrastructure. Always True.

Type:

bool

SUPPORTS_LBL#

Model supports label-based lookup. Always True.

Type:

bool

SUPPORTS_WFR#

Model supports waveform relaxation iteration. Always True.

Type:

bool

Raises:
  • ValueError – If weight, Wmin, and Wmax do not satisfy sign consistency constraints. NEST enforces: sign(weight) == sign(Wmin) and sign(weight) == sign(Wmax), where sign tests use different comparison operators for min vs max bounds.

  • ValueError – If delay <= 0 or delay_steps < 1.

  • ValueError – If any parameter is non-finite (NaN or Inf).

  • ValueError – If tau_x is zero (division by zero in trace updates).

  • AttributeError – If target neuron does not provide required get_LTP_history and get_LTD_value methods during send() call.

See also

aeif_psc_delta_clopath

Adaptive exponential IF neuron with Clopath archiving (NEST)

hh_psc_alpha_clopath

Hodgkin-Huxley neuron with Clopath archiving (NEST)

stdp_synapse

Traditional pair-based STDP synapse

Notes

Implementation Details:

  • All internal computations use 64-bit floating point (float64) to match NEST precision.

  • Precise sub-grid spike timing offsets are ignored; all spike times are treated as exact multiples of the simulation time step.

  • The update sequence strictly follows clopath_synapse::send() in NEST to ensure numerical equivalence.

  • Sign constraints use NEST’s asymmetric comparison operators: Wmin uses >= vs < while Wmax uses > vs <=.

Biological Interpretation:

The Clopath rule captures key experimental observations:

  • LTP depends on presynaptic activity (spike trace \(\bar{x}\)) and postsynaptic depolarization (voltage-derived LTP trace).

  • LTD depends on presynaptic spikes paired with postsynaptic voltage without strong depolarization.

  • The voltage dependence enables homeostatic regulation: neurons with high baseline firing rates have reduced LTP, preventing runaway excitation.

  • The model reproduces triplet STDP effects without explicit triplet terms.

Computational Considerations:

  • Memory overhead scales with the number of LTP history entries archived by the postsynaptic neuron (typically bounded by a sliding time window).

  • For large fan-in networks, the LTP history query in step (a) may become a bottleneck. Consider using sparse indexing or binned histograms for postsynaptic traces.

  • The exponential decay calculations use math.exp for scalar operations. For vectorized implementations, replace with jax.numpy.exp or equivalent.

References

Examples

Basic Usage:

Create a Clopath synapse with default parameters:

>>> import brainpy.state as bst
>>> synapse = bst.clopath_synapse(weight=0.5, tau_x=15.0, Wmin=0.0, Wmax=1.0)
>>> synapse.get_status()
{'weight': 0.5, 'tau_x': 15.0, 'Wmin': 0.0, 'Wmax': 1.0, ...}

Simulating Presynaptic Spike Train:

Assuming a postsynaptic neuron with Clopath archiving:

>>> # Mock target neuron with required interface
>>> class ClopathNeuron:
...     def get_ltp_history(self, t1, t2):
...         # Return LTP events in (t1, t2]
...         return [(10.5, 0.05), (12.3, 0.08)]  # (time_ms, dw)
...     def get_ltd_value(self, t):
...         # Return LTD amplitude at time t
...         return 0.02
>>> target = ClopathNeuron()
>>> synapse = bst.clopath_synapse(weight=1.0, tau_x=10.0, Wmin=0.0, Wmax=5.0)
>>> # Process spike train
>>> spike_times = [10.0, 20.0, 30.0]
>>> events = synapse.simulate_pre_spike_train(spike_times, target)
>>> print(f"Final weight: {synapse.weight:.3f}")
Final weight: 1.123

Weight Evolution with Voltage-Dependent Plasticity:

>>> synapse = bst.clopath_synapse(weight=1.0, tau_x=15.0, Wmin=-2.0, Wmax=2.0)
>>> # Simulate pairing protocol: pre before post (LTP)
>>> for t in [10, 20, 30]:
...     event = synapse.send(t_spike_ms=t, target=target)
>>> print(f"Weight after LTP protocol: {synapse.weight:.3f}")
Weight after LTP protocol: 1.450

Sign Constraint Validation:

>>> # Valid: all same sign (positive)
>>> synapse = bst.clopath_synapse(weight=1.0, Wmin=0.0, Wmax=5.0)
>>> # Invalid: mixed signs
>>> try:
...     synapse = bst.clopath_synapse(weight=1.0, Wmin=-1.0, Wmax=5.0)
... except ValueError as e:
...     print(e)
Weight and Wmin must have same sign.
get(key='status')[source]#

Retrieve connection status or specific parameter value.

Parameters:

key (str, optional) – Key to retrieve. Use 'status' for full status dictionary, or specify a parameter name (e.g., 'weight', 'tau_x', 'Wmin'). Default: 'status'.

Returns:

If key == 'status', returns full status dictionary. Otherwise returns the requested parameter value with type matching the parameter (float, int, or bool).

Return type:

dict[str, Any] or float or int or bool

Raises:

KeyError – If key is not 'status' and does not match any parameter or property name in the status dictionary.

Examples

>>> synapse = bst.clopath_synapse(weight=1.5, tau_x=12.0)
>>> synapse.get('weight')
1.5
>>> synapse.get('tau_x')
12.0
>>> status = synapse.get('status')
>>> status['Wmax']
100.0
get_status()[source]#

Retrieve current connection state and parameter values.

Returns:

Dictionary containing all connection parameters, state variables, and properties:

  • 'weight' (float): Current synaptic weight

  • 'delay' (float): Dendritic delay in milliseconds

  • 'delay_steps' (int): Integer delay in simulation steps

  • 'x_bar' (float): Current presynaptic trace value

  • 'tau_x' (float): Presynaptic trace time constant (ms)

  • 'Wmin' (float): Minimum weight bound

  • 'Wmax' (float): Maximum weight bound

  • '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

  • 'requires_clopath_archiving' (bool): Archiving requirement flag

  • 'supports_hpc' (bool): HPC support flag

  • 'supports_lbl' (bool): Label-based lookup flag

  • 'supports_wfr' (bool): Waveform relaxation flag

Return type:

dict[str, Any]

Notes

This method provides NEST-compatible status retrieval. All values are returned as Python native types (float, int, bool) rather than NumPy arrays.

property properties: dict[str, Any]#

Return dictionary of connection model properties and capabilities.

Returns:

Dictionary with keys:

  • 'has_delay' (bool): Connection supports propagation delay

  • 'is_primary' (bool): Connection is primary type

  • 'requires_clopath_archiving' (bool): Requires voltage trace archiving

  • 'supports_hpc' (bool): High-performance computing support

  • 'supports_lbl' (bool): Label-based lookup support

  • 'supports_wfr' (bool): Waveform relaxation support

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 with Clopath plasticity and return spike event payload.

This method implements the core plasticity update sequence:

  1. Query postsynaptic LTP history in the interval since last presynaptic spike

  2. Apply facilitation (LTP) for each history entry using decayed presynaptic trace

  3. Apply depression (LTD) at the current effective spike time

  4. Update presynaptic trace and last spike timestamp

  5. Return spike event with updated weight

Parameters:
  • t_spike_ms (float or array-like) – Current presynaptic spike time in milliseconds (scalar). Must be finite.

  • target (object) – Postsynaptic neuron or target object. Must provide get_LTP_history(t1, t2) (or get_ltp_history) and get_LTD_value(t) (or get_ltd_value) methods.

  • receptor_type (int or array-like, optional) – Receptor port index on target neuron (scalar integer). Default: 0.

  • multiplicity (float or array-like, optional) – Spike event multiplicity (scalar, >= 0). Used for batch spike processing. Default: 1.0.

  • delay (float or array-like or None, optional) – Override dendritic delay for this spike (milliseconds, scalar). If None, uses connection’s stored self.delay. Default: None.

  • delay_steps (int or array-like or None, optional) – Override integer delay in time steps for this event (scalar). If None, uses connection’s stored self.delay_steps. Default: None.

Returns:

Spike event payload dictionary with keys:

  • 'weight' (float): Updated synaptic weight after plasticity

  • 'delay' (float): Dendritic delay used (milliseconds)

  • 'delay_steps' (int): Integer delay in time steps

  • 'receptor_type' (int): Target receptor port index

  • 'multiplicity' (float): Spike multiplicity

  • 't_spike_ms' (float): Presynaptic spike timestamp

Return type:

dict[str, Any]

Raises:
  • ValueError – If tau_x is zero (division by zero in exponential decay calculations).

  • ValueError – If any parameter is non-scalar or non-finite.

  • ValueError – If delay <= 0, delay_steps < 1, or multiplicity < 0.

  • AttributeError – If target does not provide required get_LTP_history and get_LTD_value methods (or their lowercase variants).

  • ValueError – If any LTP history entry does not provide extractable time and weight change fields.

Notes

Update Sequence Details:

Let \(t\) = t_spike_ms, \(d\) = effective delay, \(t_\text{last}\) = self.t_last_spike_ms.

Step 1: LTP Application

For each LTP history entry \((t_i, \text{dw}_i)\) in \((t_\text{last} - d, t - d]\):

\[w \leftarrow \min(W_\text{max},\, w + \text{dw}_i \cdot \bar{x} \exp((t_\text{last} - (t_i + d)) / \tau_x))\]

Step 2: LTD Application

\[w \leftarrow \max(W_\text{min},\, w - \text{dw}_\text{LTD}(t - d))\]

Step 3: Presynaptic Trace Update

\[\bar{x} \leftarrow \bar{x} \exp((t_\text{last} - t) / \tau_x) + 1 / \tau_x\]

Step 4: Timestamp Update

\[t_\text{last} \leftarrow t\]

Side Effects

This method modifies connection state:

  • self.weight: Updated by LTP and LTD

  • self.x_bar: Updated with new spike contribution

  • self.t_last_spike_ms: Set to current spike time

Performance Considerations:

The LTP history query dominates runtime for neurons with many incoming connections. Consider using bounded history buffers (sliding window) in the target neuron to limit the number of entries returned.

Examples

Process single spike:

>>> class MockTarget:
...     def get_ltp_history(self, t1, t2):
...         return [(5.5, 0.1)]  # One LTP event
...     def get_ltd_value(self, t):
...         return 0.02
>>> target = MockTarget()
>>> synapse = bst.clopath_synapse(weight=1.0, tau_x=10.0, Wmin=0.0, Wmax=5.0)
>>> event = synapse.send(t_spike_ms=10.0, target=target)
>>> print(f"Updated weight: {event['weight']:.3f}")
Updated weight: 1.023

Process spike with custom delay:

>>> event = synapse.send(t_spike_ms=20.0, target=target, delay=2.5)
>>> print(f"Delay used: {event['delay']} ms")
Delay used: 2.5 ms
set_delay(delay)[source]#

Set dendritic propagation delay.

Parameters:

delay (float or array-like) – New delay in milliseconds (scalar). Must be positive.

Raises:

ValueError – If delay is non-scalar, non-finite, or <= 0.

set_delay_steps(delay_steps)[source]#

Set integer delay in simulation time steps.

Parameters:

delay_steps (int or array-like) – New delay in time steps (scalar integer). Must be >= 1.

Raises:

ValueError – If delay_steps is non-scalar, non-finite, non-integer, or < 1.

set_status(status=None, **kwargs)[source]#

Update connection parameters and state variables.

Parameters:
  • status (dict[str, Any] or None, optional) – Dictionary of parameter name-value pairs to update. Supported keys: 'weight', 'delay', 'delay_steps', 'x_bar', 'tau_x', 'Wmin', 'Wmax', 't_last_spike_ms'. Default: None.

  • **kwargs – Additional parameter updates as keyword arguments. These are merged with status dictionary; keyword arguments take precedence.

Raises:
  • ValueError – If updated parameters violate sign consistency constraints (weight, Wmin, Wmax must all have compatible signs).

  • ValueError – If delay <= 0 or delay_steps < 1.

  • ValueError – If any parameter value is non-finite (NaN or Inf).

Notes

This method provides NEST-compatible parameter setting. Sign constraints are re-checked after all updates are applied. If multiple parameters are updated together, validation occurs atomically after all changes.

Examples

Update single parameter:

>>> synapse = bst.clopath_synapse(weight=1.0)
>>> synapse.set_status(weight=0.5)
>>> synapse.get_status()['weight']
0.5

Update multiple parameters:

>>> synapse.set_status({'weight': 2.0, 'tau_x': 20.0})
>>> synapse.set_status(Wmin=0.0, Wmax=5.0)
set_weight(weight)[source]#

Set synaptic weight value.

Parameters:

weight (float or array-like) – New synaptic weight value (scalar). Must be finite and satisfy sign consistency with Wmin and Wmax.

Raises:

ValueError – If weight is non-scalar, non-finite, or violates sign constraints.

Notes

This is a convenience method equivalent to set_status(weight=...), but does not re-check sign constraints (assumes they were satisfied during initialization).

simulate_pre_spike_train(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 payloads for each.

This method sequentially processes multiple presynaptic spikes, updating connection state (weight, presynaptic trace, last spike time) after each spike. The plasticity updates are cumulative: each spike’s LTP/LTD application affects the weight seen by subsequent spikes.

Parameters:
  • spike_times_ms (array-like) – Array of presynaptic spike times in milliseconds. Values are converted to 1-D float64 array and processed in order. Must contain finite values.

  • target (object) – Postsynaptic neuron providing get_LTP_history and get_LTD_value methods.

  • receptor_type (int or array-like, optional) – Receptor port index (scalar). Default: 0.

  • multiplicity (float or array-like, optional) – Spike multiplicity for all events (scalar). Default: 1.0.

  • delay (float or array-like or None, optional) – Dendritic delay override (milliseconds). If None, uses self.delay. Default: None.

  • delay_steps (int or array-like or None, optional) – Integer delay override (time steps). If None, uses self.delay_steps. Default: None.

Returns:

List of spike event payloads, one per input spike time. Each event dictionary contains the same keys as returned by send(): 'weight', 'delay', 'delay_steps', 'receptor_type', 'multiplicity', 't_spike_ms'.

Return type:

list[dict[str, Any]]

Raises:
  • ValueError – If any spike time is non-finite or if tau_x is zero.

  • AttributeError – If target does not provide required methods.

Notes

Ordering Effects:

Spike times are processed in array order (not necessarily sorted by time). For biologically realistic simulations, ensure spike_times_ms is sorted in ascending order. Out-of-order spikes may produce unphysical weight trajectories due to incorrect exponential decay calculations.

State Evolution:

After processing spike train [t1, t2, ..., tn], the connection state reflects:

  • self.weight: Cumulative effect of all LTP/LTD updates

  • self.x_bar: Presynaptic trace at time tn

  • self.t_last_spike_ms: Set to tn

Memory Considerations:

The returned event list stores a separate dictionary for each spike. For very long spike trains (>10^6 spikes), consider processing in batches to reduce memory overhead.

Examples

Process spike train and track weight evolution:

>>> class MockTarget:
...     def get_ltp_history(self, t1, t2):
...         # Return one LTP event per query interval
...         if t2 > t1:
...             return [((t1 + t2) / 2, 0.05)]
...         return []
...     def get_ltd_value(self, t):
...         return 0.01
>>> target = MockTarget()
>>> synapse = bst.clopath_synapse(weight=1.0, tau_x=10.0, Wmin=0.0, Wmax=3.0)
>>> spike_times = [10.0, 20.0, 30.0, 40.0, 50.0]
>>> events = synapse.simulate_pre_spike_train(spike_times, target)
>>> weights = [evt['weight'] for evt in events]
>>> print(f"Weight trajectory: {weights}")
Weight trajectory: [1.045, 1.083, 1.115, 1.142, 1.165]

Verify presynaptic trace evolution:

>>> synapse = bst.clopath_synapse(weight=1.0, tau_x=15.0)
>>> events = synapse.simulate_pre_spike_train([0.0, 15.0, 30.0], target)
>>> print(f"Final x_bar: {synapse.x_bar:.3f}")
Final x_bar: 0.091
to_spike_event(t_spike_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#

Alias for send() method with identical semantics.

This method provides an alternative name for spike event generation, maintaining compatibility with different naming conventions. All parameters and return values are identical to send().

See also

send

Primary spike processing method with full documentation

Return type:

dict[str, Any]