diffusion_connection#

class brainpy.state.diffusion_connection(drift_factor=1.0, diffusion_factor=1.0, name=None)#

NEST-compatible diffusion_connection connection model.

diffusion_connection implements instantaneous diffusion-based coupling between rate-based neuron populations, specifically designed for use with siegert_neuron mean-field models. Unlike standard synaptic connections that transmit weighted spikes or rates, this connection simultaneously affects both the drift (mean) and diffusion (variance) components of the target neuron’s input statistics.

This model replaces the single synaptic weight with two independent scaling factors: drift_factor controls the contribution to the mean input current (first moment), while diffusion_factor controls the contribution to input variance (second moment). This dual-factor design enables accurate representation of population-level fluctuations in mean-field models.

1. Mathematical Model

The connection transforms presynaptic rate activity \(r_j(t)\) into dual postsynaptic inputs:

\[\begin{split}\Delta\mu_i &= g_{\mu}\,r_j(t), \\ \Delta\sigma^2_i &= g_{\sigma}\,r_j(t),\end{split}\]

where:

  • \(g_{\mu}\) is drift_factor (dimensionless scaling coefficient)

  • \(g_{\sigma}\) is diffusion_factor (dimensionless scaling coefficient)

  • \(r_j(t)\) is the presynaptic firing rate (Hz)

  • \(\Delta\mu_i\) is the drift (mean current) contribution to postsynaptic neuron \(i\)

  • \(\Delta\sigma^2_i\) is the diffusion (variance) contribution

The target siegert_neuron accumulates these contributions from all incoming connections to compute its total effective input statistics:

\[\begin{split}\mu_{\mathrm{total}} &= \sum_j g_{\mu,j}\,r_j, \\ \sigma^2_{\mathrm{total}} &= \sum_j g_{\sigma,j}\,r_j.\end{split}\]

2. Waveform Relaxation (WFR) Event Semantics

For networks using waveform relaxation iterative solvers, the connection transmits multi-lag coefficient arrays representing interpolated rate values across a time window:

Event structure:

\[\text{coeffarray} = [r(t_0), r(t_1), \ldots, r(t_{n-1})]\]

Target accumulation (per lag :math:`i`):

\[\begin{split}\mu_i &\leftarrow \mu_i + g_{\mu}\cdot r(t_i), \\ \sigma^2_i &\leftarrow \sigma^2_i + g_{\sigma}\cdot r(t_i).\end{split}\]

This allows the target neuron to iteratively refine its solution using updated presynaptic activity estimates without re-sending events at every substep.

3. Design Constraints

To maintain consistency with NEST’s implementation, this model enforces several architectural constraints:

No transmission delay:

Unlike spike-based synapses, diffusion connections are instantaneous. The absence of delay reflects the mean-field assumption that population dynamics operate on slower timescales than individual spike transmission.

No standard weight parameter:

The traditional weight parameter is intentionally unsupported. Instead, users must explicitly specify drift_factor and diffusion_factor to clarify the distinction between mean and variance contributions. Attempting to set weight raises an error with NEST’s original message (preserving the typo "specifiy" for exact compatibility).

Symmetric status API:

The get_status() method returns weight: 1.0 and delay: None for API consistency, but set_status() rejects attempts to modify these fields.

4. Usage Context

diffusion_connection is specialized for mean-field population models:

Typical use cases:

  • Connecting multiple siegert_neuron populations in a network

  • Representing effective connectivity in population density approaches

  • Modeling input fluctuations from background population activity

  • Implementing approximate mesoscale network dynamics

Not suitable for:

  • Spiking neuron models (use static_synapse or variants)

  • Conductance-based synapses (no reversal potential mechanism)

  • Plastic connections (no learning rule support)

Parameters:
  • drift_factor (float, array-like, or Quantity, optional) – Scaling coefficient for presynaptic rate contribution to postsynaptic drift (mean current) input. Must be scalar, dimensionless. Positive values increase excitatory drive; negative values contribute inhibition. Default: 1.0 (unscaled transmission).

  • diffusion_factor (float, array-like, or Quantity, optional) – Scaling coefficient for presynaptic rate contribution to postsynaptic diffusion (variance) input. Must be scalar, dimensionless. Typically non-negative to preserve physical interpretation of variance, though negative values are permitted for specialized modeling scenarios. Default: 1.0 (unscaled transmission).

  • name (str, optional) – Unique identifier for this connection instance. Used for debugging and logging. If None, no name is assigned. Default: None.

drift_factor#

Current drift scaling factor (read/write via set_drift_factor() or set_status()).

Type:

float

diffusion_factor#

Current diffusion scaling factor (read/write via set_diffusion_factor() or set_status()).

Type:

float

weight#

