urbanczik_synapse#

class brainpy.state.urbanczik_synapse(weight=1.0, delay=1.0, delay_steps=1, tau_Delta=100.0, eta=0.07, Wmin=0.0, Wmax=100.0, PI_integral=0.0, PI_exp_integral=0.0, tau_L_trace=0.0, tau_s_trace=0.0, t_last_spike_ms=-1.0, name=None)#

NEST-compatible urbanczik_synapse connection model.

Plastic synapse implementing Urbanczik-Senn dendritic prediction error learning rule for supervised learning in multi-compartment neurons. This synapse requires target neurons that archive dendritic prediction errors (e.g., pp_cond_exp_mc_urbanczik).

1. Mathematical Model

This implementation reproduces the connection-level semantics of NEST models/urbanczik_synapse.{h,cpp}. The learning rule combines presynaptic spike traces with postsynaptic dendritic prediction errors to update synaptic weights through a low-pass filtered plasticity signal.

1.1. Presynaptic Traces

Two exponential traces track presynaptic spiking activity with different time constants:

\[\tau_L^\mathrm{tr}(t) = \tau_L^\mathrm{tr}(t_{last}) \exp\left(\frac{t_{last}-t}{\tau_L}\right) + 1\]
\[\tau_s^\mathrm{tr}(t) = \tau_s^\mathrm{tr}(t_{last}) \exp\left(\frac{t_{last}-t}{\tau_s}\right) + 1\]

where \(t\) is current spike time, \(t_{last}\) is previous spike time, \(\tau_L = C_m / g_L\) is membrane time constant, and \(\tau_s\) is synaptic time constant (tau_syn_ex for excitatory weights, tau_syn_in for inhibitory).

1.2. Plasticity Signal

For each postsynaptic dendritic prediction error entry \((t_i, \Delta w_i)\) in the history window \((t_{last} - d, t - d]\) (where \(d\) is dendritic delay):

\[\Pi_i = \left[\tau_L^\mathrm{tr}\exp\left(\frac{t_{last}-(t_i+d)}{\tau_L}\right) - \tau_s^\mathrm{tr}\exp\left(\frac{t_{last}-(t_i+d)}{\tau_s}\right)\right] \Delta w_i\]

Two integrals accumulate plasticity contributions:

\[\Pi_\mathrm{int} \leftarrow \Pi_\mathrm{int} + \sum_i \Pi_i\]
\[\Pi_\mathrm{exp} \leftarrow \exp\left(\frac{t_{last}-t}{\tau_\Delta}\right)\Pi_\mathrm{exp} + \sum_i \exp\left(\frac{(t_i+d)-t}{\tau_\Delta}\right)\Pi_i\]

where \(\tau_\Delta\) is the low-pass filter time constant for weight changes.

1.3. Weight Update

The synaptic weight is updated using the filtered difference of integrals:

\[w \leftarrow \mathrm{clip}\left(w_0 + \frac{15\,C_m\,\tau_s\,\eta}{g_L(\tau_L-\tau_s)} (\Pi_\mathrm{int} - \Pi_\mathrm{exp}), W_{min}, W_{max}\right)\]

where \(w_0\) is init_weight, \(\eta\) is learning rate, and \(C_m\), \(g_L\) are membrane capacitance and leak conductance of the dendritic compartment.

2. NEST Implementation Fidelity

This class preserves NEST’s exact send-ordering in urbanczik_synapse::send(...):

  1. Read archived history in \((t_{last} - d, t - d]\)

  2. Update \(\Pi_\mathrm{int}\) and \(\Pi_\mathrm{exp}\) integrals

  3. Compute new weight with clipping

  4. Emit spike event with updated weight

  5. Update \(\tau_L^\mathrm{tr}\) and \(\tau_s^\mathrm{tr}\) traces

  6. Set \(t_{last} = t\)

3. Computational Considerations

  • Synaptic time constant selection: Uses tau_syn_ex when weight > 0, otherwise tau_syn_in, matching NEST’s current-weight-dependent branching

  • Numerical precision: Sub-grid timestamp offsets are ignored as in NEST

  • Weight bounds: Hard clipping to [Wmin, Wmax] after each update

  • Sign constraints: Weight, Wmin, and Wmax must all share the same sign (enforced at init and status updates)

4. Target Neuron Requirements

