Source code for brainpy_state._nest.tsodyks2_synapse

# 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__ = [
    'tsodyks2_synapse',
]

_UNSET = object()


class tsodyks2_synapse(static_synapse):
    r"""NEST-compatible ``tsodyks2_synapse`` connection model.

    Short description
    -----------------

    Synapse type with short-term depression and facilitation.

    Description
    -----------

    ``tsodyks2_synapse`` mirrors NEST ``models/tsodyks2_synapse.h``.
    The model stores per-connection state variables:

    - ``x``: current scaling factor of synaptic efficacy,
    - ``u``: current release probability,
    - ``t_lastspike``: last presynaptic spike stamp.

    Together with fixed model parameters ``U``, ``tau_rec`` and ``tau_fac``,
    incoming spikes are processed in the same order as NEST:

    **1. State propagation**

    For ``h = t_spike - t_lastspike``, if this is not the first spike
    (i.e. ``t_lastspike >= 0``), propagate state to the current spike:

    .. math::
       x \leftarrow 1 + (x - xu - 1)e^{-h/\tau_{rec}}

       u \leftarrow U + u(1-U)e^{-h/\tau_{fac}}

    with the NEST special case ``tau_fac == 0``:

    .. math::
       e^{-h/\tau_{fac}} \equiv 0

    **2. Effective weight computation**

    Compute effective synaptic weight for this spike:

    .. math::
       w_{eff} = x u w

    **3. Event scheduling**

    Schedule event delivery with inherited static-synapse delay semantics.

    **4. State update**

    Set ``t_lastspike = t_spike``.

    This model scales the baseline synaptic weight only and is suitable for
    current- or conductance-based postsynaptic dynamics.

    Event timing semantics
    ----------------------

    As in NEST, updates use spike stamps and ignore precise sub-step offsets.
    In this backend, each presynaptic event at simulation step ``t`` is
    evaluated at on-grid stamp ``t + dt``.

    Mathematical formulation
    ------------------------

    The tsodyks2_synapse model implements a simplified version of the
    Tsodyks-Markram short-term plasticity mechanism that tracks only two
    key dynamic variables rather than the full resource state model.

    State variables
    ---------------

    - :math:`x(t)` -- scaling factor representing available resources
    - :math:`u(t)` -- utilization (release probability)
    - :math:`t_{\mathrm{last}}` -- timestamp of last presynaptic spike

    **Parameters:**

    - :math:`U` -- baseline utilization increment (facilitation magnitude)
    - :math:`\tau_{rec}` -- recovery time constant (depression timescale)
    - :math:`\tau_{fac}` -- facilitation time constant
    - :math:`w` -- baseline synaptic weight

    **Dynamics:**

    Upon arrival of a presynaptic spike at time :math:`t_s`, with inter-spike
    interval :math:`h = t_s - t_{\mathrm{last}}`:

    1. If :math:`t_{\mathrm{last}} \geq 0` (not the first spike), propagate
       state variables:

       .. math::
          x(t_s) = 1 + [x(t_{\mathrm{last}}) - x(t_{\mathrm{last}})u(t_{\mathrm{last}}) - 1]
                   \exp\left(-\frac{h}{\tau_{rec}}\right)

          u(t_s) = U + u(t_{\mathrm{last}})(1-U)\exp\left(-\frac{h}{\tau_{fac}}\right)

       with the special case when :math:`\tau_{fac} = 0`:

       .. math::
          \exp\left(-\frac{h}{\tau_{fac}}\right) \equiv 0

    2. Compute effective synaptic strength:

       .. math::
          w_{\mathrm{eff}} = x(t_s) \cdot u(t_s) \cdot w

    3. Update last spike time:

       .. math::
          t_{\mathrm{last}} \leftarrow t_s

    **Biological interpretation:**

    - **Depression** (:math:`x` dynamics): After each spike, :math:`x`
      decreases by factor :math:`xu`, representing depletion of available
      resources. It recovers exponentially with time constant :math:`\tau_{rec}`.

    - **Facilitation** (:math:`u` dynamics): The utilization :math:`u`
      increases toward :math:`U` after each spike, representing
      calcium-dependent facilitation of release probability. It decays
      exponentially with time constant :math:`\tau_{fac}`.

    - **Combined effect**: The effective synaptic weight :math:`w_{\mathrm{eff}}`
      is the product of available resources (:math:`x`), release probability
      (:math:`u`), and baseline weight (:math:`w`).

    **Parameter regimes:**

    - **Depression-dominated**: :math:`\tau_{rec} \gg \tau_{fac}`, :math:`U`
      moderate. Resources deplete faster than facilitation builds up.

    - **Facilitation-dominated**: :math:`\tau_{fac} \gg \tau_{rec}`, :math:`U`
      small. Facilitation accumulates across multiple spikes.

    - **Pure depression**: :math:`\tau_{fac} = 0`, fixed :math:`u = U`.
      No facilitation dynamics.

    Computational considerations
    ----------------------------

    - **Event-driven updates**: State propagation occurs only at spike times,
      making the model computationally efficient for sparse spike trains.

    - **Exponential approximation**: For very small :math:`h`, numerical
      precision may be limited. NEST uses the convention that
      :math:`\tau_{fac} = 0` exactly eliminates facilitation rather than
      using a threshold.

    - **First spike handling**: The condition :math:`t_{\mathrm{last}} < 0`
      distinguishes the first spike, which uses initial :math:`x` and :math:`u`
      values directly without propagation.

    - **Multiplicative weight scaling**: Unlike ``tsodyks_synapse``, this model
      does not track postsynaptic current dynamics separately. The effective
      weight :math:`w_{\mathrm{eff}}` directly modulates the delivered event.

    Comparison with tsodyks_synapse
    --------------------------------

    The tsodyks2_synapse model differs from tsodyks_synapse in several ways:

    **State representation:**

    - tsodyks2: Two variables (:math:`x`, :math:`u`)
    - tsodyks: Three variables (:math:`x`, :math:`y`, :math:`u`) plus
      postsynaptic current :math:`\tau_{psc}`

    **Update equations:**

    - tsodyks2: Simplified model where :math:`x` represents effective resources
      after accounting for utilization
    - tsodyks: Full resource model with explicit recovered (:math:`x`), active
      (:math:`y`), and inactive (:math:`z = 1-x-y`) resource fractions

    **Postsynaptic dynamics:**

    - tsodyks2: No built-in postsynaptic filtering; :math:`w_{\mathrm{eff}}`
      directly scales the event
    - tsodyks: Includes :math:`\tau_{psc}` for postsynaptic current dynamics

    **Use cases:**

    - tsodyks2: Simpler, faster, suitable when postsynaptic filtering is
      handled separately (e.g., by neuron model)
    - tsodyks: More detailed, includes postsynaptic current kinetics in the
      synapse model

    Parameters
    ----------
    weight : float or array-like, optional
        Baseline synaptic weight :math:`w`. Dimensionless scalar or array.
        Default: ``1.0``.
    delay : float or Quantity, optional
        Synaptic transmission delay. Must be a positive time value in
        milliseconds. Accepts ``saiunit.Quantity`` with time units or
        float (interpreted as ms). Default: ``1.0 * u.ms``.
    receptor_type : int, optional
        Receiver port/receptor identifier for multi-receptor postsynaptic
        neurons. Non-negative integer. Default: ``0``.
    U : float or array-like, optional
        Utilization increment parameter :math:`U`. Must be in ``[0, 1]``.
        Controls the magnitude of facilitation per spike. Dimensionless scalar.
        Default: ``0.5``.
    u : float or array-like or UNSET, optional
        Initial release probability :math:`u(0)`. Must be in ``[0, 1]``.
        If not provided, defaults to ``U``. Dimensionless scalar.
        Default: ``U``.
    x : float or array-like, optional
        Initial scaling factor of synaptic efficacy :math:`x(0)`. Represents
        initial available resources. Dimensionless scalar. Typical range
        ``[0, 1]``, though values ``> 1`` are allowed. Default: ``1.0``.
    tau_rec : float or Quantity, optional
        Recovery (depression) time constant :math:`\tau_{rec}`. Must be
        strictly positive (``> 0``). Controls the timescale of resource
        replenishment. Accepts ``saiunit.Quantity`` with time units or
        float (interpreted as ms). Default: ``800.0 * u.ms``.
    tau_fac : float or Quantity, optional
        Facilitation time constant :math:`\tau_{fac}`. Must be non-negative
        (``>= 0``). When zero, facilitation is disabled and :math:`u` remains
        constant at :math:`U`. Accepts ``saiunit.Quantity`` with time units
        or float (interpreted as ms). Default: ``0.0 * u.ms``.
    post : object, optional
        Default postsynaptic receiver object. If provided, this receiver will
        be used by default in :meth:`send` and :meth:`update` calls when no
        explicit receiver is specified. Default: ``None``.
    name : str, optional
        Optional name identifier for this connection object. Used for debugging
        and introspection. Default: ``None``.

    Parameter Mapping
    -----------------

    The following table maps NEST parameter names to brainpy.state
    equivalents and describes their interpretation:

    +-----------------+------------------+-------------+---------------------+
    | NEST Parameter  | brainpy.state    | Type        | Description         |
    +=================+==================+=============+=====================+
    | ``weight``      | ``weight``       | float       | Baseline synaptic   |
    |                 |                  |             | weight :math:`w`    |
    +-----------------+------------------+-------------+---------------------+
    | ``delay``       | ``delay``        | Quantity    | Transmission delay  |
    |                 |                  | (ms)        | in milliseconds     |
    +-----------------+------------------+-------------+---------------------+
    | ``receptor_     | ``receptor_      | int         | Target receptor     |
    | type``          | type``           |             | port identifier     |
    +-----------------+------------------+-------------+---------------------+
    | ``U``           | ``U``            | float       | Utilization         |
    |                 |                  | [0,1]       | increment           |
    +-----------------+------------------+-------------+---------------------+
    | ``u``           | ``u``            | float       | Initial/current     |
    |                 |                  | [0,1]       | release probability |
    +-----------------+------------------+-------------+---------------------+
    | ``x``           | ``x``            | float       | Initial/current     |
    |                 |                  |             | efficacy scaling    |
    +-----------------+------------------+-------------+---------------------+
    | ``tau_rec``     | ``tau_rec``      | Quantity    | Recovery time       |
    |                 |                  | (ms)        | constant            |
    +-----------------+------------------+-------------+---------------------+
    | ``tau_fac``     | ``tau_fac``      | Quantity    | Facilitation time   |
    |                 |                  | (ms)        | constant            |
    +-----------------+------------------+-------------+---------------------+

    Notes
    -----
    - This model transmits spike-like events only. The ``event_type`` is
      fixed to ``'spike'``.

    - The state variables ``x``, ``u``, and ``t_lastspike`` are mutable
      connection states. Current values can be retrieved via :meth:`get`
      and modified via :meth:`set`.

    - When ``tau_fac == 0`` exactly, facilitation is disabled and
      :math:`u` remains constant. This matches NEST's behavior and avoids
      numerical issues with :math:`\exp(-h/0)`.

    - Calling :meth:`init_state` resets the event queue and restores
      ``x``, ``u``, and ``t_lastspike`` to their configured initial values.

    - The model uses event-driven updates: state propagation occurs only
      at spike times, not at every simulation timestep.

    Raises
    ------
    ValueError
        If ``U`` or ``u`` is not in the range ``[0, 1]``.
    ValueError
        If ``tau_rec`` is not strictly positive (``<= 0``).
    ValueError
        If ``tau_fac`` is negative (``< 0``).
    ValueError
        If any scalar parameter has size ``!= 1`` when converted to array.

    See Also
    --------
    tsodyks_synapse : Full resource-based STP model with postsynaptic current.
    static_synapse : Base class providing delay and weight handling.
    tsodyks_synapse_hom : Homogeneous-weight variant of tsodyks_synapse.

    References
    ----------
    .. [1] NEST source: ``models/tsodyks2_synapse.h`` and
           ``models/tsodyks2_synapse.cpp``.
    .. [2] Tsodyks MV, Markram H (1997). The neural code between neocortical
           pyramidal neurons depends on neurotransmitter release probability.
           PNAS, 94(2):719-723. DOI: 10.1073/pnas.94.2.719
    .. [3] 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
    .. [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

    Examples
    --------
    Create a depression-dominated synapse (fast recovery, no facilitation):

    .. code-block:: python

       >>> import brainpy.state as bst
       >>> import saiunit as u
       >>> syn = bst.tsodyks2_synapse(
       ...     weight=1.0,
       ...     delay=1.5 * u.ms,
       ...     U=0.5,
       ...     tau_rec=100.0 * u.ms,
       ...     tau_fac=0.0 * u.ms
       ... )
       >>> syn.init_state()

    Create a facilitation-dominated synapse (slow recovery, long facilitation):

    .. code-block:: python

       >>> syn_fac = bst.tsodyks2_synapse(
       ...     weight=0.5,
       ...     U=0.15,
       ...     tau_rec=800.0 * u.ms,
       ...     tau_fac=1000.0 * u.ms
       ... )

    Inspect current synapse state:

    .. code-block:: python

       >>> state = syn.get()
       >>> print(f"x={state['x']}, u={state['u']}")
       x=1.0, u=0.5

    Simulate a spike train and observe depression:

    .. code-block:: python

       >>> import brainstate as bst
       >>> import jax.numpy as jnp
       >>> # Create postsynaptic target
       >>> class SimpleTarget(bst.nn.Dynamics):
       ...     def __init__(self):
       ...         super().__init__()
       ...         self.input = 0.0
       ...     def update(self, inp=0.0):
       ...         self.input = inp
       >>> target = SimpleTarget()
       >>> syn = bst.tsodyks2_synapse(
       ...     weight=1.0,
       ...     delay=0.1 * u.ms,
       ...     U=0.5,
       ...     tau_rec=200.0 * u.ms,
       ...     tau_fac=0.0 * u.ms,
       ...     post=target
       ... )
       >>> syn.init_state()
       >>> # Simulate high-frequency spike train
       >>> with bst.environ.context(dt=0.1 * u.ms):
       ...     for i in range(10):
       ...         delivered = syn.update(pre_spike=1.0)
       ...         state = syn.get()
       ...         print(f"Step {i}: x={state['x']:.3f}, w_eff={state['x']*state['u']:.3f}")
    """

    __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,
        x: ArrayLike = 1.0,
        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')
        u0 = self.U if u is _UNSET else self._to_scalar_unit_interval(u, name='u')

        self.x = self._to_scalar_float(x, name='x')
        self.u = float(u0)
        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(self.tau_rec)
        self._validate_tau_fac(self.tau_fac)

        self._x0 = float(self.x)
        self._u0 = float(self.u)

        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 = tsodyks2_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 _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.")