Read-only pseudo-parameter, always 1.0. Present for NEST API parity. Attempting to modify via set_status() or set_weight() raises ValueError.

Type:

float

name#

Connection instance name.

Type:

str or None

SUPPORTS_WFR#

Always True. Indicates waveform relaxation compatibility.

Type:

bool (class attribute)

HAS_DELAY#

Always False. Indicates absence of transmission delay.

Type:

bool (class attribute)

Notes

Design differences from NEST:

  1. Type system: This implementation validates input types via _to_float_scalar, rejecting non-scalar values. NEST relies on C++ type system and templating.

  2. Event handling: Methods like prepare_secondary_event() and project_coeffarray() provide explicit event construction APIs not directly exposed in NEST’s C++ interface.

  3. Unit handling: Supports saiunit.Quantity inputs (mantissa extracted automatically). NEST is unit-agnostic at the connection level.

Error message compatibility:

The weight-setting error preserves NEST’s original typo: "specifiy" instead of "specify". This intentional deviation maintains exact string matching for compatibility testing and migration scripts.

Negative diffusion factors:

While physically questionable (variance should be non-negative), negative diffusion_factor values are permitted. This flexibility supports:

  • Variance reduction mechanisms (anti-correlated noise cancellation)

  • Numerical experiments requiring signed contributions

  • Surrogate models with unconventional interpretations

Users should validate biological plausibility of their parameter choices.

Performance considerations:

  • Lightweight: No state variables, minimal computation overhead

  • Memory: Stores only two scalar factors (drift, diffusion)

  • Thread safety: Not thread-safe; use separate instances per thread

See also

gap_junction

Electrical coupling connection (voltage-difference driven)

rate_connection_instantaneous

Generic instantaneous rate connection

siegert_neuron

Target neuron model for diffusion connections

References

Examples

Basic connection between two Siegert neurons:

>>> import brainpy.state as bs
>>> # Create two mean-field neurons (pseudo-code; siegert_neuron not yet implemented)
>>> source = bs.siegert_neuron(1, mu=10.0, sigma=5.0)
>>> target = bs.siegert_neuron(1, mu=5.0, sigma=3.0)
>>>
>>> # Create diffusion connection
>>> conn = bs.diffusion_connection(
...     drift_factor=0.8,      # Strong mean current contribution
...     diffusion_factor=0.3,  # Moderate variance contribution
...     name='excitatory_diffusion',
... )
>>>
>>> # Query connection parameters
>>> status = conn.get_status()
>>> print(f"Drift: {status['drift_factor']}, Diffusion: {status['diffusion_factor']}")
Drift: 0.8, Diffusion: 0.3

Creating a WFR secondary event:

>>> import numpy as np
>>> # Presynaptic rate trajectory over 5 lags
>>> rate_coeffs = np.array([10.0, 12.0, 11.5, 10.2, 9.8])
>>>
>>> # Prepare event payload
>>> event = conn.prepare_secondary_event(rate_coeffs)
>>> print(event)
{'coeffarray': array([10. , 12. , 11.5, 10.2,  9.8]),
 'drift_factor': 0.8, 'diffusion_factor': 0.3}

Projecting coefficients to drift/diffusion inputs:

>>> # Apply connection factors to coefficient array
>>> drift_input, diffusion_input = conn.project_coeffarray(rate_coeffs)
>>> print(f"Drift contribution: {drift_input}")
>>> print(f"Diffusion contribution: {diffusion_input}")
Drift contribution: [ 8.   9.6  9.2  8.16 7.84]
Diffusion contribution: [3.  3.6 3.45 3.06 2.94]

Creating step-wise delayed events:

>>> # Convert multi-lag coefficients to step-indexed events
>>> events = conn.coeffarray_to_step_events(
...     rate_coeffs,
...     first_delay_steps=5,  # Start at delay step 5
...     multiplicity=1.0,
... )
>>> for i, evt in enumerate(events):
...     print(f"Lag {i}: coeff={evt['coeff']}, delay_steps={evt['delay_steps']}")
Lag 0: coeff=10.0, delay_steps=5
Lag 1: coeff=12.0, delay_steps=6
Lag 2: coeff=11.5, delay_steps=7
Lag 3: coeff=10.2, delay_steps=8
Lag 4: coeff=9.8, delay_steps=9

Modifying parameters during simulation:

>>> # Update drift factor only
>>> conn.set_drift_factor(1.2)
>>> assert conn.drift_factor == 1.2
>>>
>>> # Update both factors via set_status
>>> conn.set_status(drift_factor=0.5, diffusion_factor=0.1)
>>> status = conn.get_status()
>>> assert status['drift_factor'] == 0.5
>>> assert status['diffusion_factor'] == 0.1

Attempting to set weight (error demonstration):

