# Copyright 2026 BrainX Ecosystem Limited. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
# -*- coding: utf-8 -*-
import math
import brainstate
import saiunit as u
import jax.numpy as jnp
import numpy as np
from brainstate.typing import ArrayLike
from .static_synapse import static_synapse
__all__ = [
'quantal_stp_synapse',
]
_UNSET = object()
class quantal_stp_synapse(static_synapse):
r"""NEST-compatible ``quantal_stp_synapse`` connection model.
Probabilistic synapse model with short-term plasticity based on stochastic
quantal vesicle release. Implements the Tsodyks-Markram model with
discrete release sites, where each presynaptic spike triggers
probabilistic release from a finite pool of vesicle docking sites.
Mathematical Model
------------------
Each synapse maintains four state variables:
- ``u`` -- dynamic release probability per site (evolves with facilitation)
- ``n`` -- total number of release sites (fixed)
- ``a`` -- currently available (recovered) release sites
- ``t_lastspike`` -- timestamp of last presynaptic spike
**1. State Evolution Between Spikes**
Upon arrival of a presynaptic spike at time :math:`t_s`, if a previous
spike occurred at :math:`t_{\mathrm{last}}` (i.e., ``t_lastspike >= 0``):
.. math::
h = t_s - t_{\mathrm{last}}
p_{\mathrm{decay}} = e^{-h/\tau_{\mathrm{rec}}}
u_{\mathrm{decay}} =
\begin{cases}
0, & \tau_{\mathrm{fac}} < 10^{-10} \\
e^{-h/\tau_{\mathrm{fac}}}, & \text{otherwise}
\end{cases}
u \leftarrow U + u(1 - U) \, u_{\mathrm{decay}}
The release probability ``u`` undergoes:
- Depression baseline: decays toward baseline ``U``
- Facilitation: modulated by exponential decay with time constant
:math:`\tau_{\mathrm{fac}}`
- Special case: if :math:`\tau_{\mathrm{fac}} < 10^{-10}`, no
facilitation occurs (:math:`u_{\mathrm{decay}} = 0`), and ``u`` is
reset to ``U``
**2. Stochastic Recovery of Release Sites**
Depleted sites recover probabilistically. For each of the :math:`n - a`
depleted sites, an independent Bernoulli trial with success probability
:math:`1 - p_{\mathrm{decay}}` determines recovery. This implements
exponential-distributed recovery times in discrete form.
**3. Stochastic Vesicle Release**
For each of the ``a`` currently available sites, an independent Bernoulli
trial with success probability ``u`` determines whether that site releases
a vesicle. Let :math:`n_{\mathrm{release}}` denote the total number of
sites that released.
**4. Synaptic Transmission**
If :math:`n_{\mathrm{release}} > 0`:
.. math::
w_{\mathrm{eff}} = n_{\mathrm{release}} \cdot w
where ``w`` is the baseline per-site weight. The effective weight
:math:`w_{\mathrm{eff}}` is delivered to the postsynaptic neuron after
the synaptic delay. The number of available sites is updated:
.. math::
a \leftarrow a - n_{\mathrm{release}}
If :math:`n_{\mathrm{release}} = 0`, no event is sent (synaptic failure).
**5. Timestamp Update**
Regardless of whether an event was transmitted, the last spike time is
updated:
.. math::
t_{\mathrm{lastspike}} \leftarrow t_s
**Implementation Fidelity**
The update ordering matches NEST ``models/quantal_stp_synapse.h::send``
exactly:
1. Update ``u`` (facilitation)
2. Recover depleted sites
3. Draw released sites
4. Send event if any release occurred
5. Update ``t_lastspike``
**Probabilistic Properties**
- **Expected release**: :math:`\mathbb{E}[n_{\mathrm{release}}] = a \cdot u`
- **Variance**: :math:`\mathrm{Var}[n_{\mathrm{release}}] = a \cdot u \cdot (1 - u)`
- **Failure probability**: :math:`P(\text{no release}) = (1 - u)^a`
For small ``u`` and moderate ``a``, the distribution of released vesicles
is approximately Poisson with rate :math:`\lambda = a \cdot u`.
**Event Timing Semantics**
As in NEST, the model operates on discrete spike timestamps and ignores
sub-step temporal offsets. Each event processed at simulation step ``t``
uses the on-grid timestamp ``t + dt`` for all decay calculations.
Parameters
----------
weight : float or array_like, optional
Baseline per-site synaptic weight ``w`` (dimensionless or with units).
The effective transmitted weight is :math:`w_{\mathrm{eff}} =
n_{\mathrm{release}} \cdot w`. Default: ``1.0``.
delay : float or array_like, optional
Synaptic transmission delay. Must be positive and include time units.
Default: ``1.0 * u.ms``.
receptor_type : int, optional
Target receptor port identifier on the postsynaptic neuron. Used to
route synaptic input to specific receptor channels. Default: ``0``.
U : float or array_like, optional
Baseline release probability per site. Must be in ``[0, 1]``. Controls
the equilibrium facilitation level. Default: ``0.5``.
u : float or array_like, optional
Initial release probability per site. Must be in ``[0, 1]``. If not
specified, defaults to ``U``. Default: ``U``.
n : int or array_like, optional
Total number of release sites (vesicle docking sites). Must be a
non-negative integer. Default: ``1``.
a : int or array_like, optional
Initial number of available (recovered) release sites. Must be a
non-negative integer not exceeding ``n``. If not specified, defaults
to ``n`` (all sites initially available). Default: ``n``.
tau_rec : float or array_like, optional
Recovery time constant for depleted sites. Must be positive and include
time units. Governs the rate of site replenishment. Default:
``800.0 * u.ms``.
tau_fac : float or array_like, optional
Facilitation time constant. Must be non-negative and include time units.
If zero or very small (:math:`< 10^{-10}` ms), facilitation is disabled
and ``u`` is reset to ``U`` on each spike. Default: ``0.0 * u.ms``
(no facilitation).
post : object, optional
Default postsynaptic receiver object. Can be overridden in ``send()``
and ``update()`` calls.
name : str, optional
Unique identifier for this synapse instance.
Parameter Mapping
-----------------
Direct correspondence with NEST ``quantal_stp_synapse``:
+-------------------+--------------------+-----------------------------------+
| brainpy.state | NEST | Description |
+===================+====================+===================================+
| ``weight`` | ``weight`` | Per-site baseline weight |
+-------------------+--------------------+-----------------------------------+
| ``delay`` | ``delay`` | Transmission delay (ms) |
+-------------------+--------------------+-----------------------------------+
| ``receptor_type`` | ``receptor_type`` | Receptor port identifier |
+-------------------+--------------------+-----------------------------------+
| ``U`` | ``U`` | Baseline release probability |
+-------------------+--------------------+-----------------------------------+
| ``u`` | ``u`` | Current release probability |
+-------------------+--------------------+-----------------------------------+
| ``n`` | ``n`` | Total release sites |
+-------------------+--------------------+-----------------------------------+
| ``a`` | ``a`` | Available release sites |
+-------------------+--------------------+-----------------------------------+
| ``tau_rec`` | ``tau_rec`` | Recovery time constant (ms) |
+-------------------+--------------------+-----------------------------------+
| ``tau_fac`` | ``tau_fac`` | Facilitation time constant (ms) |
+-------------------+--------------------+-----------------------------------+
Notes
-----
**Random Number Generation**
All probabilistic operations use the NumPy global random number generator
(``np.random.random()``). For reproducible simulations, seed the generator
with ``np.random.seed()`` before initialization.
**State Initialization**
Calling ``init_state()`` resets the mutable state variables (``u``, ``a``,
``t_lastspike``) to their configured initial values. The reset values are
stored internally as ``_u0`` and ``_a0`` when parameters are set.
**Computational Cost**
The per-spike cost scales as :math:`O(n)` due to the independent Bernoulli
trials for each site. For large ``n`` (e.g., :math:`n > 100`), consider
using the continuous Tsodyks-Markram model (``tsodyks_synapse``) as an
efficient approximation.
**Biophysical Interpretation**
- ``n``: number of active zones or docking sites
- ``u``: vesicle release probability (calcium-dependent)
- ``U``: baseline release probability (intrinsic to synapse type)
- ``tau_rec``: vesicle recycling / replenishment time
- ``tau_fac``: residual calcium decay time (controls facilitation)
**Comparison with tsodyks_synapse**
``tsodyks_synapse`` uses continuous-valued resource variables and
deterministic updates. ``quantal_stp_synapse`` uses discrete site counts
and stochastic release, producing trial-to-trial variability consistent
with experimental observations of synaptic unreliability.
See Also
--------
tsodyks_synapse : Deterministic continuous Tsodyks-Markram model
tsodyks2_synapse : Alternative Tsodyks-Markram formulation
static_synapse : Static synaptic connection without plasticity
References
----------
.. [1] NEST source: ``models/quantal_stp_synapse.h`` and
``models/quantal_stp_synapse_impl.h``.
.. [2] Fuhrmann G, Segev I, Markram H, Tsodyks MV (2002).
Coding of temporal information by activity-dependent synapses.
*Journal of Neurophysiology*, 87(1):140-148.
DOI: `10.1152/jn.00258.2001 <https://doi.org/10.1152/jn.00258.2001>`_
.. [3] Loebel A, Silberberg G, Helbig D, Markram H, Tsodyks MV,
Richardson MJE (2009). Multiquantal release underlies the
distribution of synaptic efficacies in the neocortex.
*Frontiers in Computational Neuroscience*, 3:27.
DOI: `10.3389/neuro.10.027.2009 <https://doi.org/10.3389/neuro.10.027.2009>`_
.. [4] Maass W, Markram H (2002). Synapses as dynamic memory buffers.
*Neural Networks*, 15(2):155-161.
DOI: `10.1016/S0893-6080(01)00144-7 <https://doi.org/10.1016/S0893-6080(01)00144-7>`_
Examples
--------
**Basic usage with default parameters**
.. code-block:: python
>>> import brainpy.state as bp
>>> import saiunit as u
>>> # Single release site, 50% release probability
>>> syn = bp.quantal_stp_synapse(weight=1.0, U=0.5, n=1)
>>> syn.init_state()
**Multi-site synapse with facilitation**
.. code-block:: python
>>> # 10 release sites, facilitating synapse
>>> syn = bp.quantal_stp_synapse(
... weight=0.1,
... U=0.3,
... n=10,
... tau_rec=800.0 * u.ms,
... tau_fac=50.0 * u.ms
... )
>>> syn.init_state()
**Depressing synapse (no facilitation)**
.. code-block:: python
>>> # High initial release probability, slow recovery
>>> syn = bp.quantal_stp_synapse(
... weight=0.5,
... U=0.8,
... n=5,
... tau_rec=1000.0 * u.ms,
... tau_fac=0.0 * u.ms # No facilitation
... )
>>> syn.init_state()
**Simulating paired-pulse response**
.. code-block:: python
>>> import numpy as np
>>> np.random.seed(42) # For reproducibility
>>> syn = bp.quantal_stp_synapse(
... weight=1.0, U=0.5, n=10, tau_rec=500.0 * u.ms,
... tau_fac=50.0 * u.ms
... )
>>> syn.init_state()
>>> # First pulse
>>> response1 = syn.send(multiplicity=1.0)
>>> # Second pulse 50 ms later (simulate time advance)
>>> syn.t_lastspike = 0.0
>>> # ... (advance simulation time by 50 ms)
>>> response2 = syn.send(multiplicity=1.0)
**Runtime parameter modification**
.. code-block:: python
>>> syn.set(U=0.7, tau_rec=300.0 * u.ms)
>>> status = syn.get()
>>> print(status['U'], status['tau_rec'])
0.7 300.0
"""
__module__ = 'brainpy.state'
def __init__(
self,
weight: ArrayLike = 1.0,
delay: ArrayLike = 1.0 * u.ms,
receptor_type: int = 0,
U: ArrayLike = 0.5,
u: ArrayLike | object = _UNSET,
n: ArrayLike = 1,
a: ArrayLike | object = _UNSET,
tau_rec: ArrayLike = 800.0 * u.ms,
tau_fac: ArrayLike = 0.0 * u.ms,
post=None,
name: str | None = None,
):
super().__init__(
weight=weight,
delay=delay,
receptor_type=receptor_type,
post=post,
event_type='spike',
name=name,
)
self.U = self._to_scalar_unit_interval(U, name='U')
self.u = self.U if u is _UNSET else self._to_scalar_unit_interval(u, name='u')
self.n = self._to_scalar_int(n, name='n')
self.a = self.n if a is _UNSET else self._to_scalar_int(a, name='a')
self.tau_rec = self._to_scalar_time_ms(tau_rec, name='tau_rec')
self.tau_fac = self._to_scalar_time_ms(tau_fac, name='tau_fac')
self._validate_tau_rec(float(self.tau_rec))
self._validate_tau_fac(float(self.tau_fac))
self._u0 = float(self.u)
self._a0 = int(self.a)
self.t_lastspike = -1.0
@staticmethod
def _to_scalar_float(value: ArrayLike, *, name: str) -> float:
dftype = brainstate.environ.dftype()
arr = np.asarray(u.math.asarray(value, dtype=dftype), dtype=dftype)
if arr.size != 1:
raise ValueError(f'{name} must be scalar.')
return float(arr.reshape(()))
@staticmethod
def _to_scalar_unit_interval(value: ArrayLike, *, name: str) -> float:
v = quantal_stp_synapse._to_scalar_float(value, name=name)
if v < 0.0 or v > 1.0:
raise ValueError(f"'{name}' must be in [0,1].")
return float(v)
@staticmethod
def _to_scalar_int(value: ArrayLike, *, name: str) -> int:
v = quantal_stp_synapse._to_scalar_float(value, name=name)
if not float(v).is_integer():
raise ValueError(f"'{name}' must be an integer.")
return int(v)
@staticmethod
def _validate_tau_rec(value: float):
if value <= 0.0:
raise ValueError("'tau_rec' must be > 0.")
@staticmethod
def _validate_tau_fac(value: float):
if value < 0.0:
raise ValueError("'tau_fac' must be >= 0.")
@staticmethod
def _sample_uniform() -> float:
return float(np.random.random())
[docs]
def init_state(self, batch_size: int = None, **kwargs):
r"""Initialize or reset synapse state to configured initial values.
Resets the three mutable state variables to their initial configuration:
- ``u``: reset to ``_u0`` (initial release probability)
- ``a``: reset to ``_a0`` (initial available sites)
- ``t_lastspike``: reset to ``-1.0`` (no prior spike)
This method should be called before simulation or when resetting
between trials.
Parameters
----------
batch_size : int, optional
Ignored (retained for API compatibility with batch-aware models).
**kwargs
Additional keyword arguments (ignored).
Notes
-----
The initial values ``_u0`` and ``_a0`` are cached internally when
parameters are set via ``__init__()`` or ``set()``. Modifying ``u``
or ``a`` directly during simulation does not affect these cached
values.
"""
del batch_size, kwargs
super().init_state()
self.u = float(self._u0)
self.a = int(self._a0)
self.t_lastspike = -1.0
[docs]
def get(self) -> dict:
r"""Return current public parameters and mutable state.
Retrieves all NEST-compatible parameters and the current values of
mutable state variables. This method is useful for inspecting synapse
configuration and monitoring state evolution during simulation.
Returns
-------
dict
Dictionary containing:
- Inherited static synapse parameters (``weight``, ``delay``,
``receptor_type``)
- ``U`` : float
Baseline release probability
- ``u`` : float
Current release probability
- ``tau_rec`` : float
Recovery time constant (ms, unitless value)
- ``tau_fac`` : float
Facilitation time constant (ms, unitless value)
- ``n`` : int
Total number of release sites
- ``a`` : int
Current number of available sites
- ``synapse_model`` : str
Fixed string ``'quantal_stp_synapse'`` for NEST compatibility
Notes
-----
The returned dictionary is a snapshot of the current state. Modifying
the dictionary does not affect the synapse. Use ``set()`` to update
parameters.
Examples
--------
.. code-block:: python
>>> syn = bp.quantal_stp_synapse(U=0.5, n=10)
>>> syn.init_state()
>>> status = syn.get()
>>> print(status['u'], status['a'])
0.5 10
"""
params = super().get()
params['U'] = float(self.U)
params['u'] = float(self.u)
params['tau_rec'] = float(self.tau_rec)
params['tau_fac'] = float(self.tau_fac)
params['n'] = int(self.n)
params['a'] = int(self.a)
params['synapse_model'] = 'quantal_stp_synapse'
return params
[docs]
def set(
self,
*,
weight: ArrayLike | object = _UNSET,
delay: ArrayLike | object = _UNSET,
receptor_type: ArrayLike | object = _UNSET,
U: ArrayLike | object = _UNSET,
u: ArrayLike | object = _UNSET,
n: ArrayLike | object = _UNSET,
a: ArrayLike | object = _UNSET,
tau_rec: ArrayLike | object = _UNSET,
tau_fac: ArrayLike | object = _UNSET,
post: object = _UNSET,
):
r"""Set NEST-style public parameters and state variables.
Updates synapse parameters and optionally resets mutable state. All
parameters are validated before being applied. If validation fails,
the synapse state remains unchanged.
Parameters
----------
weight : float or array_like, optional
New baseline per-site weight. If not provided, current value is
retained.
delay : float or array_like, optional
New synaptic delay (must include time units). If not provided,
current value is retained.
receptor_type : int, optional
New receptor port identifier. If not provided, current value is
retained.
U : float or array_like, optional
New baseline release probability. Must be in ``[0, 1]``. If not
provided, current value is retained.
u : float or array_like, optional
New current release probability. Must be in ``[0, 1]``. If not
provided, current value is retained. Also updates ``_u0`` (the
reset value used by ``init_state()``).
n : int or array_like, optional
New total number of release sites. Must be a non-negative integer.
If not provided, current value is retained.
a : int or array_like, optional
New number of available sites. Must be a non-negative integer not
exceeding ``n``. If not provided, current value is retained. Also
updates ``_a0`` (the reset value used by ``init_state()``).
tau_rec : float or array_like, optional
New recovery time constant (must include time units and be
positive). If not provided, current value is retained.
tau_fac : float or array_like, optional
New facilitation time constant (must include time units and be
non-negative). If not provided, current value is retained.
post : object, optional
New default postsynaptic receiver. If not provided, current value
is retained.
Raises
------
ValueError
If any parameter fails validation:
- ``U`` or ``u`` not in ``[0, 1]``
- ``n`` or ``a`` not an integer
- ``tau_rec <= 0``
- ``tau_fac < 0``
Notes
-----
**State Reset Values**
Setting ``u`` or ``a`` updates both the current state and the cached
initial values (``_u0``, ``_a0``). This means subsequent calls to
``init_state()`` will use the new values as the reset target.
**Atomic Update**
All validations are performed before any state is modified. If any
parameter is invalid, the entire operation is aborted without
side effects.
Examples
--------
.. code-block:: python
>>> syn = bp.quantal_stp_synapse(U=0.5, n=10)
>>> syn.set(U=0.7, tau_rec=300.0 * u.ms)
>>> syn.set(a=5) # Partially deplete available sites
>>> syn.init_state() # Now resets to a=5 (new initial value)
"""
new_U = self.U if U is _UNSET else self._to_scalar_unit_interval(U, name='U')
new_u = self.u if u is _UNSET else self._to_scalar_unit_interval(u, name='u')
new_n = self.n if n is _UNSET else self._to_scalar_int(n, name='n')
new_a = self.a if a is _UNSET else self._to_scalar_int(a, name='a')
new_tau_rec = (
self.tau_rec
if tau_rec is _UNSET
else self._to_scalar_time_ms(tau_rec, name='tau_rec')
)
new_tau_fac = (
self.tau_fac
if tau_fac is _UNSET
else self._to_scalar_time_ms(tau_fac, name='tau_fac')
)
self._validate_tau_rec(float(new_tau_rec))
self._validate_tau_fac(float(new_tau_fac))
super_kwargs = {}
if weight is not _UNSET:
super_kwargs['weight'] = weight
if delay is not _UNSET:
super_kwargs['delay'] = delay
if receptor_type is not _UNSET:
super_kwargs['receptor_type'] = receptor_type
if post is not _UNSET:
super_kwargs['post'] = post
if super_kwargs:
super().set(**super_kwargs)
self.U = float(new_U)
self.u = float(new_u)
self.n = int(new_n)
self.a = int(new_a)
self.tau_rec = float(new_tau_rec)
self.tau_fac = float(new_tau_fac)
self._u0 = float(self.u)
self._a0 = int(self.a)
[docs]
def send(
self,
multiplicity: ArrayLike = 1.0,
*,
post=None,
receptor_type: ArrayLike | None = None,
) -> bool:
r"""Schedule one outgoing synaptic event with quantal STP dynamics.
Processes a presynaptic spike by updating facilitation state, recovering
depleted sites, drawing stochastic vesicle release, and scheduling
transmission to the postsynaptic target. This method implements the
NEST ``quantal_stp_synapse::send`` algorithm exactly.
**Processing Steps**
1. **Facilitation Update**: If a previous spike exists
(``t_lastspike >= 0``), compute inter-spike interval and update
release probability ``u`` according to decay and facilitation rules.
2. **Site Recovery**: For each depleted site, perform a Bernoulli
trial with recovery probability :math:`1 - e^{-h/\tau_{\mathrm{rec}}}`.
3. **Vesicle Release**: For each available site, perform a Bernoulli
trial with release probability ``u``. Count total released sites
as ``n_release``.
4. **Event Transmission**: If ``n_release > 0``, schedule a delayed
event with effective weight :math:`w_{\mathrm{eff}} =
n_{\mathrm{release}} \cdot w \cdot \text{multiplicity}` and update
available sites: ``a <- a - n_release``.
5. **Timestamp Update**: Store current spike time in ``t_lastspike``,
regardless of whether transmission occurred.
Parameters
----------
multiplicity : float or array_like, optional
Presynaptic spike count or scaling factor. The effective transmitted
weight is :math:`w_{\mathrm{eff}} = n_{\mathrm{release}} \cdot w
\cdot \text{multiplicity}`. If zero or negligible, processing is
skipped. Default: ``1.0``.
post : object, optional
Target postsynaptic receiver. If ``None``, uses the default
receiver set during initialization.
receptor_type : int or array_like, optional
Target receptor port. If ``None``, uses the default receptor type
set during initialization.
Returns
-------
bool
``True`` if at least one vesicle was released and an event was
transmitted; ``False`` if synaptic failure occurred (no release)
or if ``multiplicity`` was zero.
Notes
-----
**Synaptic Failure**
Due to stochastic release, transmission may fail even with positive
``multiplicity``. The failure probability is :math:`(1 - u)^a`. For
small ``u`` or ``a``, failures are common.
**Timestamp Semantics**
The spike timestamp is computed as ``current_time + dt``, representing
the end of the current simulation step. This matches NEST's on-grid
spike timing convention.
**Random Number Generation**
Each call may invoke the RNG up to :math:`n` times (worst case: all
sites depleted, all recover, all release). For reproducibility, seed
``np.random`` before simulation.
Examples
--------
.. code-block:: python
>>> import numpy as np
>>> np.random.seed(42)
>>> syn = bp.quantal_stp_synapse(weight=1.0, U=0.5, n=10)
>>> syn.init_state()
>>> transmitted = syn.send(multiplicity=1.0)
>>> if transmitted:
... print(f"Released vesicles, available sites now: {syn.a}")
... else:
... print("Synaptic failure")
"""
if not self._is_nonzero(multiplicity):
return False
dt_ms = self._refresh_delay_if_needed()
current_step = self._curr_step(dt_ms)
# NEST evaluates this model on spike stamps.
t_spike = self._current_time_ms() + dt_ms
if self.t_lastspike >= 0.0:
h = float(t_spike - self.t_lastspike)
p_decay = math.exp(-h / self.tau_rec)
u_decay = 0.0 if self.tau_fac < 1.0e-10 else math.exp(-h / self.tau_fac)
# Keep ordering identical to NEST models/quantal_stp_synapse.h::send.
self.u = self.U + self.u * (1.0 - self.U) * u_decay
recovery_prob = 1.0 - p_decay
depleted_sites = int(self.n - self.a)
for _ in range(depleted_sites if depleted_sites > 0 else 0):
if self._sample_uniform() < recovery_prob:
self.a += 1
n_release = 0
available_sites = int(self.a)
for _ in range(available_sites if available_sites > 0 else 0):
if self._sample_uniform() < self.u:
n_release += 1
send_spike = n_release > 0
if send_spike:
receiver = self._resolve_receiver(post)
weighted_payload = multiplicity * (float(n_release) * self.weight)
rport = self.receptor_type if receptor_type is None else self._to_receptor_type(receptor_type)
delivery_step = int(current_step + int(self._delay_steps))
self._queue[delivery_step].append((receiver, weighted_payload, int(rport), 'spike'))
self.a -= int(n_release)
self.t_lastspike = float(t_spike)
return send_spike
[docs]
def update(
self,
pre_spike: ArrayLike = 0.0,
*,
post=None,
receptor_type: ArrayLike | None = None,
) -> int:
r"""Deliver due events and process current presynaptic input.
Performs two operations in sequence:
1. **Event Delivery**: Delivers all queued synaptic events scheduled
for the current simulation step to their target postsynaptic
receivers.
2. **Input Processing**: Aggregates all current-step presynaptic input
(from ``pre_spike`` argument and registered input sources via
``current_inputs`` and ``delta_inputs``), then calls ``send()`` if
the total input is non-zero.
This method is typically called once per simulation step as part of
the projection's update loop.
Parameters
----------
pre_spike : float or array_like, optional
Presynaptic spike input for the current step (dimensionless or
with units). This value is summed with inputs from registered
sources before processing. Default: ``0.0``.
post : object, optional
Target postsynaptic receiver for the outgoing event (if any). If
``None``, uses the default receiver.
receptor_type : int or array_like, optional
Target receptor port for the outgoing event (if any). If ``None``,
uses the default receptor type.
Returns
-------
int
Number of synaptic events delivered to postsynaptic targets during
this step. This count reflects events scheduled in prior steps,
not the current ``send()`` call.
Notes
-----
**Update Order**
Event delivery precedes input processing. This means events generated
in the current step are delivered ``delay`` steps later, consistent
with NEST's event-driven semantics.
**Input Aggregation**
The method sums three input sources:
- Direct ``pre_spike`` argument
- Registered ``current_inputs`` (via ``add_current_input()``)
- Registered ``delta_inputs`` (via ``add_delta_input()``)
If the total is zero (within numerical tolerance), ``send()`` is
skipped.
Examples
--------
.. code-block:: python
>>> syn = bp.quantal_stp_synapse(weight=1.0, U=0.5, n=10)
>>> syn.init_state()
>>> # Process spike input
>>> delivered = syn.update(pre_spike=1.0)
>>> # Check how many events were delivered this step
>>> print(f"Delivered {delivered} events")
"""
dt_ms = self._refresh_delay_if_needed()
step = self._curr_step(dt_ms)
delivered = self._deliver_due_events(step)
total = self.sum_current_inputs(pre_spike)
total = self.sum_delta_inputs(total)
if self._is_nonzero(total):
self.send(total, post=post, receptor_type=receptor_type)
return delivered