Customizing synaptic parameters#
In this tutorial, you will learn how to:
use the
select()method to fully customize network simulations withJaxley.use the
copy_node_property_to_edges()method to flexibly modify synapses.
Here is a code snippet which you will learn to understand in this tutorial:
net = ... # See tutorial on Basics of Jaxley.
# Set synaptic conductance of the synapse with index 0 and 1.
net.select(edges=[0, 1]).set("Ionotropic_gS", 0.1)
# Set synaptic conductance of all synapses that have cells 3 or 4 as presynaptic neuron.
net.copy_node_property_to_edges("global_cell_index")
df = net.edges
df = df.query("pre_global_cell_index in [3, 4]")
net.select(edges=df.index).set("Ionotropic_gS", 0.2)
# Set synaptic conductance of all synapses that
# 1) have cells 2 or 3 as presynaptic neuron and
# 2) has cell 5 as postsynaptic neuron
df = net.edges
df = df.query("pre_global_cell_index in [2, 3]")
df = df.query("post_global_cell_index == 5")
net.select(edges=df.index).set("Ionotropic_gS", 0.3)
In a previous tutorial you learned how to set parameters of a jx.Network. In that tutorial, we briefly mentioned the select() method which allowed to set individual synapses to particular values. In this tutorial, we will go into detail in how you can fully customize your Jaxley simulation.
Let’s go!
import jaxley as jx
from jaxley.channels import Na, K, Leak
from jaxley.connect import fully_connect
from jaxley.synapses import IonotropicSynapse
Preface: Building the network#
We first build a network consisting of six neurons, in the same way as we showed in the previous tutorials:
dt = 0.025
t_max = 10.0
comp = jx.Compartment()
branch = jx.Branch(comp, ncomp=2)
cell = jx.Cell(branch, parents=[-1, 0])
net = jx.Network([cell for _ in range(6)])
fully_connect(net.cell([0, 1, 2]), net.cell([3, 4, 5]), IonotropicSynapse())
Setting individual synapse parameters#
As always, you can use the .edges table to inspect synaptic parameters of the network:
net.edges
| global_edge_index | pre_index | post_index | type | type_ind | pre_locs | post_locs | IonotropicSynapse_gS | IonotropicSynapse_e_syn | IonotropicSynapse_k_minus | IonotropicSynapse_v_th | IonotropicSynapse_delta | IonotropicSynapse_s | controlled_by_param | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 15 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
| 1 | 1 | 0 | 20 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
| 2 | 2 | 0 | 25 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
| 3 | 3 | 5 | 15 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
| 4 | 4 | 5 | 20 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
| 5 | 5 | 5 | 25 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
| 6 | 6 | 10 | 15 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
| 7 | 7 | 10 | 20 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
| 8 | 8 | 10 | 25 | IonotropicSynapse | 0 | 0.25 | 0.25 | 0.0001 | 0.0 | 0.025 | -35.0 | 10.0 | 0.2 | 0 |
This table has nine rows, each corresponding to one synapse. This makes sense because we fully connected three neurons (0, 1, 2) to three other neurons (3, 4, 5), giving a total of 3x3=9 synapses.
You can modify parameters of individual synapses as follows:
net.select(edges=[3, 4, 5]).set("IonotropicSynapse_gS", 0.2)
Above, we are modifying the synapses with indices [3, 4, 5] (i.e., the indices of the net.edges DataFrame). The resulting values are indeed changed:
net.edges.IonotropicSynapse_gS
0 0.0001
1 0.0001
2 0.0001
3 0.2000
4 0.2000
5 0.2000
6 0.0001
7 0.0001
8 0.0001
Name: IonotropicSynapse_gS, dtype: float64
Example 1: Setting synaptic parameters which connect particular neurons#
This is great, but setting synaptic parameters just by their index can be exhausting, in particular in very large networks. Instead, we would want to, for example, set the maximal conductance of all synapses that connect from cell 0 or 1 to any other neuron.
In Jaxley, such customization can be achieved by filtering the .edges dataframe accordingly, as shown below:
net = jx.Network([cell for _ in range(6)])
fully_connect(net.cell([0, 1, 2]), net.cell([3, 4, 5]), IonotropicSynapse())
net.copy_node_property_to_edges("global_cell_index")
df = net.edges
df = df.query("pre_global_cell_index in [0, 1]")
net.select(edges=df.index).set("IonotropicSynapse_gS", 0.23)
net.edges.IonotropicSynapse_gS
0 0.2300
1 0.2300
2 0.2300
3 0.2300
4 0.2300
5 0.2300
6 0.0001
7 0.0001
8 0.0001
Name: IonotropicSynapse_gS, dtype: float64
Indeed, the first six synapses now have the value 0.23! Let’s look at the individual lines to understand how this worked:
We want to set parameter by cell index. However, by default, the pre- or post-synaptic cell-indices are not listed in net.edges. We can add the cell index to the .edges dataframe by calling .copy_node_property_to_edges():
net.copy_node_property_to_edges("global_cell_index")
After this, the pre- and post-synaptic cell indices are listed in net.edges as pre_global_cell_index and post_global_cell_index.
Next, we take .edges, which is a pandas DataFrame:
df = net.edges
We then modify this DataFrame to only contain those rows where the global cell index is in 0 or 1:
df = df.query("pre_global_cell_index in [0, 1]")
For the above step, you use any column of the DataFrame to filter it (you can see all columns with df.columns). Note that, while we used .query() here, you can really filter the pandas DataFrame however you want. For example, the query above is identical to df = df[df["pre_global_cell_index"].isin([0, 1])].
Finally, we use the .select() method, which returns a subset of the Network at the specified indices. This subset of the network can be modified with .set():
net.select(edges=df.index).set("IonotropicSynapse_gS", 0.23)
Example 2: Setting parameters given pre- and post-synaptic cell indices#
Say you want to select all synapses that have cells 1 or 2 as presynaptic neuron and cell 4 or 5 as postsynaptic neuron.
net = jx.Network([cell for _ in range(6)])
fully_connect(net.cell([0, 1, 2]), net.cell([3, 4, 5]), IonotropicSynapse())
Just like before, we can simply use .query() as already shown above. However, this time, call .query() to twice to filter by pre- and post-synaptic cell indices:
net.copy_node_property_to_edges("global_cell_index")
df = net.edges
df = df.query("pre_global_cell_index in [1, 2]")
df = df.query("post_global_cell_index in [4, 5]")
net.select(edges=df.index).set("IonotropicSynapse_gS", 0.3)
net.edges.IonotropicSynapse_gS
0 0.0001
1 0.0001
2 0.0001
3 0.0001
4 0.3000
5 0.3000
6 0.0001
7 0.3000
8 0.3000
Name: IonotropicSynapse_gS, dtype: float64
Example 3: Applying this strategy to cell level parameters#
You had previously seen that you can modify parameters with, e.g., net.cell(0).set(...). However, if you need more flexibility than this, you can also use the above strategy to modify cell-level parameters:
net = jx.Network([cell for _ in range(6)])
fully_connect(net.cell([0, 1, 2]), net.cell([3, 4, 5]), IonotropicSynapse())
df = net.nodes
df = df.query("global_cell_index in [0, 1]")
net.select(nodes=df.index).set("radius", 0.1)
Example 4: Flexibly setting parameters based on their groups#
If you are using groups, as shown in this tutorial, then you can also use this for querying synapses. To demonstrate this, let’s create a group of excitatory neurons (e.g., cells 0, 3, 5):
# Redefine network.
net = jx.Network([cell for _ in range(6)])
fully_connect(net.cell([0, 1, 2]), net.cell([3, 4, 5]), IonotropicSynapse())
net.cell([0, 3, 5]).add_to_group("exc")
Now, say we want all synapses that start from these excitatory neurons. You can do this as follows:
# First, we have to identify which cells are in the `exc` group.
indices_of_excitatory_cells = net.exc.nodes["global_cell_index"].unique().tolist() # [0, 3, 5]
# Then we can proceed as before:
net.copy_node_property_to_edges("global_cell_index")
df = net.edges
df = df.query(f"pre_global_cell_index in {indices_of_excitatory_cells}")
net.select(edges=df.index).set("IonotropicSynapse_gS", 0.4)
Example 5: Setting synaptic parameters based on properties of the presynaptic cell#
Let’s discuss one more example: Imagine we only want to modify those synapses whose presynaptic compartment has a sodium channel. Let’s first add a sodium channel to some of the cells:
net = jx.Network([cell for _ in range(6)])
fully_connect(net.cell([0, 1, 2]), net.cell([3, 4, 5]), IonotropicSynapse())
net.cell(0).branch(0).comp(0).insert(Na())
net.cell(2).branch(1).comp(1).insert(Na())
Now, let us query which cells have the desired synapses:
net.copy_node_property_to_edges("global_comp_index")
df = net.nodes
df = df.query("Na")
indices_of_sodium_compartments = df["global_comp_index"].unique().tolist()
indices_of_sodium_compartments lists all compartments which contained sodium:
print(indices_of_sodium_compartments)
[0, 11]
Then, we can proceed as always and filter for the global pre-synaptic compartment index:
df = net.edges
df = df.query(f"pre_global_comp_index in {indices_of_sodium_compartments}")
net.select(edges=df.index).set("IonotropicSynapse_gS", 0.6)
net.edges.IonotropicSynapse_gS
0 0.6000
1 0.6000
2 0.6000
3 0.0001
4 0.0001
5 0.0001
6 0.0001
7 0.0001
8 0.0001
Name: IonotropicSynapse_gS, dtype: float64
Indeed, only synapses coming from the first neuron were modified (as its presynaptic compartment contained sodium), in contrast to synapses from neuron 2 (whose presynaptic compartment did not).
Summary#
In this tutorial, you learned how to fully customize your Jaxley simulation. This works by querying rows from the .edges DataFrame.