>>> try:
...     conn.set_weight(2.0)
... except ValueError as e:
...     print(f"Error: {e}")
Error: Please use the parameters drift_factor and diffusion_factor to specifiy the weights.

Attempting to set delay (error demonstration):

>>> try:
...     conn.set_status(delay=1.0)
... except ValueError as e:
...     print(f"Error: {e}")
Error: diffusion_connection has no delay.

Heterogeneous population connectivity:

>>> # Multiple connections with different factor profiles
>>> excitatory_conn = bs.diffusion_connection(
...     drift_factor=1.5,    # Strong excitatory drive
...     diffusion_factor=0.5, # Moderate noise
... )
>>> inhibitory_conn = bs.diffusion_connection(
...     drift_factor=-1.0,   # Inhibitory drive (negative)
...     diffusion_factor=0.2, # Low noise
... )
>>> background_conn = bs.diffusion_connection(
...     drift_factor=0.1,    # Weak drive
...     diffusion_factor=2.0, # Strong noise (background fluctuations)
... )

Single-step event creation for custom integration:

>>> # Create a single-step event for custom handling
>>> single_event = conn.to_siegert_event(
...     coeff=15.0,          # Rate value
...     delay_steps=10,      # Delivery at step 10
...     multiplicity=1.0,    # Single event
... )
>>> print(single_event)
{'coeff': 15.0, 'drift_factor': 0.5, 'diffusion_factor': 0.1,
 'delay_steps': 10, 'multiplicity': 1.0}

Inspecting connection properties:

>>> props = conn.properties
>>> print(f"Supports WFR: {props['supports_wfr']}")
>>> print(f"Has delay: {props['has_delay']}")
Supports WFR: True
Has delay: False

Using get() method for flexible parameter access:

>>> # Get full status dictionary
>>> full_status = conn.get('status')
>>>
>>> # Get specific parameter
>>> drift = conn.get('drift_factor')
>>> print(f"Drift factor: {drift}")
Drift factor: 0.5
>>>
>>> # Attempting unsupported key raises error
>>> try:
...     conn.get('unsupported_key')
... except KeyError as e:
...     print(f"Error: {e}")
Error: 'Unsupported key "unsupported_key" for diffusion_connection.get().'
coeffarray_to_step_events(coeffarray, first_delay_steps=0, multiplicity=1.0)[source]#

Convert multi-lag coefficient array into sequential single-step events.

Transforms a NEST-style lag-indexed coefficient array (used in WFR secondary events) into a list of single-step event dictionaries with linearly increasing delay steps. Each coefficient becomes a separate event scheduled for sequential future time steps.

Parameters:
  • coeffarray (array-like or Quantity) – Multi-lag coefficient array representing rate values at sequential time substeps. Shape: (n_lags,) where n_lags is the number of events to generate. If saiunit.Quantity, mantissa is extracted. Must be non-empty 1D array.

  • first_delay_steps (int, array-like, or Quantity, optional) – Time step offset for the first event (lag 0). Subsequent events are scheduled at first_delay_steps + lag_index. Must be scalar non-negative integer. Default: 0 (immediate delivery sequence starting current step).

  • multiplicity (float, array-like, or Quantity, optional) – Shared event weight multiplier applied to all events. Must be scalar. Typically 1.0 (no scaling) or represents population fraction. Default: 1.0 (no scaling).

Returns:

List of event dictionaries, length n_lags. Each dictionary contains:

  • 'coeff' : float — Coefficient value from coeffarray[i]

  • 'drift_factor' : float — Connection’s drift scaling factor

  • 'diffusion_factor' : float — Connection’s diffusion scaling factor

  • 'delay_steps' : int — Delivery step = first_delay_steps + i

  • 'multiplicity' : float — Shared multiplicity value

Return type:

list[dict[str, Any]]

Raises:
  • ValueError – If coeffarray is empty after conversion.

  • ValueError – If first_delay_steps is negative.

  • ValueError – If first_delay_steps or multiplicity is not scalar.

Notes

Delay step calculation:

For lag index \(i \in [0, n_{\text{lags}}-1]\):

\[\text{delay\_steps}_i = d_0 + i,\]

where \(d_0\) is first_delay_steps.

Event ordering:

Events are returned in ascending delay order (lag 0 first). This matches the typical chronological event processing order in simulation loops.

Multiplicity semantics:

All events share the same multiplicity. For per-event scaling, call to_siegert_event() individually in a loop with different multiplicity values.

Use cases:

  • Converting WFR multi-lag events to single-step event queue format

  • Implementing custom delay profiles in event-driven simulators

  • Debugging event delivery sequences

  • Bridging between time-driven (coeffarray) and event-driven representations

Performance note:

This method creates n_lags separate dictionary objects. For large coefficient arrays (n_lags > 1000), consider using prepare_secondary_event() with native WFR handling instead.