The target neuron must implement the Urbanczik archiving interface:

  • get_urbanczik_history(t1, t2, comp): Returns prediction error entries in \((t1, t2]\) for compartment comp (default 1 for dendritic)

  • get_g_L(comp), get_tau_L(comp) or get_C_m(comp)/get_g_L(comp)

  • get_C_m(comp), get_tau_syn_ex(comp), get_tau_syn_in(comp)

History entries support multiple formats: objects with t_/dw_ attributes (NEST-style), objects with t/dw attributes, dicts with those keys, or 2-tuples (t, dw).

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

  • delay (float or ArrayLike, optional) – Dendritic delay in milliseconds used for history lookup. Must be > 0. Default: 1.0 ms

  • delay_steps (int or ArrayLike, optional) – Event delivery delay in simulation time steps. Must be >= 1. Default: 1

  • tau_Delta (float or ArrayLike, optional) – Time constant in milliseconds for low-pass filtering of weight changes. Controls the temporal smoothing of plasticity signals. Larger values produce slower, more stable learning. Default: 100.0 ms

  • eta (float or ArrayLike, optional) – Learning rate (dimensionless). Scales the magnitude of weight updates. Typical range 0.01–0.1 for cortical models. Default: 0.07

  • Wmin (float or ArrayLike, optional) – Lower bound of synaptic weight (hard clipping). Must share sign with weight and Wmax. Default: 0.0

  • Wmax (float or ArrayLike, optional) – Upper bound of synaptic weight (hard clipping). Must share sign with weight and Wmin. Default: 100.0

  • PI_integral (float or ArrayLike, optional) – Initial value of unfiltered accumulated plasticity integral \(\Pi_\mathrm{int}\). Default: 0.0

  • PI_exp_integral (float or ArrayLike, optional) – Initial value of exponentially filtered plasticity integral \(\Pi_\mathrm{exp}\). Default: 0.0

  • tau_L_trace (float or ArrayLike, optional) – Initial state of \(\tau_L\) presynaptic trace. Default: 0.0

  • tau_s_trace (float or ArrayLike, optional) – Initial state of \(\tau_s\) presynaptic trace. Default: 0.0

  • t_last_spike_ms (float or ArrayLike, optional) – Last presynaptic spike time in milliseconds. Default: -1.0 (no previous spike)

  • name (str, optional) – Instance name for debugging and logging. Default: None

Parameter Mapping

NEST parameter mappings to this implementation:

NEST Parameter

brainpy.state Attribute

weight

weight (current synaptic weight)

delay

delay (dendritic delay for history)

tau_Delta

tau_Delta (low-pass time constant)

eta

eta (learning rate)

Wmin

Wmin (lower weight bound)

Wmax

Wmax (upper weight bound)

init_weight

init_weight (baseline weight for updates)

receptor_type

passed per spike event

t_lastspike

t_last_spike_ms

Raises:
  • ValueError – If delay is not positive, delay_steps < 1, or weight/Wmin/Wmax have inconsistent signs

  • AttributeError – If target neuron does not implement required Urbanczik archiving interface methods

Notes

  • set_status() behavior: Following NEST, set_status() always resets init_weight to the current weight unless explicitly provided in the status dict

  • Multiplicity: Spike multiplicity is validated but not used in plasticity computation (NEST compatibility)

  • Sub-grid timing: Precise spike time offsets within a time step are ignored in this plasticity rule (consistent with NEST implementation)

Examples

Basic synapse creation and spike processing:

>>> import brainpy.state as bp
>>> # Create synapse with moderate learning rate
>>> syn = bp.urbanczik_synapse(
...     weight=0.5,
...     delay=1.0,
...     tau_Delta=80.0,
...     eta=0.05,
...     Wmin=0.0,
...     Wmax=10.0
... )
>>>
>>> # Check initial status
>>> status = syn.get_status()
>>> print(f"Initial weight: {status['weight']}")
Initial weight: 0.5
>>> print(f"Learning rate: {status['eta']}")
Learning rate: 0.05

Processing spike trains with mock target neuron:

