flowchart LR
Pre["Pre-synaptic<br/>Neuron"]
Dep["Depression<br/>Synapse"]
Fac["Facilitation<br/>Synapse"]
TM["Tsodyks-Markram<br/>Synapse"]
Post["Post-synaptic<br/>Neuron"]
Pre -->|"r_out"| Dep
Pre -->|"r_out"| Fac
Pre -->|"r_out"| TM
Dep -->|"r_eff"| Post
Fac -->|"r_eff"| Post
TM -->|"r_eff"| Post
Heterogeneous Edges
Part of the running example, where stage 5 adds a connectome and a coupling function.
An edge can carry its own dynamics. Instead of a static weight, the connection between two nodes passes through a relay node with its own equations, here a short-term–plasticity synapse (depression, facilitation, Tsodyks–Markram). A pre-synaptic node drives three parallel synapse nodes, each filtering the signal differently before it reaches the post-synaptic node.
A relay node is just another node on the same graph: the synapse is a node, peer to the neurons it connects. This is heterogeneity on one scale, not multi-scale nesting: nothing here contains a finer network with its own node-space or clock.
A relay’s transmitted signal is input-dependent: r_eff = r_in · x · u reads what the synapse receives, not just its own state. PyRates routes such algebraic edge outputs natively, so this example runs on PyRates.
The tvboptim (JAX) engine behind heterogeneous nodes transports signals derived from a node’s own state; input-dependent relay outputs are a deferred milestone there. To model short-term plasticity on tvboptim today, put the synaptic state (x, u) in the receiving node and drive it through ordinary coupling.
from tvbo import Dynamics
# Tsodyks-Markram short-term plasticity model
# Combines depression (x) and facilitation (u)
# r_eff is an input-dependent output (r_in * x * u); the PyRates backend routes it
tsodyks = Dynamics.from_string("""
name: TsodyksMarkram
description: "Short-term synaptic plasticity with depression and facilitation (canonical Tsodyks-Markram)"
parameters:
tau_D:
value: 200.0
description: "Depression recovery time constant (ms)"
tau_F:
value: 600.0
description: "Facilitation time constant (ms)"
U0:
value: 0.2
description: "Baseline release probability"
state_variables:
x:
equation:
rhs: "(1 - x)/tau_D - u*x*r_in"
initial_value: 1.0
description: "Available synaptic resources (depression variable)"
u:
equation:
rhs: "(U0 - u)/tau_F + U0*(1 - u)*r_in"
initial_value: 0.2
description: "Release probability (facilitation variable)"
coupling_inputs:
r_in: {}
derived_variables:
r_eff:
equation:
rhs: "r_in*x*u"
description: "Effective synaptic transmission (release probability x resources x rate)"
output:
- r_eff
""")
# Pure synaptic depression model
depression = Dynamics.from_string("""
name: Depression
description: "Short-term synaptic depression only (fixed release probability)"
parameters:
tau_D:
value: 300.0
description: "Depression recovery time constant (ms)"
U:
value: 0.5
description: "Release probability (fixed)"
state_variables:
x:
equation:
rhs: "(1 - x)/tau_D - U*x*r_in"
initial_value: 1.0
description: "Available synaptic resources"
coupling_inputs:
r_in: {}
derived_variables:
r_eff:
equation:
rhs: "U*r_in*x"
description: "Effective transmission with depression"
output:
- r_eff
""")
# Pure synaptic facilitation model
facilitation = Dynamics.from_string("""
name: Facilitation
description: "Short-term synaptic facilitation only (full resources)"
parameters:
tau_F:
value: 500.0
description: "Facilitation time constant (ms)"
U0:
value: 0.2
description: "Baseline release probability"
state_variables:
u:
equation:
rhs: "(U0 - u)/tau_F + U0*(1 - u)*r_in"
initial_value: 0.2
description: "Release probability (facilitation variable)"
coupling_inputs:
r_in: {}
derived_variables:
r_eff:
equation:
rhs: "r_in*u"
description: "Effective transmission with facilitation"
output:
- r_eff
""")
# Simple rate neuron (I_ext=0.0, will receive external stimulus)
rate_neuron = Dynamics.from_string("""
name: RateNeuron
description: "Simple rate-based neuron with external input"
parameters:
tau:
value: 10.0
I_ext:
value: 0.0
state_variables:
r:
equation:
rhs: "(-r + I_ext + r_in)/tau"
initial_value: 0.0
coupling_inputs:
r_in: {}
""")
print(f"Created plasticity models: {tsodyks.name}, {depression.name}, {facilitation.name}")Created plasticity models: TsodyksMarkram, Depression, Facilitation
import yaml
from tvbo import SimulationExperiment
from tvbo import Network
# Network: Pre-synaptic neuron → three parallel synaptic pathways → Post-synaptic neuron
# This allows direct comparison of how different plasticity affects the SAME post-synaptic target
network_yaml = """
label: SynapticPlasticityComparison
number_of_nodes: 5
nodes:
- id: 0
label: PreSynaptic
dynamics: RateNeuron
position:
x: 0.0
y: 0.5
z: 0
- id: 1
label: DepressionSynapse
dynamics: Depression
position:
x: 0.5
y: 0.8
z: 0
- id: 2
label: FacilitationSynapse
dynamics: Facilitation
position:
x: 0.5
y: 0.5
z: 0
- id: 3
label: TsodyksSynapse
dynamics: TsodyksMarkram
position:
x: 0.5
y: 0.2
z: 0
- id: 4
label: PostSynaptic
dynamics: RateNeuron
position:
x: 1.0
y: 0.5
z: 0
edges:
# Pre-synaptic drives all three synaptic relays
- source: 0
target: 1
parameters:
weight:
value: 1.0
source_var: r_out
target_var: r_in
- source: 0
target: 2
parameters:
weight:
value: 1.0
source_var: r_out
target_var: r_in
- source: 0
target: 3
parameters:
weight:
value: 1.0
source_var: r_out
target_var: r_in
# All synapses converge onto the post-synaptic neuron
- source: 1
target: 4
parameters:
weight:
value: 0.33
source_var: r_eff
target_var: r_in
- source: 2
target: 4
parameters:
weight:
value: 0.33
source_var: r_eff
target_var: r_in
- source: 3
target: 4
parameters:
weight:
value: 0.33
source_var: r_eff
target_var: r_in
"""
# Parse network and create experiment with all components
network_plasticity = Network.from_string(network_yaml)
network_plasticity.plot_graph(node_size=150, edge_cmap="bwr", fontsize=13)
Run Simulation with Pulsed Stimulus
We create a pulsed input pattern that drives the pre-synaptic neuron. This matches Use Case 2 from the PyRates tutorial:
import numpy as np
# Create time array and pulsed input signal
duration = 1000.0
step_size = 0.1
time = np.arange(0, duration, step_size)
# Pulse train with longer recovery periods to show plasticity effects
# Two bursts of rapid pulses separated by a long gap
pulse_times = [
50,
80,
110,
140,
170, # First burst (rapid)
500,
530,
560,
590,
620,
] # Second burst after recovery
input_signal = np.zeros_like(time)
for pt in pulse_times:
mask = (time >= pt) & (time < pt + 15)
input_signal[mask] = 5.0
network_plasticity.dynamics.update(
{
"RateNeuron": rate_neuron,
"Depression": depression,
"Facilitation": facilitation,
"TsodyksMarkram": tsodyks,
}
),
# Create experiment
exp_plasticity = SimulationExperiment(
network=network_plasticity,
)
exp_plasticity.integration.duration = duration
exp_plasticity.integration.step_size = step_size
# Run with pulsed input to PreSynaptic neuron
pyrates_inputs = {"PreSynaptic/RateNeuron_op/I_ext": input_signal}
res = exp_plasticity.run("pyrates", inputs=pyrates_inputs)
print(res)* Owlready2 * Warning: ignoring cyclic subclass of/subproperty of, involving:
http://uri.interlex.org/tgbugs/uris/readable/atlas/Space
Compilation Progress
--------------------
(1) Translating the circuit template into a networkx graph representation...
...finished.
(2) Preprocessing edge transmission operations...
...finished.
(3) Parsing the model equations into a compute graph...
...finished.
Model compilation was finished.
Simulation Progress
-------------------
(1) Generating the network run function...
(2) Processing output variables...
...finished.
(3) Running the simulation...
...finished after 0.22746691689826548s.
Experiment
└── integration
data: (10000, 4, 5)
Visualize Results
import matplotlib.pyplot as plt
fig, axes = plt.subplots(4, 1, figsize=(10, 10), sharex=True)
t = res.time
# Row 1: Pulsed input stimulus
ax = axes[0]
ax.plot(t, input_signal, 'k', lw=2)
ax.set_ylabel("Input\nStimulus")
ax.set_title("Short-Term Synaptic Plasticity: Pre → Synapses → Post")
ax.set_ylim(-0.5, 6)
# Row 2: Pre-synaptic activity (state variable: r)
ax = axes[1]
pre_r = res.get_region('PreSynaptic').get_state_variable('r')
ax.plot(t, pre_r.data.squeeze(), 'k', lw=2, label="Pre-synaptic Rate")
ax.set_ylabel("Pre-synaptic\nRate")
ax.legend(loc='upper right')
# Row 3: Synaptic variables
ax = axes[2]
dep_x = res.get_region('DepressionSynapse').get_state_variable('x')
fac_u = res.get_region('FacilitationSynapse').get_state_variable('u')
tso_x = res.get_region('TsodyksSynapse').get_state_variable('x')
tso_u = res.get_region('TsodyksSynapse').get_state_variable('u')
ax.plot(t, dep_x.data.squeeze(), 'C0', lw=2, label="Depression (x)")
ax.plot(t, fac_u.data.squeeze(), 'C1', lw=2, label="Facilitation (u)")
ax.plot(t, tso_x.data.squeeze(), 'C2', lw=2, label="Tsodyks x")
ax.plot(t, tso_u.data.squeeze(), 'C3', lw=2, label="Tsodyks u")
ax.set_ylabel("Synaptic\nVariables")
ax.legend(loc='right')
ax.axhline(1.0, color='gray', ls=':', alpha=0.5)
# Row 4: Post-synaptic response (state variable: r)
ax = axes[3]
post_r = res.get_region('PostSynaptic').get_state_variable('r')
ax.plot(t, post_r.data.squeeze(), 'C4', lw=2, label="Post-synaptic Rate")
ax.set_ylabel("Post-synaptic\nRate")
ax.set_xlabel("Time (ms)")
ax.legend(loc='upper right')
plt.tight_layout()
plt.show()/var/folders/ym/9kw1g21j1nd7kwfn8c0z3st40000gn/T/ipykernel_71632/4283454728.py:43: UserWarning: Glyph 8594 (\N{RIGHTWARDS ARROW}) missing from font(s) Helvetica.
plt.tight_layout()
/Users/leonmartin_bih/tools/tvbo/.venv/lib/python3.12/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 8594 (\N{RIGHTWARDS ARROW}) missing from font(s) Helvetica.
fig.canvas.print_figure(bytes_io, **kw)