Examples

Basic conversion with zero initial delay:

>>> import numpy as np
>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(drift_factor=0.8, diffusion_factor=0.3)
>>> coeffs = np.array([10.0, 12.0, 11.5, 10.2, 9.8])
>>> events = conn.coeffarray_to_step_events(coeffs, first_delay_steps=0)
>>> for i, evt in enumerate(events):
...     print(f"Lag {i}: coeff={evt['coeff']:.1f}, delay={evt['delay_steps']}")
Lag 0: coeff=10.0, delay=0
Lag 1: coeff=12.0, delay=1
Lag 2: coeff=11.5, delay=2
Lag 3: coeff=10.2, delay=3
Lag 4: coeff=9.8, delay=4

Non-zero initial delay:

>>> coeffs = np.array([5.0, 6.0, 7.0])
>>> events = conn.coeffarray_to_step_events(coeffs, first_delay_steps=10)
>>> for evt in events:
...     print(f"Delay: {evt['delay_steps']}, Coeff: {evt['coeff']}")
Delay: 10, Coeff: 5.0
Delay: 11, Coeff: 6.0
Delay: 12, Coeff: 7.0

With multiplicity scaling:

>>> # Population of 100 neurons, 75 active
>>> coeffs = np.array([20.0, 25.0, 22.0])
>>> events = conn.coeffarray_to_step_events(
...     coeffs,
...     first_delay_steps=0,
...     multiplicity=0.75,  # 75% population activity
... )
>>> print(events[0]['multiplicity'])
0.75

Integration with event queue:

>>> # Convert WFR coefficients to event queue
>>> from collections import defaultdict
>>> coeffs = np.array([10.0, 11.0, 12.0, 13.0, 14.0])
>>> events = conn.coeffarray_to_step_events(coeffs, first_delay_steps=5)
>>>
>>> # Organize by delivery step
>>> event_queue = defaultdict(list)
>>> for evt in events:
...     event_queue[evt['delay_steps']].append(evt)
>>>
>>> # Process events at step 7
>>> step_7_events = event_queue[7]
>>> print(f"Step 7 receives {len(step_7_events)} event(s)")
>>> print(f"Coefficient: {step_7_events[0]['coeff']}")
Step 7 receives 1 event(s)
Coefficient: 12.0

Negative first_delay_steps error:

>>> try:
...     conn.coeffarray_to_step_events(coeffs, first_delay_steps=-5)
... except ValueError as e:
...     print(e)
first_delay_steps must be >= 0.

Verifying event structure:

>>> coeffs = np.array([15.0, 16.0])
>>> events = conn.coeffarray_to_step_events(coeffs)
>>> evt = events[0]
>>> print(f"Keys: {sorted(evt.keys())}")
>>> print(f"Drift factor: {evt['drift_factor']}")
>>> print(f"Diffusion factor: {evt['diffusion_factor']}")
Keys: ['coeff', 'delay_steps', 'diffusion_factor', 'drift_factor', 'multiplicity']
Drift factor: 0.8
Diffusion factor: 0.3
get(key='status')[source]#

Retrieve connection parameter(s) with flexible key-based access.

Provides unified access to connection status via string keys. Can return the full status dictionary or individual parameter values.

Parameters:

key (str, optional) – Parameter key to retrieve. Valid keys: 'status' (returns full dictionary), 'weight', 'delay', 'drift_factor', 'diffusion_factor', 'supports_wfr', 'has_delay'. Default: 'status' (returns full status dictionary).

Returns:

If key='status': full status dictionary from get_status(). If key matches a status field: the corresponding scalar value.

Return type:

dict or scalar

Raises:

KeyError – If key is not 'status' and does not match any status dictionary key.

Notes

Supported keys:

Key

Return Type

Description

'status'

dict

Full status dictionary (default)

'weight'

float

Always 1.0 (pseudo-parameter)

'delay'

None

Always None (no delay)

'drift_factor'

float

Current drift scaling factor

'diffusion_factor'

float

Current diffusion scaling factor

'supports_wfr'

bool

Always True

'has_delay'

bool

Always False

Implementation strategy:

The method calls get_status() once, then performs dictionary lookup for specific keys. This ensures consistency (all values come from a single status snapshot) but duplicates computation for single-key queries.

Examples

Get full status dictionary:

>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(drift_factor=0.8, diffusion_factor=0.3)
>>> status = conn.get('status')
>>> print(status)
{'weight': 1.0, 'delay': None, 'drift_factor': 0.8, ...}

Get specific parameter:

>>> drift = conn.get('drift_factor')
>>> print(drift)
0.8

Get multiple parameters:

