jonke_synapse#
- class brainpy.state.jonke_synapse(weight=1.0, delay=1.0, delay_steps=1, Kplus=0.0, t_last_spike_ms=0.0, alpha=1.0, beta=0.0, lambda_=0.01, mu_plus=0.0, mu_minus=0.0, tau_plus=20.0, Wmax=100.0, name=None)#
NEST-compatible
jonke_synapseconnection model with weight-dependent STDP.Implements spike-timing-dependent plasticity with exponential weight dependence and additive offsets, following NEST’s
jonke_synapsesemantics. The model applies multiplicative weight factors \(\exp(\mu w)\) to both facilitation and depression branches, producing nonlinear weight dynamics that can stabilize synaptic strengths or implement homeostatic control.1. Mathematical Formulation
The plasticity rule operates on synaptic weight \(w(t)\) using presynaptic trace \(K_+(t)\) (with time constant \(\tau_+\)) and postsynaptic trace \(K_-(t)\):
\[\begin{split}\frac{dK_+}{dt} &= -\frac{K_+}{\tau_+} + \sum_f \delta(t - t_f^{\text{pre}}) \\ \frac{dK_-}{dt} &= -\frac{K_-}{\tau_-} + \sum_j \delta(t - t_j^{\text{post}})\end{split}\]Weight-dependent plasticity kernels:
\[\begin{split}\Phi_+(w) &= \exp(\mu_+ w) \\ \Phi_-(w) &= \exp(\mu_- w)\end{split}\]Update rules (applied at spike times):
\[\begin{split}\Delta w_+ &= \lambda \left( \Phi_+(w) K_+ - \beta \right) \quad \text{(facilitation)} \\ \Delta w_- &= \lambda \left( -\alpha \Phi_-(w) K_- - \beta \right) \quad \text{(depression)}\end{split}\]The weight is hard-bounded to \([0, W_{\max}]\) after each update.
2. Temporal Dynamics
At each presynaptic spike at time \(t\):
History lookup: Read postsynaptic spikes in \((t_{\text{last}} - d,\; t - d]\)
Facilitation pass: For each post-spike \(t_j\) in history:
\[ \begin{align}\begin{aligned}K_+^{\text{eff}} = K_+(t_{\text{last}}) \exp\left(\frac{t_{\text{last}} - (t_j + d)}{\tau_+}\right)\\w \leftarrow w + \lambda \left( \Phi_+(w) K_+^{\text{eff}} - \beta \right)\end{aligned}\end{align} \]Depression: Using postsynaptic \(K_-(t - d)\):
\[w \leftarrow w + \lambda \left( -\alpha \Phi_-(w) K_-(t-d) - \beta \right)\]Event emission: Spike delivered with updated weight
Trace update:
\[K_+ \leftarrow K_+ \exp\left(\frac{t_{\text{last}} - t}{\tau_+}\right) + 1\]State update: \(t_{\text{last}} \leftarrow t\)
3. Design Considerations
Exponential weight dependence: \(\mu_+ > 0\) implements soft upper bound (larger weights resist growth); \(\mu_+ < 0\) enables runaway potentiation. Similarly for \(\mu_-\) and depression.
Additive offset :math:`beta`: Shifts both update branches uniformly. Positive \(\beta\) biases toward depression, negative toward potentiation. Can implement heterosynaptic competition.
Asymmetric depression scaling: \(\alpha\) allows independent control of depression amplitude relative to potentiation.
Numerical stability: For large \(|\mu w|\), \(\exp(\mu w)\) may overflow/underflow. Consider \(\mu\) values keeping \(|\mu W_{\max}| < 10\).
4. Computational Properties
Time complexity: \(O(N_{\text{post-spikes}})\) per presynaptic spike, where \(N_{\text{post-spikes}}\) is the count in delay window.
Dendritic delay semantics: History lookups use \(t - d\) (compensating for backpropagation time). Event delivery uses delay_steps (axonal propagation).
Precision: Sub-grid spike timing (offset component in NEST) is ignored; all updates use grid-aligned times only.
- Parameters:
weight (
floatorarray-like, default1.0) – Initial synaptic efficacy (dimensionless or pA/mV). Must be finite. Updated during plasticity and bounded to \([0, W_{\max}]\).delay (
floatorarray-like, default1.0) – Dendritic delay in milliseconds for history lookups and depression timing (\(d\) in equations). Must be positive.delay_steps (
intorarray-like, default1) – Axonal event delivery delay in simulation time steps. Must be ≥ 1. Typically set to match delay quantized to grid resolution.Kplus (
floatorarray-like, default0.0) – Initial presynaptic trace value \(K_+(0)\). Must be non-negative. Evolves according to \(\tau_+\) dynamics.t_last_spike_ms (
floatorarray-like, default0.0) – Timestamp of last presynaptic spike in milliseconds. Used for trace decay computation between spikes.alpha (
floatorarray-like, default1.0) – Depression amplitude scaling factor. \(\alpha = 1\) gives symmetric update magnitudes (when \(\mu_+ = \mu_- = 0, \beta = 0\)).beta (
floatorarray-like, default0.0) – Additive offset applied to both update branches (dimensionless). Positive values bias toward depression. Enables heterosynaptic effects.lambda (
floatorarray-like, default0.01) – Learning rate \(\lambda\). Controls plasticity time scale. Set to 0 to disable learning. Typical values: \(10^{-4}\) to \(10^{-1}\).mu_plus (
floatorarray-like, default0.0) – Facilitation weight dependence exponent \(\mu_+\). Positive values create soft upper bound. Units: inverse of weight units.mu_minus (
floatorarray-like, default0.0) – Depression weight dependence exponent \(\mu_-\). Positive values accelerate depression at high weights.tau_plus (
floatorarray-like, default20.0) – Presynaptic trace time constant in milliseconds \(\tau_+\). Controls potentiation temporal window. Typical range: 10–40 ms.Wmax (
floatorarray-like, default100.0) – Hard upper weight bound \(W_{\max}\). Weights exceeding this after updates are clipped. Lower bound is always 0.name (
strorNone, optional) – Instance identifier for debugging and logging.
Parameter Mapping
NEST Parameter
brainpy.state Parameter
Units / Notes
weightweightdimensionless
delaydelayms
delay_stepsdelay_stepssteps
KplusKplusdimensionless
t_lastspiket_last_spike_msms
alphaalphadimensionless
betabetadimensionless
lambdalambda_dimensionless
mu_plusmu_plus1/weight
mu_minusmu_minus1/weight
tau_plustau_plusms
WmaxWmaxsame as weight
- Raises:
If
Kplus < 0(violates trace non-negativity). - Ifdelay <= 0(non-physical delay). - Ifdelay_steps < 1(invalid event scheduling). - If any parameter is non-finite (NaN or ±inf). - If scalar parameters have size ≠ 1.
Notes
Target interface requirements: The postsynaptic target object passed to
send()must implement:get_history(t1, t2) -> iterable: Returns postsynaptic spike times in \((t_1, t_2]\). Each entry is an object/dict/tuple with time accessible via.t_,.t,['t_'],['t'], or first element.get_K_value(t) -> floatorget_k_value(t) -> float: Returns depression trace \(K_-(t)\) at time t.
NEST compatibility: Reproduces behavior of
nest-simulator/models/jonke_synapse.cppincluding parameter validation, update ordering, and spike event payload structure.Sub-grid timing: Unlike NEST’s precise spike timing mode, this implementation uses grid-aligned times only (ignoring offset components).
Homeostatic interpretation: With \(\mu_+ > 0\) and appropriate \(\beta\), the model can implement sliding threshold mechanisms that stabilize weight distributions.
Examples
Basic STDP with linear weight dependence:
>>> import brainpy.state as bp >>> # Create synapse with standard STDP parameters >>> syn = bp.jonke_synapse( ... weight=5.0, ... delay=1.0, ... lambda_=0.01, ... tau_plus=20.0, ... alpha=1.0, ... beta=0.0, ... mu_plus=0.0, ... mu_minus=0.0, ... Wmax=10.0 ... ) >>> syn.get_status()['weight'] 5.0
Exponential weight dependence (soft bounds):
>>> # Mu_plus > 0 creates resistance to potentiation at high weights >>> syn_bounded = bp.jonke_synapse( ... weight=1.0, ... lambda_=0.01, ... mu_plus=0.1, ... mu_minus=0.05, ... Wmax=20.0 ... ) >>> # At w=10: Phi_+(10) = exp(0.1*10) = 2.72 (potentiation enhanced) >>> # At w=0: Phi_+(0) = 1.0 (baseline)
Heterosynaptic plasticity via beta offset:
>>> # Positive beta biases toward depression >>> syn_hetero = bp.jonke_synapse( ... weight=5.0, ... lambda_=0.005, ... beta=0.05, ... alpha=1.2 ... ) >>> # All weights slowly decay even without post-spikes (beta term)
Simulate spike-pair interaction:
>>> class MockTarget: ... def get_history(self, t1, t2): ... # Return single post-spike at t=15 ms ... if t1 < 15.0 <= t2: ... return [{'t_': 15.0}] ... return [] ... def get_k_value(self, t): ... # Kminus trace at depression check time ... return 0.8 >>> >>> target = MockTarget() >>> syn = bp.jonke_synapse(weight=5.0, lambda_=0.01, tau_plus=20.0) >>> >>> # Pre-spike at t=10 ms (pre-before-post → no facilitation yet) >>> event1 = syn.send(t_spike_ms=10.0, target=target) >>> print(f"Weight after pre@10: {event1['weight']:.3f}") Weight after pre@10: 4.992 >>> >>> # Pre-spike at t=20 ms (post@15 in history → facilitation) >>> event2 = syn.send(t_spike_ms=20.0, target=target) >>> print(f"Weight after pre@20: {event2['weight']:.3f}") Weight after pre@20: 5.034
See also
stdp_synapseClassical pair-based STDP without weight dependence.
stdp_triplet_synapseTriplet STDP rule with better experimental fit.
vogels_sprekeler_synapseInhibitory STDP for E/I balance.
References
- get(key='status')[source]#
Retrieve parameter or full status dictionary by key (NEST Get compatible).
- Parameters:
key (
str, default'status') – Parameter name or'status'for full dictionary. Valid keys include:'weight','delay','Kplus','lambda','tau_plus', etc.- Returns:
If
key='status': full dictionary fromget_status().Otherwise: scalar value of the requested parameter.
- Return type:
Any- Raises:
KeyError – If
keyis not'status'and not found in status dictionary.
Examples
>>> syn = bp.jonke_synapse(weight=7.0, tau_plus=25.0) >>> syn.get('weight') 7.0 >>> syn.get('tau_plus') 25.0 >>> syn.get('status')['lambda'] 0.01
- get_status()[source]#
Retrieve complete synapse state snapshot (NEST GetStatus compatible).
Returns current values of all parameters, state variables, and model capabilities. Output format matches NEST’s
GetStatusdictionary structure.- Returns:
Dictionary containing:
weight(float): Current synaptic efficacy.delay(float): Dendritic delay in ms.delay_steps(int): Event delivery delay in steps.Kplus(float): Current presynaptic trace value.t_last_spike_ms(float): Last presynaptic spike time in ms.alpha(float): Depression scaling factor.beta(float): Additive offset.lambda(float): Learning rate (key name uses NEST convention).mu_plus(float): Facilitation weight exponent.mu_minus(float): Depression weight exponent.tau_plus(float): Presynaptic trace time constant in ms.Wmax(float): Maximum weight bound.size_of(int): Memory footprint in bytes.Capability flags (
has_delay,is_primary, etc.).
- Return type:
dict[str,Any]
Examples
>>> syn = bp.jonke_synapse(weight=3.5, lambda_=0.02, tau_plus=15.0) >>> status = syn.get_status() >>> print(status['weight'], status['lambda'], status['tau_plus']) 3.5 0.02 15.0
- property properties: dict[str, Any]#
NEST synapse model capability flags.
- Returns:
Dictionary with boolean capability flags:
has_delay: Supports delayed spike delivery (always True).is_primary: Primary connection type for spike transmission (always True).supports_hpc: Compatible with NEST’s high-performance computing mode (True).supports_lbl: Supports label-based connectivity (True).supports_wfr: Supports waveform relaxation method (always False for plasticity models).
- Return type:
dict[str,Any]
- send(t_spike_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#
Process presynaptic spike with plasticity and return spike event payload.
Implements the full NEST
jonke_synapse::send()protocol:Retrieve postsynaptic spike history in delay-compensated window
Apply facilitation for each postsynaptic spike in history
Apply depression using current postsynaptic trace
Update presynaptic trace and timestamp
Return spike event dictionary with updated weight
This is the core method for spike-driven plasticity computation.
- Parameters:
t_spike_ms (
floatorarray-like) – Current presynaptic spike time in milliseconds. Must be scalar and ≥t_last_spike_ms(non-decreasing spike times assumed).target (
object) –Postsynaptic neuron or recorder object. Must implement:
get_history(t1, t2) -> iterable: Postsynaptic spike times in (t1, t2].get_K_value(t) -> floatorget_k_value(t) -> float: Depression trace \(K_-(t)\).
receptor_type (
intorarray-like, default0) – Postsynaptic receptor channel identifier (e.g., 0=AMPA, 1=NMDA, 2=GABA_A). Passed through to event payload without modification.multiplicity (
floatorarray-like, default1.0) – Spike event amplitude multiplier. Must be non-negative. Scales effective weight in postsynaptic neuron. Typical use: probabilistic synapses or multi-vesicle release.delay (
floatorarray-likeorNone, optional) – Override dendritic delay for this spike (in ms). If None, usesself.delay. Affects history lookup window: \((t_{\text{last}} - d,\; t - d]\).delay_steps (
intorarray-likeorNone, optional) – Override event delivery delay (in steps). If None, usesself.delay_steps. Determines when postsynaptic neuron receives the spike.
- Returns:
Spike event payload dictionary containing:
weight(float): Updated synaptic efficacy after plasticity.delay(float): Dendritic delay used (ms).delay_steps(int): Event delivery delay (steps).receptor_type(int): Postsynaptic receptor channel.multiplicity(float): Spike amplitude multiplier.t_spike_ms(float): Presynaptic spike time (ms).Kminus(float): Postsynaptic trace value at depression check time.Kplus_pre(float): Presynaptic trace before update.Kplus_post(float): Presynaptic trace after update.
- Return type:
dict[str,Any]- Raises:
If
t_spike_ms,receptor_type, ormultiplicityare non-scalar. - Ifmultiplicity < 0. - Ifdelay <= 0ordelay_steps < 1(when overriding defaults).
AttributeError – If
targetdoes not implement requiredget_history()orget_K_value()methods.TypeError – If history entries do not expose time via supported interface (see Notes).
Notes
History entry format: Each entry from
target.get_history(t1, t2)must be:Object with
.t_or.tattribute, ORDictionary with
't_'or't'key, ORTuple/list where first element is time (float).
Delay semantics: History lookup uses \(t - d\) to account for backpropagation delay. Event delivery uses
delay_stepsfor forward propagation.State mutation: Updates
self.weight,self.Kplus, andself.t_last_spike_msin place. Not thread-safe without external synchronization.Causality: If \(t_{\text{spike}} < t_{\text{last}}\), trace decay may produce negative exponential argument (mathematically valid but may indicate simulation error).
Examples
Basic spike transmission:
>>> class PostNeuron: ... def get_history(self, t1, t2): ... return [] # No post-spikes ... def get_K_value(self, t): ... return 0.5 # Constant depression trace >>> >>> syn = bp.jonke_synapse(weight=5.0, lambda_=0.01, beta=0.02) >>> target = PostNeuron() >>> event = syn.send(t_spike_ms=10.0, target=target) >>> >>> print(f"Weight: {event['weight']:.3f}") Weight: 4.980 >>> # Depression applied: dw = 0.01 * (-1.0 * 1.0 * 0.5 - 0.02) = -0.007 >>> # Weight bounded: max(0, 5.0 - 0.007) = 4.993 (approx, with exp factors)
Spike-pair potentiation:
>>> class PostNeuron: ... def get_history(self, t1, t2): ... # Post-spike at t=12 ms (between t1 and t2) ... if t1 < 12.0 <= t2: ... return [{'t_': 12.0}] ... return [] ... def get_K_value(self, t): ... return 0.0 # No depression trace yet >>> >>> syn = bp.jonke_synapse(weight=5.0, lambda_=0.01, tau_plus=20.0) >>> target = PostNeuron() >>> >>> # First pre-spike at t=10 ms (before post@12) >>> event1 = syn.send(t_spike_ms=10.0, target=target) >>> print(f"Weight after pre@10: {event1['weight']:.3f}, Kplus: {event1['Kplus_post']:.3f}") Weight after pre@10: 5.000, Kplus: 1.000 >>> >>> # Second pre-spike at t=15 ms (post@12 now in history) >>> event2 = syn.send(t_spike_ms=15.0, target=target) >>> print(f"Weight after pre@15: {event2['weight']:.3f}") Weight after pre@15: 5.009 >>> # Facilitation from Kplus(10) decayed to t=12: exp((10-13)/20) ≈ 0.861
Override delay per spike:
>>> event = syn.send( ... t_spike_ms=20.0, ... target=target, ... delay=2.5, ... delay_steps=3 ... ) >>> print(event['delay'], event['delay_steps']) 2.5 3
See also
to_spike_eventAlias for
send()(NEST naming compatibility).simulate_pre_spike_trainProcess multiple spikes in sequence.
- set_delay(delay)[source]#
Update dendritic delay (convenience method).
- Parameters:
delay (
floatorarray-like) – New delay in milliseconds. Must be positive and finite.- Raises:
ValueError – If delay ≤ 0, non-scalar, or non-finite.
- set_delay_steps(delay_steps)[source]#
Update event delivery delay in steps (convenience method).
- Parameters:
delay_steps (
intorarray-like) – New delay in simulation time steps. Must be ≥ 1.- Raises:
ValueError – If delay_steps < 1, non-integer, or non-finite.
- set_status(status=None, **kwargs)[source]#
Update synapse parameters and state (NEST SetStatus compatible).
Modifies any subset of parameters. Unspecified keys retain current values. Validates all updates before applying (atomic operation). Accepts both dictionary argument and keyword arguments (merged with kwargs taking precedence).
- Parameters:
status (
dict[str,Any]orNone, optional) – Dictionary of parameter updates. Keys match those inget_status(). If None, onlykwargsare processed.**kwargs – Additional parameter updates as keyword arguments. Merged with
statusdict. If bothstatus['key']andkey=valueare provided for the same parameter,kwargstakes precedence.
- Raises:
If
Kplusis set to negative value. - Ifdelay <= 0ordelay_steps < 1. - If bothlambdaandlambda_are provided with different values. - If any scalar parameter is non-finite or has size ≠ 1.
Notes
The learning rate can be specified as either
lambda(NEST convention) orlambda_(Python identifier). Both refer to the same internal state.Validation occurs after all updates are collected, ensuring atomic updates (all succeed or all fail).
Setting
lambda=0disables plasticity without affecting trace dynamics.
Examples
>>> syn = bp.jonke_synapse(weight=5.0, lambda_=0.01) >>> syn.set_status({'weight': 8.0, 'lambda': 0.005}) >>> syn.get_status()['weight'] 8.0 >>> syn.get_status()['lambda'] 0.005
Keyword argument syntax:
>>> syn.set_status(Wmax=50.0, mu_plus=0.1) >>> syn.get_status()['Wmax'] 50.0
Disable learning:
>>> syn.set_status(lambda_=0.0) >>> # Synapse now transmits spikes but does not update weight
- set_weight(weight)[source]#
Update synaptic weight (convenience method).
- Parameters:
weight (
floatorarray-like) – New synaptic efficacy value. Must be scalar and finite.- Raises:
ValueError – If weight is non-scalar or non-finite.
- simulate_pre_spike_train(pre_spike_times_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#
Process sequence of presynaptic spikes and track weight evolution.
Convenience method for simulating complete spike train interactions. Sequentially calls
send()for each spike time, maintaining plasticity state across spikes. Useful for analyzing STDP curves, weight trajectories, or protocol responses.- Parameters:
pre_spike_times_ms (
array-like) – Presynaptic spike times in milliseconds. Shape:(n_spikes,)or any shape (will be flattened). Times need not be sorted but should be non-decreasing for physically meaningful trace dynamics.target (
object) – Postsynaptic target with required interface (same assend()).receptor_type (
intorarray-like, default0) – Receptor channel identifier (constant for all spikes).multiplicity (
floatorarray-like, default1.0) – Spike amplitude multiplier (constant for all spikes).delay (
floatorarray-likeorNone, optional) – Override dendritic delay for all spikes (ms). If None, usesself.delay.delay_steps (
intorarray-likeorNone, optional) – Override event delivery delay for all spikes (steps).
- Returns:
Event payloads for each spike, in order. Length equals
len(pre_spike_times_ms). Each dictionary has structure documented insend().- Return type:
list[dict[str,Any]]
Notes
State evolution: Synapse state (
weight,Kplus,t_last_spike_ms) evolves across the sequence. Final state reflects cumulative plasticity from all spikes.Performance: For large spike trains (>10⁴ spikes), consider batching or vectorized implementations if available.
Non-sorted times: If times are unsorted, trace decay may produce unexpected results (negative exponential arguments). Always verify input ordering.
Examples
STDP pairing protocol (pre-post and post-pre pairs):
>>> class PostNeuron: ... def __init__(self): ... self.post_spikes = [15.0, 35.0] # Post-spikes at t=15, 35 ... def get_history(self, t1, t2): ... return [{'t_': t} for t in self.post_spikes if t1 < t <= t2] ... def get_K_value(self, t): ... return 1.0 if t >= 15.0 else 0.0 >>> >>> syn = bp.jonke_synapse(weight=5.0, lambda_=0.01, tau_plus=20.0) >>> target = PostNeuron() >>> >>> # Pre-spikes at t=[10, 20, 30, 40] ms >>> events = syn.simulate_pre_spike_train( ... pre_spike_times_ms=[10.0, 20.0, 30.0, 40.0], ... target=target ... ) >>> >>> # Track weight evolution >>> for i, evt in enumerate(events): ... print(f"Spike {i}: t={evt['t_spike_ms']:.1f} ms, weight={evt['weight']:.4f}") Spike 0: t=10.0 ms, weight=5.0000 Spike 1: t=20.0 ms, weight=5.0085 Spike 2: t=30.0 ms, weight=5.0112 Spike 3: t=40.0 ms, weight=5.0098
Extract weight trajectory:
>>> weights = [evt['weight'] for evt in events] >>> pre_traces = [evt['Kplus_post'] for evt in events] >>> print(weights) [5.0, 5.0085, 5.0112, 5.0098]
Frequency-dependent plasticity:
>>> # High-frequency pre-spikes (10 Hz for 1 sec) >>> times = np.arange(0, 1000, 100) # t=0, 100, 200, ..., 900 ms >>> events = syn.simulate_pre_spike_train(times, target) >>> print(f"Initial weight: {events[0]['weight']:.3f}") >>> print(f"Final weight: {events[-1]['weight']:.3f}")
See also
sendSingle spike processing (core method).
- to_spike_event(t_spike_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#
Alias for
send()method (NEST naming compatibility).Identical functionality to
send(). Provided for API consistency with NEST’sConnection::to_spike_event()naming convention.- Parameters:
t_spike_ms (
floatorarray-like) – Presynaptic spike time in milliseconds.target (
object) – Postsynaptic target with required interface.receptor_type (
intorarray-like, default0) – Receptor channel identifier.multiplicity (
floatorarray-like, default1.0) – Spike amplitude multiplier.delay (
floatorarray-likeorNone, optional) – Override dendritic delay (ms).delay_steps (
intorarray-likeorNone, optional) – Override event delivery delay (steps).
- Returns:
Spike event payload (see
send()for structure).- Return type:
dict[str,Any]
See also
sendPrimary spike processing method (full documentation).