>>> class MockUrbanczikNeuron:
...     def __init__(self):
...         self.history = []
...
...     def get_urbanczik_history(self, t1, t2, comp):
...         # Return prediction errors in (t1, t2]
...         return [(t, dw) for t, dw in self.history if t1 < t <= t2]
...
...     def get_g_L(self, comp): return 10.0  # nS
...     def get_tau_L(self, comp): return 20.0  # ms
...     def get_C_m(self, comp): return 200.0  # pF
...     def get_tau_syn_ex(self, comp): return 2.0  # ms
...     def get_tau_syn_in(self, comp): return 5.0  # ms
>>>
>>> target = MockUrbanczikNeuron()
>>>
>>> # Simulate presynaptic spike at t=10 ms
>>> event = syn.send(t_spike_ms=10.0, target=target)
>>> print(f"Weight after spike: {event['weight']:.3f}")
Weight after spike: 0.500
>>>
>>> # Add dendritic prediction error and process another spike
>>> target.history.append((8.0, 0.1))  # (time_ms, delta_w)
>>> event = syn.send(t_spike_ms=20.0, target=target)
>>> print(f"Weight after learning: {event['weight']:.3f}")
Weight after learning: 0.503

Weight bound enforcement:

>>> syn = bp.urbanczik_synapse(weight=5.0, Wmin=0.0, Wmax=10.0)
>>> syn.set_status({'weight': 12.0})  # Exceeds Wmax
>>> print(syn.get('weight'))
10.0
>>> syn.set_status({'weight': -1.0})  # Violates sign constraint
Traceback (most recent call last):
ValueError: Weight and Wmax must have same sign.

References

See also

pp_cond_exp_mc_urbanczik

Multi-compartment neuron supporting Urbanczik archiving

stdp_synapse

Classical spike-timing dependent plasticity synapse

get(key='status')[source]#

Retrieve synapse parameter or full status.

Parameters:

key (str, optional) – Parameter name or 'status' for complete state dict. Valid keys match those in get_status() return dict. Default: 'status'

Returns:

If key='status', returns full status dict. Otherwise returns scalar value of requested parameter.

Return type:

Any

Raises:

KeyError – If key is not a recognized parameter name.

Examples

>>> syn = bp.urbanczik_synapse(weight=1.5, eta=0.06)
>>> syn.get('weight')
1.5
>>> syn.get('eta')
0.06
>>> full_status = syn.get('status')
>>> print(type(full_status))
<class 'dict'>
get_status()[source]#

Return current synapse state and parameters.

Returns:

Complete synapse state dictionary. Keys include: weight (float), delay (float), delay_steps (int), tau_Delta (float), eta (float), Wmin (float), Wmax (float), init_weight (float), PI_integral (float), PI_exp_integral (float), tau_L_trace (float), tau_s_trace (float), t_last_spike_ms (float), size_of (int), and capability flags (has_delay, is_primary, etc.).

Return type:

dict[str, Any]

Notes

Compatible with NEST GetStatus() semantics. All floating-point values are guaranteed finite (no NaN or infinity).

Examples

>>> syn = bp.urbanczik_synapse(weight=2.5, eta=0.08)
>>> status = syn.get_status()
>>> print(f"Weight: {status['weight']}, Learning rate: {status['eta']}")
Weight: 2.5, Learning rate: 0.08
property properties: dict[str, Any]#

Return synapse capability flags.

Returns:

Dictionary with boolean flags: - has_delay: Connection supports transmission delays (always True) - is_primary: This is a primary connection type (always True) - requires_urbanczik_archiving: Target must implement Urbanczik history (always True) - supports_hpc: Compatible with high-performance computing features (always True) - supports_lbl: Supports local branching levels in dendritic trees (always True) - supports_wfr: Supports waveform relaxation methods (always True)

Return type:

dict[str, Any]

Notes

These flags match NEST synapse property conventions for integration with NEST-compatible simulation infrastructure.