>>> drift = conn.get('drift_factor')
>>> diffusion = conn.get('diffusion_factor')
>>> print(f"Factors: drift={drift}, diffusion={diffusion}")
Factors: drift=0.8, diffusion=0.3

Default behavior (omit key argument):

>>> # Equivalent to conn.get('status')
>>> status = conn.get()
>>> print(type(status))
<class 'dict'>

Invalid key error:

>>> try:
...     conn.get('invalid_key')
... except KeyError as e:
...     print(e)
'Unsupported key "invalid_key" for diffusion_connection.get().'
get_status()[source]#

Retrieve current connection parameters (NEST GetStatus equivalent).

Returns a dictionary of all connection parameters, including pseudo-parameters weight and delay for NEST API compatibility.

Returns:

Dictionary with keys:

  • 'weight' : float — Always 1.0 (read-only pseudo-parameter)

  • 'delay' : None — Always None (diffusion connections have no delay)

  • 'drift_factor' : float — Current drift scaling factor

  • 'diffusion_factor' : float — Current diffusion scaling factor

  • 'supports_wfr' : bool — Always True (WFR compatibility)

  • 'has_delay' : bool — Always False (instantaneous connection)

Return type:

dict[str, Any]

Notes

NEST API compatibility:

The returned dictionary structure matches NEST’s GetStatus output format. The weight and delay keys are present for API parity but represent immutable pseudo-parameters.

Implementation detail:

All returned values are cast to native Python types (float, bool, None) rather than JAX/NumPy arrays, ensuring JSON serializability and consistent behavior across different numerical backends.

Examples

>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(drift_factor=0.8, diffusion_factor=0.3)
>>> status = conn.get_status()
>>> print(status)
{'weight': 1.0, 'delay': None, 'drift_factor': 0.8,
 'diffusion_factor': 0.3, 'supports_wfr': True, 'has_delay': False}
prepare_secondary_event(coeffarray)[source]#

Construct a WFR secondary event payload for transmission.

Packages presynaptic rate coefficients with connection scaling factors into a dictionary suitable for delivery to target siegert_neuron instances during waveform relaxation iterations.

Parameters:

coeffarray (array-like or Quantity) – Presynaptic rate coefficient array, typically representing interpolated firing rates across multiple time lags. Shape: (n_lags,) where n_lags is the number of WFR substeps per iteration window. If saiunit.Quantity, mantissa is extracted (assumed Hz or dimensionless). Must be non-empty 1D array after conversion.

Returns:

Event payload dictionary with keys:

  • 'coeffarray' : np.ndarray — Validated coefficient array (float64)

  • 'drift_factor' : float — Connection’s drift scaling factor

  • 'diffusion_factor' : float — Connection’s diffusion scaling factor

Return type:

dict[str, Any]

Raises:

ValueError – If coeffarray is empty after conversion and reshaping.

Notes

Event structure rationale:

The returned dictionary contains all information needed by the target neuron to accumulate multi-lag inputs without re-querying the connection object:

\[\begin{split}\mu_i \leftarrow \mu_i + \text{drift\_factor} \cdot \text{coeffarray}[i], \\ \sigma^2_i \leftarrow \sigma^2_i + \text{diffusion\_factor} \cdot \text{coeffarray}[i].\end{split}\]

Conversion and validation:

  1. Extract mantissa if saiunit.Quantity

  2. Convert to NumPy float64 array via u.math.asarray

  3. Flatten to 1D (reshape(-1))

  4. Validate non-empty (size > 0)

Usage in simulation loop:

Typically called by presynaptic neuron during WFR event emission:

# Presynaptic neuron computes interpolation coefficients
rate_coeffs = presynaptic.get_wfr_coefficients()

# Connection packages them into event
event = connection.prepare_secondary_event(rate_coeffs)

# Deliver to all postsynaptic targets
for target in postsynaptic_neurons:
    target.handle_diffusion_event(event)

Examples

Basic event preparation:

>>> import numpy as np
>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(drift_factor=0.8, diffusion_factor=0.3)
>>> coeffs = np.array([10.0, 12.0, 11.5, 10.2, 9.8])
>>> event = conn.prepare_secondary_event(coeffs)
>>> print(event['coeffarray'])
[10.  12.  11.5 10.2  9.8]
>>> print(event['drift_factor'])
0.8

Event with saiunit Quantity:

>>> import saiunit as u
>>> rate_Hz = np.array([20.0, 25.0, 22.5]) * u.Hz
>>> event = conn.prepare_secondary_event(rate_Hz)
>>> # Mantissa extracted, units stripped
>>> print(event['coeffarray'])
[20.  25.  22.5]

Empty coeffarray error:

>>> try:
...     conn.prepare_secondary_event(np.array([]))
... except ValueError as e:
...     print(e)
Coefficient array must not be empty.
project_coeffarray(coeffarray)[source]#

