Note
Go to the end to download the full example code.
Random balanced network with astrocytes with fixed-indegree connectivity¶
Run this example as a Jupyter notebook:
See our guide for more information and troubleshooting.
This script simulates a random balanced network with excitatory and inhibitory
neurons and astrocytes. The astrocytes are modeled with astrocyte_lr_1994
,
implemented according to [1], [2], and [3]. The neurons are modeled with
aeif_cond_alpha_astro
, an adaptive exponential integrate-and-fire neuron
supporting neuron-astrocyte interactions.
The simulation results show how astrocytes affect neuronal excitability. The figures displayed at the end of the simulation show the astrocytic dynamics, the slow inward current induced by the astrocytes in the postsynaptic neurons, and a raster plot of neuronal firings, respectively.
In this version of the model, primary connections between populations are created with the fixed-indegree rule.
References¶
See Also¶
A small neuron-astrocyte network, Random balanced network with astrocytes with Bernoulli connectivity
Import all necessary modules for simulation and plotting.
import random
import matplotlib.pyplot as plt
import nest
import numpy as np
Set simulation parameters.
sim_params = {
"dt": 0.1, # simulation resolution in ms
"sim_time": 1000.0, # simulation time in ms
"N_rec_spk": 100, # number of neurons to record from with spike recorder
"N_rec_mm": 50, # number of nodes (neurons, astrocytes) to record from with multimeter
"n_vp": 4, # number of virtual processes for NEST
"seed": 100, # seed for the random module
}
Set network parameters.
network_params = {
"N_ex": 8000, # number of excitatory neurons
"N_in": 2000, # number of inhibitory neurons
"N_astro": 10000, # number of astrocytes
"CE": 800, # number of incoming excitatory connections per neuron
"CI": 200, # number of incoming inhbitory connections per neuron
"p_third_if_primary": 0.5, # probability of each created neuron-neuron connection to be paired with one astrocyte
"pool_size": 10, # astrocyte pool size for each target neuron
"pool_type": "random", # astrocyte pool will be chosen randomly for each target neuron
"poisson_rate": 2000, # Poisson input rate for neurons
}
syn_params = {
"w_a2n": 0.05, # weight of astrocyte-to-neuron connection
"w_e": 1.0, # weight of excitatory connection in nS
"w_i": -4.0, # weight of inhibitory connection in nS
"d_e": 2.0, # delay of excitatory connection in ms
"d_i": 1.0, # delay of inhibitory connection in ms
}
Set astrocyte parameters.
astrocyte_model = "astrocyte_lr_1994"
astrocyte_params = {
"IP3": 0.4, # IP3 initial value in µM
}
Set neuron parameters.
neuron_model = "aeif_cond_alpha_astro"
tau_syn_ex = 2.0
tau_syn_in = 4.0
neuron_params_ex = {
"tau_syn_ex": tau_syn_ex, # excitatory synaptic time constant in ms
"tau_syn_in": tau_syn_in, # inhibitory synaptic time constant in ms
}
neuron_params_in = {
"tau_syn_ex": tau_syn_ex, # excitatory synaptic time constant in ms
"tau_syn_in": tau_syn_in, # inhibitory synaptic time constant in ms
}
This function creates the nodes and build the network. The astrocytes only
respond to excitatory synaptic inputs; therefore, only the excitatory
neuron-neuron connections are paired with the astrocytes. The
TripartiteConnect()
function and the tripartite_bernoulli_with_pool
rule
are used to create the connectivity of the network.
def create_astro_network(scale=1.0):
"""Create nodes for a neuron-astrocyte network.
Nodes in a neuron-astrocyte network are created according to the give scale
of the model. The nodes created include excitatory and inhibitory neurons,
astrocytes, and a Poisson generator.
Parameters
---------
scale
Scale of the model.
Return values
-------------
Created nodes and Poisson generator.
"""
print("Creating nodes ...")
assert scale >= 1.0, "scale must be >= 1.0"
nodes_ex = nest.Create(neuron_model, int(network_params["N_ex"] * scale), params=neuron_params_ex)
nodes_in = nest.Create(neuron_model, int(network_params["N_in"] * scale), params=neuron_params_in)
nodes_astro = nest.Create(astrocyte_model, int(network_params["N_astro"] * scale), params=astrocyte_params)
nodes_noise = nest.Create("poisson_generator", params={"rate": network_params["poisson_rate"]})
return nodes_ex, nodes_in, nodes_astro, nodes_noise
def connect_astro_network(nodes_ex, nodes_in, nodes_astro, nodes_noise):
"""Connect the nodes in a neuron-astrocyte network.
Nodes in a neuron-astrocyte network are connected. The indegree of neurons
is not changed with network scale to preserve the expected number of connections
for each node (consistent with the corresponding bernoulli example).
The astrocytes are paired with excitatory connections only.
Parameters
---------
nodes_ex
Nodes of excitatory neurons.
nodes_in
Nodes of inhibitory neurons.
nodes_astro
Nodes of astrocytes.
node_noise
Poisson generator.
"""
print("Connecting Poisson generator ...")
nest.Connect(nodes_noise, nodes_ex + nodes_in, syn_spec={"weight": syn_params["w_e"]})
print("Connecting neurons and astrocytes ...")
# excitatory connections are paired with astrocytes
# conn_spec and syn_spec according to the "tripartite_bernoulli_with_pool" rule
conn_params_e = {"rule": "fixed_indegree", "indegree": network_params["CE"]}
conn_params_astro = {
"rule": "third_factor_bernoulli_with_pool",
"p": network_params["p_third_if_primary"],
"pool_size": network_params["pool_size"],
"pool_type": network_params["pool_type"],
}
syn_params_e = {
"primary": {
"synapse_model": "tsodyks_synapse",
"weight": syn_params["w_e"],
"tau_psc": tau_syn_ex,
"delay": syn_params["d_e"],
},
"third_in": {
"synapse_model": "tsodyks_synapse",
"weight": syn_params["w_e"],
"tau_psc": tau_syn_ex,
"delay": syn_params["d_e"],
},
"third_out": {"synapse_model": "sic_connection", "weight": syn_params["w_a2n"]},
}
nest.TripartiteConnect(
nodes_ex,
nodes_ex + nodes_in,
nodes_astro,
conn_spec=conn_params_e,
third_factor_conn_spec=conn_params_astro,
syn_specs=syn_params_e,
)
# inhibitory connections are not paired with astrocytes
conn_params_i = {"rule": "fixed_indegree", "indegree": network_params["CI"]}
syn_params_i = {
"synapse_model": "tsodyks_synapse",
"weight": syn_params["w_i"],
"tau_psc": tau_syn_in,
"delay": syn_params["d_i"],
}
nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_i, syn_params_i)
This function plots the dynamics in the astrocytes and their resultant output to the neurons. The IP3 and calcium in the astrocytes and the SIC in neurons are plotted. Means and standard deviations across sampled nodes are indicated by lines and shaded areas, respectively.
def plot_dynamics(astro_data, neuron_data, start):
"""Plot the dynamics in neurons and astrocytes.
The dynamics in the given neuron and astrocyte nodes are plotted. The
dynamics include IP3 and calcium in the astrocytes, and the SIC input to
the postsynaptic neurons.
Parameters
---------
astro_data
Data of IP3 and calcium dynamics in the astrocytes.
neuron_data
Data of SIC input to the neurons.
start
Start time of the plotted dynamics.
"""
print("Plotting dynamics ...")
# astrocyte data
astro_mask = astro_data["times"] > start
astro_ip3 = astro_data["IP3"][astro_mask]
astro_cal = astro_data["Ca_astro"][astro_mask]
astro_times = astro_data["times"][astro_mask]
astro_times_set = list(set(astro_times))
ip3_means = np.array([np.mean(astro_ip3[astro_times == t]) for t in astro_times_set])
ip3_sds = np.array([np.std(astro_ip3[astro_times == t]) for t in astro_times_set])
cal_means = np.array([np.mean(astro_cal[astro_times == t]) for t in astro_times_set])
cal_sds = np.array([np.std(astro_cal[astro_times == t]) for t in astro_times_set])
# neuron data
neuron_mask = neuron_data["times"] > start
neuron_sic = neuron_data["I_SIC"][neuron_mask]
neuron_times = neuron_data["times"][neuron_mask]
neuron_times_set = list(set(neuron_times))
sic_means = np.array([np.mean(neuron_sic[neuron_times == t]) for t in neuron_times_set])
sic_sds = np.array([np.std(neuron_sic[neuron_times == t]) for t in neuron_times_set])
# set plots
fig, axes = plt.subplots(2, 1, sharex=True)
color_ip3 = "tab:blue"
color_cal = "tab:green"
color_sic = "tab:purple"
# astrocyte plot
axes[0].set_title(f"{r'IP$_{3}$'} and {r'Ca$^{2+}$'} in astrocytes (n={len(set(astro_data['senders']))})")
axes[0].set_ylabel(r"IP$_{3}$ ($\mu$M)")
axes[0].tick_params(axis="y", labelcolor=color_ip3)
axes[0].fill_between(
astro_times_set, ip3_means + ip3_sds, ip3_means - ip3_sds, alpha=0.3, linewidth=0.0, color=color_ip3
)
axes[0].plot(astro_times_set, ip3_means, linewidth=2, color=color_ip3)
ax = axes[0].twinx()
ax.set_ylabel(r"Ca$^{2+}$ ($\mu$M)")
ax.tick_params(axis="y", labelcolor=color_cal)
ax.fill_between(
astro_times_set, cal_means + cal_sds, cal_means - cal_sds, alpha=0.3, linewidth=0.0, color=color_cal
)
ax.plot(astro_times_set, cal_means, linewidth=2, color=color_cal)
# neuron plot
axes[1].set_title(f"SIC in neurons (n={len(set(neuron_data['senders']))})")
axes[1].set_ylabel("SIC (pA)")
axes[1].set_xlabel("Time (ms)")
axes[1].fill_between(
neuron_times_set, sic_means + sic_sds, sic_means - sic_sds, alpha=0.3, linewidth=0.0, color=color_sic
)
axes[1].plot(neuron_times_set, sic_means, linewidth=2, color=color_sic)
This is the main function for simulation. The network is created and the neurons and astrocytes are randomly chosen for recording. After simulation, recorded data of neurons and astrocytes are plotted.
def run_simulation():
"""Run simulation of a neuron-astrocyte network."""
# NEST configuration
nest.ResetKernel()
nest.resolution = sim_params["dt"]
nest.local_num_threads = sim_params["n_threads"]
nest.print_time = True
nest.overwrite_files = True
# use random seed for reproducible sampling
random.seed(sim_params["seed"])
# simulation settings
sim_time = sim_params["sim_time"]
# create and connect nodes
exc, inh, astro, noise = create_astro_network()
connect_astro_network(exc, inh, astro, noise)
# create and connect recorders (multimeter default resolution = 1 ms)
sr_neuron = nest.Create("spike_recorder")
mm_neuron = nest.Create("multimeter", params={"record_from": ["I_SIC"]})
mm_astro = nest.Create("multimeter", params={"record_from": ["IP3", "Ca_astro"]})
# select nodes randomly and connect them with recorders
print("Connecting recorders ...")
neuron_list = (exc + inh).tolist()
astro_list = astro.tolist()
n_neuron_rec_spk = min(len(neuron_list), sim_params["N_rec_spk"])
n_neuron_rec_mm = min(len(neuron_list), sim_params["N_rec_mm"])
n_astro_rec = min(len(astro), sim_params["N_rec_mm"])
neuron_list_for_sr = neuron_list[: min(len(neuron_list), n_neuron_rec_spk)]
neuron_list_for_mm = sorted(random.sample(neuron_list, n_neuron_rec_mm))
astro_list_for_mm = sorted(random.sample(astro_list, n_astro_rec))
nest.Connect(neuron_list_for_sr, sr_neuron)
nest.Connect(mm_neuron, neuron_list_for_mm)
nest.Connect(mm_astro, astro_list_for_mm)
# run simulation
print("Running simulation ...")
nest.Simulate(sim_time)
# read out recordings
neuron_data = mm_neuron.events
astro_data = mm_astro.events
# make raster plot
nest.raster_plot.from_device(
sr_neuron, hist=True, title=f"Raster plot of neuron {neuron_list_for_sr[0]} to {neuron_list_for_sr[-1]}"
)
# plot dynamics in astrocytes and neurons
plot_dynamics(astro_data, neuron_data, 0.0)
# show plots
plt.show()
Run simulation.
run_simulation()