send(t_spike_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#

Process presynaptic spike, update weight via dendritic prediction errors, and emit event.

This method implements the core Urbanczik-Senn plasticity computation. It retrieves postsynaptic prediction error history from the target neuron, updates presynaptic traces and plasticity integrals, computes new synaptic weight, and returns spike event payload.

Computation Order (NEST-exact):

  1. Query target’s get_urbanczik_history() for entries in \((t_{last} - d, t - d]\)

  2. For each history entry, compute \(\Pi_i\) using current trace states

  3. Update \(\Pi_\mathrm{int}\) and \(\Pi_\mathrm{exp}\) accumulators

  4. Compute new weight with clipping to [Wmin, Wmax]

  5. Create spike event dict with updated weight

  6. Update \(\tau_L^\mathrm{tr}\) and \(\tau_s^\mathrm{tr}\) traces

  7. Set \(t_{last} = t\)

Parameters:
  • t_spike_ms (float or ArrayLike) – Presynaptic spike time in milliseconds. Must be finite scalar.

  • target (Any) – Postsynaptic neuron implementing Urbanczik archiving interface. Must provide: get_urbanczik_history(t1, t2, comp), get_g_L(comp), get_tau_L(comp) (or get_C_m(comp)), get_tau_syn_ex(comp), get_tau_syn_in(comp).

  • receptor_type (int or ArrayLike, optional) – Receptor channel index on target neuron. Default: 0

  • multiplicity (float or ArrayLike, optional) – Spike event multiplicity (validated but not used in plasticity). Must be >= 0. Default: 1.0

  • delay (float or ArrayLike, optional) – Override dendritic delay for this spike (milliseconds, must be > 0). If None, uses self.delay. Default: None

  • delay_steps (int or ArrayLike, optional) – Override event delivery delay for this spike (steps, must be >= 1). If None, uses self.delay_steps. Default: None

Returns:

Spike event dictionary. Keys: weight (float, updated synaptic weight), delay (float, dendritic delay in ms), delay_steps (int), receptor_type (int), multiplicity (float), t_spike_ms (float, spike time in ms), tau_s_ms (float, synaptic time constant used), PI_integral (float), PI_exp_integral (float), tau_L_trace_post (float), tau_s_trace_post (float).

Return type:

dict[str, Any]

Raises:
  • AttributeError – If target does not implement required Urbanczik archiving interface methods.

  • ValueError – If any parameter is non-finite, delay is not positive, delay_steps < 1, or multiplicity < 0.

Notes

  • Synaptic time constant selection: Uses tau_syn_ex if current weight > 0, otherwise tau_syn_in. This branching matches NEST’s current-weight-dependent logic.

  • Sub-grid precision: Sub-timestep spike offsets are ignored in plasticity computation (NEST compatibility).

  • History window: Query interval \((t_{last} - d, t - d]\) is open on left, closed on right, matching NEST’s get_history() semantics.

  • Multiplicity: Validated but not incorporated into weight update (NEST behavior).

Examples

Process single spike:

>>> syn = bp.urbanczik_synapse(weight=1.0, eta=0.05)
>>> event = syn.send(t_spike_ms=10.0, target=mock_neuron)
>>> print(f"Updated weight: {event['weight']:.3f}")
Updated weight: 1.003

Override delay for specific spike:

>>> event = syn.send(t_spike_ms=20.0, target=mock_neuron, delay=2.0)
>>> print(f"Delay used: {event['delay']} ms")
Delay used: 2.0 ms

Access trace states post-update:

>>> event = syn.send(t_spike_ms=30.0, target=mock_neuron)
>>> print(f"tau_L trace: {event['tau_L_trace_post']:.3f}")
tau_L trace: 1.105
set_delay(delay)[source]#

Update dendritic delay.

Parameters:

delay (float or ArrayLike) – New dendritic delay in milliseconds. Must be > 0.

Raises:

ValueError – If delay is not positive, non-finite, or non-scalar.

set_delay_steps(delay_steps)[source]#

Update event delivery delay in time steps.

Parameters:

delay_steps (int or ArrayLike) – New event delivery delay. Must be >= 1.

Raises:

ValueError – If delay_steps is less than 1 or not an integer value.

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

Update synapse parameters and state.

Parameters:
  • status (dict[str, Any], optional) – Dictionary of parameter updates. Keys match those returned by get_status().

  • **kwargs – Additional parameter updates as keyword arguments. These override any values in status dict if both are provided.

Raises:

ValueError – If updated parameters violate sign constraints (weight, Wmin, Wmax must share sign), if delay is not positive, if delay_steps < 1, or if any value is non-finite.

Notes

  • NEST compatibility: Following NEST SetStatus() semantics, init_weight is automatically reset to the current weight after updates unless explicitly provided in the update dict

  • All updatable parameters: weight, delay, delay_steps, tau_Delta, eta, Wmin, Wmax, PI_integral, PI_exp_integral, tau_L_trace, tau_s_trace, t_last_spike_ms, init_weight

  • Sign constraint validation occurs after all updates are applied

Examples

Update single parameter:

>>> syn = bp.urbanczik_synapse(weight=1.0)
>>> syn.set_status(eta=0.1)
>>> print(syn.get('eta'))
0.1

Batch update with dict:

>>> syn.set_status({'weight': 2.0, 'tau_Delta': 120.0})
>>> status = syn.get_status()
>>> print(f"Weight: {status['weight']}, tau_Delta: {status['tau_Delta']}")
Weight: 2.0, tau_Delta: 120.0

Keyword arguments override dict values:

>>> syn.set_status({'eta': 0.05}, eta=0.08)
>>> print(syn.get('eta'))
0.08
set_weight(weight)[source]#

Update synaptic weight with sign constraint validation.

Parameters:

weight (float or ArrayLike) – New synaptic weight. Must be finite scalar and share sign with Wmin/Wmax.

Raises:

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

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 return event history.

Convenience method for batch processing of spike trains. Each spike is processed sequentially using send(), with synapse state (weight, traces, integrals) updating after each spike.

Parameters:
  • pre_spike_times_ms (array_like) – 1D array or sequence of presynaptic spike times in milliseconds. Will be flattened if multidimensional. Order matters: earlier spikes affect later ones through trace dynamics.

  • target (Any) – Postsynaptic neuron with Urbanczik archiving interface.

  • receptor_type (int or ArrayLike, optional) – Receptor channel index applied to all spikes. Default: 0

  • multiplicity (float or ArrayLike, optional) – Spike multiplicity applied to all spikes. Default: 1.0

  • delay (float or ArrayLike, optional) – Override dendritic delay for all spikes (milliseconds). Default: None

  • delay_steps (int or ArrayLike, optional) – Override event delivery delay for all spikes (steps). Default: None

Returns:

List of spike event dictionaries, one per input spike, in chronological order. Each dict has same structure as send() return value.

Return type:

list[dict[str, Any]]

Notes

  • Stateful processing: Synapse internal state (weight, traces) persists across spikes in the train. Final state reflects cumulative plasticity effects.

  • Performance: For large spike trains (>10000 spikes), consider batching or vectorization depending on target neuron implementation.

  • Temporal ordering: Input spikes should typically be sorted in ascending time order for biologically realistic plasticity dynamics.

Examples

Process spike train:

>>> syn = bp.urbanczik_synapse(weight=1.0, eta=0.05)
>>> spike_times = [10.0, 15.0, 20.0, 25.0]
>>> events = syn.simulate_pre_spike_train(spike_times, target=mock_neuron)
>>> weights = [e['weight'] for e in events]
>>> print(f"Weight trajectory: {weights}")
Weight trajectory: [1.002, 1.005, 1.008, 1.011]

Extract trace evolution:

>>> import numpy as np
>>> spike_times = np.arange(0, 100, 5.0)
>>> events = syn.simulate_pre_spike_train(spike_times, target=mock_neuron)
>>> tau_L_traces = [e['tau_L_trace_post'] for e in events]
>>> print(f"Final tau_L trace: {tau_L_traces[-1]:.3f}")
Final tau_L trace: 2.456

See also

send

Process single spike with full control over parameters.

to_spike_event(t_spike_ms, target, receptor_type=0, multiplicity=1.0, delay=None, delay_steps=None)[source]#

Alias for send() method.

This method provides an alternative API name for spike event generation, matching common naming conventions in event-based simulators.

Parameters:
  • t_spike_ms (float or ArrayLike) – Presynaptic spike time in milliseconds.

  • target (Any) – Postsynaptic neuron with Urbanczik archiving interface.

  • receptor_type (int or ArrayLike, optional) – Receptor channel index. Default: 0

  • multiplicity (float or ArrayLike, optional) – Spike event multiplicity. Default: 1.0

  • delay (float or ArrayLike, optional) – Override dendritic delay (milliseconds). Default: None (use self.delay)

  • delay_steps (int or ArrayLike, optional) – Override event delivery delay (steps). Default: None (use self.delay_steps)

Returns:

Spike event dictionary identical to send() return value.

Return type:

dict[str, Any]

See also

send

Primary spike processing method with full documentation.