Apply connection scaling factors to coefficient array.

Transforms presynaptic rate coefficients into separate drift and diffusion input contributions by element-wise multiplication with drift_factor and diffusion_factor.

Parameters:

coeffarray (array-like or Quantity) – Presynaptic rate coefficient array. Shape: (n_lags,) where n_lags is the number of time substeps in the WFR window. If saiunit.Quantity, mantissa is extracted. Must be non-empty 1D array after conversion.

Return type:

tuple[ndarray, ndarray]

Returns:

  • drift_contribution (np.ndarray) – Drift (mean current) input per lag, computed as drift_factor * coeffarray. Shape: (n_lags,), dtype: float64.

  • diffusion_contribution (np.ndarray) – Diffusion (variance) input per lag, computed as diffusion_factor * coeffarray. Shape: (n_lags,), dtype: float64.

Raises:

ValueError – If coeffarray is empty after conversion.

Notes

Mathematical operation:

For each lag index \(i\):

\[\begin{split}\text{drift}_i &= g_{\mu} \cdot r_i, \\ \text{diffusion}_i &= g_{\sigma} \cdot r_i,\end{split}\]

where \(r_i\) is coeffarray[i], \(g_{\mu}\) is drift_factor, and \(g_{\sigma}\) is diffusion_factor.

Return value interpretation:

The returned arrays represent additive contributions to the target neuron’s input statistics. The target accumulates these across all incoming connections:

\[\begin{split}\mu_{\mathrm{total},i} &= \sum_j \text{drift}_{j,i}, \\ \sigma^2_{\mathrm{total},i} &= \sum_j \text{diffusion}_{j,i}.\end{split}\]

Relationship to prepare_secondary_event():

This method performs the projection computation explicitly, while prepare_secondary_event() packages the raw factors for target-side computation. Use this method when pre-computing projected inputs on the sender side; use prepare_secondary_event() for standard event transmission.

Examples

Basic projection:

>>> import numpy as np
>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(drift_factor=0.8, diffusion_factor=0.3)
>>> coeffs = np.array([10.0, 12.0, 11.5, 10.2, 9.8])
>>> drift, diffusion = conn.project_coeffarray(coeffs)
>>> print("Drift contributions:", drift)
Drift contributions: [ 8.   9.6  9.2  8.16 7.84]
>>> print("Diffusion contributions:", diffusion)
Diffusion contributions: [3.   3.6  3.45 3.06 2.94]

Accumulation across multiple connections:

>>> # Target neuron receives from multiple sources
>>> conn1 = bs.diffusion_connection(drift_factor=0.8, diffusion_factor=0.2)
>>> conn2 = bs.diffusion_connection(drift_factor=0.5, diffusion_factor=0.4)
>>>
>>> coeffs1 = np.array([10.0, 11.0, 12.0])
>>> coeffs2 = np.array([5.0, 6.0, 7.0])
>>>
>>> drift1, diff1 = conn1.project_coeffarray(coeffs1)
>>> drift2, diff2 = conn2.project_coeffarray(coeffs2)
>>>
>>> total_drift = drift1 + drift2
>>> total_diffusion = diff1 + diff2
>>> print("Total drift per lag:", total_drift)
Total drift per lag: [10.5 11.8 13.1]

Identity transformation (unit factors):

>>> conn_identity = bs.diffusion_connection(drift_factor=1.0, diffusion_factor=1.0)
>>> coeffs = np.array([5.0, 10.0, 15.0])
>>> drift, diffusion = conn_identity.project_coeffarray(coeffs)
>>> # Both outputs equal input
>>> assert np.allclose(drift, coeffs)
>>> assert np.allclose(diffusion, coeffs)

Negative drift (inhibitory effect):

>>> conn_inhibitory = bs.diffusion_connection(drift_factor=-0.5, diffusion_factor=0.1)
>>> coeffs = np.array([10.0, 10.0, 10.0])
>>> drift, diffusion = conn_inhibitory.project_coeffarray(coeffs)
>>> print("Drift (inhibitory):", drift)
Drift (inhibitory): [-5. -5. -5.]
>>> print("Diffusion (positive):", diffusion)
Diffusion (positive): [1. 1. 1.]
set_delay(_)[source]#

Reject attempts to set delay (NEST compatibility stub).

diffusion_connection is instantaneous and does not support transmission delay. This method exists solely for NEST API compatibility and always raises an error.

Parameters:

_ (Any) – Ignored. Any value triggers error.

Raises:

ValueError – Always raised with message: "diffusion_connection has no delay".

Notes

Design rationale:

Diffusion connections represent mean-field population coupling, which operates on timescales slower than individual spike transmission. The absence of delay reflects this theoretical abstraction.

Examples