[docs] def init_state(self, batch_size: int = None, **kwargs): r"""Initialize or reset synapse state variables. Resets the event delivery queue and restores dynamic state variables (``x``, ``u``, ``t_lastspike``) to their configured initial values. This method should be called before starting a simulation or when reinitializing the model. Parameters ---------- batch_size : int, optional Ignored. Included for API compatibility with batched models. **kwargs : dict, optional Additional keyword arguments. Ignored. Notes ----- - The initial values of ``x`` and ``u`` are determined by the values provided during object construction (or last :meth:`set` call). - ``t_lastspike`` is always reset to ``-1.0`` to indicate no prior spike has occurred. - The inherited event queue from :class:`static_synapse` is also cleared. """ del batch_size, kwargs super().init_state() self.x = float(self._x0) self.u = float(self._u0) self.t_lastspike = -1.0
[docs] def get(self) -> dict: r"""Return current public parameters and mutable state. Retrieves a dictionary containing all NEST-compatible parameters and current dynamic state variables. This is useful for introspection, checkpointing, and parameter queries. Returns ------- dict Dictionary with the following keys: - ``'weight'`` : float Baseline synaptic weight. - ``'delay'`` : float Synaptic delay in milliseconds. - ``'receptor_type'`` : int Target receptor port identifier. - ``'U'`` : float Utilization increment parameter (fixed). - ``'u'`` : float Current release probability (mutable state). - ``'x'`` : float Current efficacy scaling factor (mutable state). - ``'tau_rec'`` : float Recovery time constant in milliseconds. - ``'tau_fac'`` : float Facilitation time constant in milliseconds. - ``'synapse_model'`` : str Model identifier string ``'tsodyks2_synapse'``. Notes ----- - The returned ``u`` and ``x`` values reflect the current synapse state, which changes after each spike. - All time constants are returned as float values in milliseconds, with units stripped. - The ``synapse_model`` key is included for NEST compatibility and model identification. Examples -------- .. code-block:: python >>> import brainpy.state as bst >>> import saiunit as u >>> syn = bst.tsodyks2_synapse(weight=2.0, U=0.4, tau_rec=500*u.ms) >>> syn.init_state() >>> params = syn.get() >>> print(params['U'], params['x']) 0.4 1.0 """ params = super().get() params['U'] = float(self.U) params['u'] = float(self.u) params['x'] = float(self.x) params['tau_rec'] = float(self.tau_rec) params['tau_fac'] = float(self.tau_fac) params['synapse_model'] = 'tsodyks2_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, x: ArrayLike | object = _UNSET, tau_rec: ArrayLike | object = _UNSET, tau_fac: ArrayLike | object = _UNSET, post: object = _UNSET, ): r"""Set NEST-style public parameters and mutable state. Updates one or more synapse parameters or state variables. All arguments are keyword-only and optional. Only provided parameters are modified; others retain their current values. New values are validated before assignment. Parameters ---------- weight : float or array-like, optional New baseline synaptic weight. If not provided, unchanged. delay : float or Quantity, optional New synaptic delay in milliseconds. Must be positive. If not provided, unchanged. receptor_type : int, optional New target receptor port identifier. If not provided, unchanged. U : float or array-like, optional New utilization increment parameter. Must be in ``[0, 1]``. If not provided, unchanged. u : float or array-like, optional New current release probability. Must be in ``[0, 1]``. If not provided, unchanged. x : float or array-like, optional New current efficacy scaling factor. If not provided, unchanged. tau_rec : float or Quantity, optional New recovery time constant in milliseconds. Must be ``> 0``. If not provided, unchanged. tau_fac : float or Quantity, optional New facilitation time constant in milliseconds. Must be ``>= 0``. If not provided, unchanged. post : object, optional New default postsynaptic receiver. If not provided, unchanged. Raises ------ ValueError If ``U`` or ``u`` is not in ``[0, 1]``. ValueError If ``tau_rec`` is not strictly positive. ValueError If ``tau_fac`` is negative. ValueError If any scalar parameter has incorrect shape. Notes ----- - Setting ``u`` or ``x`` modifies the current synapse state, affecting subsequent spike processing. - The initial values (``_u0``, ``_x0``) are updated to match new ``u`` and ``x`` values, so :meth:`init_state` will restore these new values. - All validation is performed before any state modification, ensuring atomicity (either all changes succeed or none do). Examples -------- .. code-block:: python >>> import brainpy.state as bst >>> import saiunit as u >>> syn = bst.tsodyks2_synapse(weight=1.0, U=0.5) >>> syn.init_state() >>> # Modify parameters >>> syn.set(U=0.3, tau_rec=300.0 * u.ms) >>> # Modify current state >>> syn.set(u=0.8, x=0.5) >>> params = syn.get() >>> print(params['U'], params['u'], params['x']) 0.3 0.8 0.5 """ 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_x = self.x if x is _UNSET else self._to_scalar_float(x, name='x') 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.x = float(new_x) self.tau_rec = float(new_tau_rec) self.tau_fac = float(new_tau_fac) self._x0 = float(self.x) self._u0 = float(self.u)
[docs] def send( self, multiplicity: ArrayLike = 1.0, *, post=None, receptor_type: ArrayLike | None = None, ) -> bool: r"""Schedule one outgoing event with NEST ``tsodyks2_synapse`` dynamics. Processes a presynaptic spike event by: 1. Propagating synapse state (``x``, ``u``) from the last spike time to the current spike time using exponential decay equations. 2. Computing the effective synaptic weight as :math:`w_{\mathrm{eff}} = x \cdot u \cdot w \cdot \text{multiplicity}`. 3. Scheduling delayed delivery of the weighted event to the postsynaptic target. 4. Updating ``t_lastspike`` to the current spike timestamp. This method implements the exact NEST ``tsodyks2_synapse`` update semantics, including the special case for ``tau_fac == 0``. Parameters ---------- multiplicity : float or array-like, optional Presynaptic spike multiplicity. Scales the effective weight linearly. For standard point-neuron spikes, this is ``1.0``. For population models or weighted spike counts, can be > 1. Default: ``1.0``. post : object, optional Target postsynaptic receiver for this event. If ``None``, uses the default receiver set during construction. Default: ``None``. receptor_type : int or array-like, optional Target receptor port for this event. If ``None``, uses the default ``receptor_type`` set during construction. Default: ``None``. Returns ------- bool ``True`` if an event was scheduled (multiplicity non-zero), ``False`` otherwise. Notes ----- - If ``multiplicity`` is zero or evaluates to zero after conversion, no event is scheduled and state is not updated. - The spike timestamp is computed as ``current_time + dt``, where ``dt`` is the simulation timestep. This matches NEST's on-grid spike timing convention. - For the first spike (``t_lastspike < 0``), state propagation is skipped and initial ``x`` and ``u`` values are used directly. - State variables ``x`` and ``u`` are updated in place during this call, affecting all subsequent spikes. - The effective weight calculation includes the multiplicity factor, so multiple coincident spikes can be represented as a single call with ``multiplicity > 1``. Examples -------- .. code-block:: python >>> import brainpy.state as bst >>> import saiunit as u >>> # Create synapse with target >>> class Target(bst.nn.Dynamics): ... def __init__(self): ... super().__init__() ... self.current = 0.0 >>> target = Target() >>> syn = bst.tsodyks2_synapse( ... weight=1.0, ... delay=1.0 * u.ms, ... U=0.5, ... tau_rec=100.0 * u.ms, ... post=target ... ) >>> syn.init_state() >>> # Send a spike >>> with bst.environ.context(dt=0.1 * u.ms): ... success = syn.send(multiplicity=1.0) >>> print(success) True >>> # Send multiple spikes in quick succession >>> for i in range(5): ... syn.send(multiplicity=1.0) ... state = syn.get() ... print(f"Spike {i+1}: x={state['x']:.3f}, u={state['u']:.3f}") """ 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 the spike stamp. t_spike = self._current_time_ms() + dt_ms if self.t_lastspike >= 0.0: h = float(t_spike - self.t_lastspike) x_decay = math.exp(-h / self.tau_rec) u_decay = 0.0 if self.tau_fac == 0.0 else math.exp(-h / self.tau_fac) # Keep ordering identical to NEST models/tsodyks2_synapse.h::send. self.x = 1.0 + (self.x - self.x * self.u - 1.0) * x_decay self.u = self.U + self.u * (1.0 - self.U) * u_decay receiver = self._resolve_receiver(post) weighted_payload = multiplicity * (self.x * self.u * 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.t_lastspike = float(t_spike) return True
[docs] def update( self, pre_spike: ArrayLike = 0.0, *, post=None, receptor_type: ArrayLike | None = None, ) -> int: r"""Deliver due events, then schedule current-step presynaptic input. Primary update method called at each simulation timestep. Performs two operations in sequence: 1. **Delivery phase**: Delivers all events scheduled for the current timestep to their target receivers (accumulated from past spikes with appropriate delays). 2. **Reception phase**: Processes current presynaptic input by aggregating spike counts and calling :meth:`send` to schedule new delayed events. This method integrates with the event queue system inherited from :class:`static_synapse` and handles both current and delta input collection via :meth:`sum_current_inputs` and :meth:`sum_delta_inputs`. Parameters ---------- pre_spike : float or array-like, optional Presynaptic spike input at the current timestep. Represents spike multiplicity (typically ``0.0`` for no spike, ``1.0`` for a single spike). Can be > 1 for population models. Default: ``0.0``. post : object, optional Target postsynaptic receiver for new events. If ``None``, uses the default receiver set during construction. Default: ``None``. receptor_type : int or array-like, optional Target receptor port for new events. If ``None``, uses the default ``receptor_type`` set during construction. Default: ``None``. Returns ------- int Number of events delivered to postsynaptic targets during the delivery phase of this timestep. Notes ----- - The method first delivers events from the queue, then processes new presynaptic input. This ordering ensures causality: events generated at timestep :math:`t` are delivered at :math:`t + d`, where :math:`d` is the delay in timesteps. - Current inputs are accumulated from multiple sources via :meth:`sum_current_inputs`, allowing multiple projections to target the same synapse. - Delta inputs (instantaneous inputs delivered in the same timestep) are also accumulated via :meth:`sum_delta_inputs`. - If the total aggregated input is zero, no new event is scheduled and synapse state remains unchanged. Examples -------- .. code-block:: python >>> import brainpy.state as bst >>> import saiunit as u >>> # Create synapse with target >>> class Target(bst.nn.Dynamics): ... def __init__(self): ... super().__init__() ... self.current = 0.0 ... def update(self, inp=0.0): ... self.current += inp >>> target = Target() >>> syn = bst.tsodyks2_synapse( ... weight=1.0, ... delay=1.0 * u.ms, ... U=0.5, ... tau_rec=200.0 * u.ms, ... post=target ... ) >>> syn.init_state() >>> target.init_state() >>> # Simulate spike train >>> with bst.environ.context(dt=0.1 * u.ms): ... for t in range(20): ... spike = 1.0 if t in [0, 5, 10] else 0.0 ... n_delivered = syn.update(pre_spike=spike) ... if n_delivered > 0: ... print(f"Step {t}: delivered {n_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