>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection()
>>> try:
...     conn.set_delay(1.0)
... except ValueError as e:
...     print(e)
diffusion_connection has no delay
set_diffusion_factor(diffusion_factor)[source]#

Update diffusion scaling factor.

Convenience method to modify diffusion_factor independently of other parameters. Validates and converts input to scalar float.

Parameters:

diffusion_factor (float, array-like, or Quantity) – New diffusion scaling factor. Must be scalar. If saiunit.Quantity, mantissa is extracted (assumed dimensionless).

Raises:

ValueError – If diffusion_factor is not scalar (size != 1).

See also

set_status

General parameter update method

set_drift_factor

Update drift factor

Examples

>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(diffusion_factor=1.0)
>>> conn.set_diffusion_factor(0.3)
>>> assert conn.diffusion_factor == 0.3
set_drift_factor(drift_factor)[source]#

Update drift scaling factor.

Convenience method to modify drift_factor independently of other parameters. Validates and converts input to scalar float.

Parameters:

drift_factor (float, array-like, or Quantity) – New drift scaling factor. Must be scalar. If saiunit.Quantity, mantissa is extracted (assumed dimensionless).

Raises:

ValueError – If drift_factor is not scalar (size != 1).

See also

set_status

General parameter update method

set_diffusion_factor

Update diffusion factor

Examples

>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(drift_factor=1.0)
>>> conn.set_drift_factor(0.5)
>>> assert conn.drift_factor == 0.5
set_status(status=None, **kwargs)[source]#

Update connection parameters (NEST SetStatus equivalent).

Modifies drift_factor and/or diffusion_factor while rejecting attempts to set unsupported parameters (weight, delay). Accepts parameters via dictionary argument or keyword arguments.

Parameters:
  • status (dict[str, Any], optional) – Dictionary of parameter updates. Valid keys: 'drift_factor', 'diffusion_factor'. Invalid keys 'weight' and 'delay' raise ValueError. If None, only keyword arguments are applied. Default: None.

  • **kwargs – Additional parameter updates via keyword arguments. Overrides values in status dictionary for duplicate keys. Same validation rules apply.

Raises:
  • ValueError – If 'delay' key is present (message: "diffusion_connection has no delay").

  • ValueError – dftype = brainstate.environ.dftype() If 'weight' key is present (message: "Please use the parameters     drift_factor and diffusion_factor to specifiy the weights."). Note: NEST’s original typo "specifiy" is preserved.

  • ValueError – If drift_factor or diffusion_factor values are not scalar.

Notes

Parameter merging:

When both status dict and **kwargs are provided:

updates = {}
updates.update(status)      # Apply dictionary parameters
updates.update(kwargs)      # Keyword arguments override dictionary

Validation order:

  1. Check for 'delay' (immediate error)

  2. Check for 'weight' (immediate error)

  3. Apply 'drift_factor' via set_drift_factor()

  4. Apply 'diffusion_factor' via set_diffusion_factor()

Validation errors in steps 3-4 prevent partial updates (atomic failure).

NEST compatibility:

The error messages exactly match NEST’s BadProperty exception strings, including the intentional typo in the weight error message.

Examples

Update via dictionary:

>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(drift_factor=1.0, diffusion_factor=1.0)
>>> conn.set_status({'drift_factor': 0.8, 'diffusion_factor': 0.3})
>>> assert conn.drift_factor == 0.8
>>> assert conn.diffusion_factor == 0.3

Update via keyword arguments:

>>> conn.set_status(drift_factor=1.2, diffusion_factor=0.5)
>>> assert conn.drift_factor == 1.2

Keyword arguments override dictionary:

>>> conn.set_status(
...     {'drift_factor': 0.5},  # Dictionary value
...     drift_factor=1.0,       # Keyword overrides to 1.0
... )
>>> assert conn.drift_factor == 1.0

Update single parameter:

>>> conn.set_status(drift_factor=2.0)  # Only drift changes
>>> # diffusion_factor remains unchanged

Attempting to set delay (error):

>>> try:
...     conn.set_status(delay=1.0)
... except ValueError as e:
...     print(e)
diffusion_connection has no delay

Attempting to set weight (error):

>>> try:
...     conn.set_status(weight=2.0)
... except ValueError as e:
...     print(e)
Please use the parameters drift_factor and diffusion_factor to specifiy the weights.
set_weight(_)[source]#

Reject attempts to set weight (NEST compatibility stub).

diffusion_connection does not support the standard weight parameter. This method exists solely for NEST API compatibility and always raises an error.

Parameters:

_ (Any) – Ignored. Any value triggers error.

Raises:

ValueError – Always raised with message: "Please use the parameters drift_factor and     diffusion_factor to specifiy the weights." Note: NEST’s original typo "specifiy" is preserved for exact compatibility.

Notes

Why this exists:

NEST’s connection API allows querying weight via GetStatus but forbids setting it via SetStatus for certain connection types. This asymmetric design prevents accidental misuse while maintaining uniform status dictionaries.

Examples

>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection()
>>> try:
...     conn.set_weight(2.0)
... except ValueError as e:
...     print(e)
Please use the parameters drift_factor and diffusion_factor to specifiy the weights.
to_siegert_event(coeff, delay_steps=1, multiplicity=1.0)[source]#

Create a single-step event payload for custom siegert_neuron handling.

Constructs a minimal event dictionary representing a single rate value scheduled for delivery at a specific future time step with optional multiplicity scaling. Useful for custom integration loops or non-standard event scheduling.

Parameters:
  • coeff (float, array-like, or Quantity) – Single rate coefficient value (typically Hz or dimensionless). Must be scalar. If saiunit.Quantity, mantissa is extracted.

  • delay_steps (int, array-like, or Quantity, optional) – Number of simulation time steps until event delivery. Must be scalar non-negative integer. Note: Unlike standard diffusion_connection semantics, this parameter exists for flexible custom scheduling. Default: 1 (deliver next step).

  • multiplicity (float, array-like, or Quantity, optional) – Event weight multiplier. Must be scalar. Scaled coefficient becomes coeff * multiplicity. Used for representing multiple simultaneous events or fractional event contributions. Default: 1.0 (no scaling).

Returns:

Event payload dictionary with keys:

  • 'coeff' : float — Single rate coefficient value

  • 'drift_factor' : float — Connection’s drift scaling factor

  • 'diffusion_factor' : float — Connection’s diffusion scaling factor

  • 'delay_steps' : int — Delivery time step offset

  • 'multiplicity' : float — Event multiplicity weight

Return type:

dict[str, Any]

Raises:

ValueError – If coeff, delay_steps, or multiplicity is not scalar.

Notes

Relationship to prepare_secondary_event():

Delay step semantics:

While diffusion_connection is nominally instantaneous (HAS_DELAY = False), this method accepts delay_steps for compatibility with event-driven simulation frameworks that require explicit delivery time specification.

Multiplicity interpretation:

The target neuron typically applies multiplicity as:

\[\begin{split}\Delta\mu &= m \cdot g_{\mu} \cdot c, \\ \Delta\sigma^2 &= m \cdot g_{\sigma} \cdot c,\end{split}\]

where \(m\) is multiplicity, \(g\) are connection factors, and \(c\) is the coefficient.

Use cases:

  • Custom event queues separate from WFR infrastructure

  • Hybrid simulations mixing time-driven and event-driven updates

  • Debugging individual event contributions

  • Implementing non-standard delivery schedules

Examples

Basic single-step event:

>>> import brainpy.state as bs
>>> conn = bs.diffusion_connection(drift_factor=0.8, diffusion_factor=0.3)
>>> event = conn.to_siegert_event(coeff=15.0, delay_steps=5, multiplicity=1.0)
>>> print(event)
{'coeff': 15.0, 'drift_factor': 0.8, 'diffusion_factor': 0.3,
 'delay_steps': 5, 'multiplicity': 1.0}

Immediate delivery (zero delay):

>>> immediate_event = conn.to_siegert_event(coeff=20.0, delay_steps=0)
>>> print(immediate_event['delay_steps'])
0

Fractional multiplicity (population averaging):

>>> # 100-neuron population, 37 neurons fire this step
>>> # Average rate contribution per neuron
>>> avg_event = conn.to_siegert_event(coeff=42.0, multiplicity=37.0/100.0)
>>> print(avg_event['multiplicity'])
0.37

Custom event queue integration:

>>> # Build event queue for future delivery
>>> event_queue = []
>>> for t in range(10):
...     rate_value = 10.0 + 2.0 * t  # Ramping rate
...     evt = conn.to_siegert_event(coeff=rate_value, delay_steps=t)
...     event_queue.append(evt)
>>>
>>> # Process queue in simulation loop
>>> for step in range(10):
...     due_events = [e for e in event_queue if e['delay_steps'] == step]
...     for evt in due_events:
...         # Deliver to target neuron
...         pass

Comparison with multi-lag event:

>>> import numpy as np
>>> # Multi-lag WFR event
>>> multi_lag = conn.prepare_secondary_event(np.array([10.0, 12.0, 11.5]))
>>> print(multi_lag['coeffarray'])
[10.  12.  11.5]
>>>
>>> # Equivalent single-step events
>>> single_events = [
...     conn.to_siegert_event(coeff=10.0, delay_steps=0),
...     conn.to_siegert_event(coeff=12.0, delay_steps=1),
...     conn.to_siegert_event(coeff=11.5, delay_steps=2),
... ]