Example 4: Vectorized Populations (E/I Network)
Let's scale up to population-level models. Instead of building thousands of individual scalar systems, we can use the Vectorized topology. A single GenericChannel with a vectorized topology expands to N elements. We wire dense excitatory/inhibitory populations together using a weight matrix and the build_synapse_block helper.
The main benefit of the Vectorized topology is that it saves compile time. Compiling 1,000 scalar systems requires MTK to process 1,000 separate symbolic graphs. Using Vectorized(1000) collapses the entire population into a single system that uses array operations.
An annoyance is the need to build both scalar and vector definitions for the components. It would be nice (and maybe exists?) for MTK to have some automatic way of turning a scalar component into a vectorised one? I spent some time attempting this with postwalk, and got it running. But it was messy and dependeng on the MTK internal API so decided to demand a separate set of equations for a vectorised component.
using MTKNeuralToolkit
using ModelingToolkit: mtkcompile, @named
using OrdinaryDiffEq
using Plots1. Define Shared Gating Dynamics
These functions use broadcasting (.) so they work identically for both scalar and vectorized topologies.
hh_na_m = v -> (
0.182 .* (v .+ 35.0) ./ (1.0 .- exp.(-(v .+ 35.0) ./ 9.0)),
-0.124 .* (v .+ 35.0) ./ (1.0 .- exp.((v .+ 35.0) ./ 9.0))
)
hh_na_h = v -> (
0.25 .* exp.(-(v .+ 90.0) ./ 12.0),
0.25 .* (exp.((v .+ 62.0) ./ 6.0)) ./ exp.(-(v .+ 90.0) ./ 12.0)
)
sodium_gates = [GateSpec(:m, 3, 0.0, hh_na_m), GateSpec(:h, 1, 0.0, hh_na_h)]
hh_k_n = v -> (
0.02 .* (v .- 25.0) ./ (1.0 .- exp.(-(v .- 25.0) ./ 9.0)),
-0.002 .* (v .- 25.0) ./ (1.0 .- exp.((v .- 25.0) ./ 9.0))
)
potassium_gates = [GateSpec(:n, 4, 0.0, hh_k_n)]1-element Vector{GateSpec{Int64, Float64, Main.var"#8#9"}}:
GateSpec{Int64, Float64, Main.var"#8#9"}(:n, 4, 0.0, Main.var"#8#9"())2. Define Topologies and Build Populations
N_E = 15
N_I = 5
top_E = Vectorized(N_E)
top_I = Vectorized(N_I)
function build_population(name::Symbol, top)
#Let's make the sodium conductance heterogeneous across the population
gNa_heterogeneous = collect(range(119.0, 121.0, length=top.N))
@named cap = Capacitor(topology=top, C=1.0)
@named na = GenericChannel(topology=top, g=gNa_heterogeneous, E_rev=50.0, gates=sodium_gates)
@named k = GenericChannel(topology=top, g=36.0, E_rev=-77.0, gates=potassium_gates)
@named leak= GenericChannel(topology=top, g=0.3, E_rev=-54.4, gates=GateSpec[])
return build_compartment(cap, [na, k, leak]; name=name, V_init=-65.0, topology=top)
end
pop_E = build_population(:pop_E, top_E)
pop_I = build_population(:pop_I, top_I)Compartment{NoMorphology, NoGeometry, Float64}(Model pop_I:
Subsystems (8): see hierarchy(pop_I)
cap
injector
syn_injector
p
⋮
Equations (76):
60 standard: see equations(pop_I)
16 connecting: see equations(expand_connections(pop_I))
Unknowns (51): see unknowns(pop_I)
cap₊v(t)
cap₊i(t)
cap₊p₊v(t)
cap₊p₊i(t)
⋮
Parameters (7): see parameters(pop_I)
cap₊C
na₊g
na₊E_rev
k₊g
⋮, (V = (pop_I₊cap₊v(t))[1:5], p_pin = Model pop_I₊p:
Equations (2):
2 connecting: see equations(expand_connections(pop_I₊p))
Unknowns (2): see unknowns(pop_I₊p)
v(t)
i(t), n_pin = Model pop_I₊n:
Equations (2):
2 connecting: see equations(expand_connections(pop_I₊n))
Unknowns (2): see unknowns(pop_I₊n)
v(t)
i(t), I_ext = (pop_I₊injector₊I₊u(t))[1:5], I_syn = (pop_I₊syn_injector₊I₊u(t))[1:5], cap_name = :cap), -65.0, Vectorized(5), NoGeometry(), NoMorphology())3. Define Connectivity Matrices
The weight matrix W maps presynaptic populations to postsynaptic populations. Dimensions must be (Npost, Npre).
W_EE = 0.5 .* rand(N_E, N_E) #E -> E
W_EI = 1.0 .* rand(N_I, N_E) #E -> I
W_IE = 2.0 .* rand(N_E, N_I) #I -> E
W_II = 1.0 .* rand(N_I, N_I) #I -> I5×5 Matrix{Float64}:
0.293718 0.515388 0.620702 0.230925 0.699552
0.735884 0.0192808 0.686515 0.162095 0.359942
0.626611 0.551435 0.131316 0.575342 0.610688
0.625633 0.642916 0.539218 0.791609 0.257407
0.836875 0.743044 0.0776607 0.120448 0.5778864. Build Synapse Blocks
build_synapse_block sets up the vectorized synapse matrices and creates the SynapseSpecs for the network builder.
syn_EE = build_synapse_block(pop_E, pop_E, W_EE; name=:syn_EE, E_rev=0.0)
syn_EI = build_synapse_block(pop_E, pop_I, W_EI; name=:syn_EI, E_rev=0.0)
syn_IE = build_synapse_block(pop_I, pop_E, W_IE; name=:syn_IE, E_rev=-80.0)
syn_II = build_synapse_block(pop_I, pop_I, W_II; name=:syn_II, E_rev=-80.0)
synapse_specs = [syn_EE, syn_EI, syn_IE, syn_II]4-element Vector{SynapseSpec}:
SynapseSpec((pop_E₊cap₊v(t))[1:15], (pop_E₊cap₊v(t))[1:15], (pop_E₊syn_injector₊I₊u(t))[1:15], Model syn_EE:
Equations (2):
2 standard: see equations(syn_EE)
Unknowns (4): see unknowns(syn_EE)
s(t)
I_syn(t)
V_pre(t)
V_post(t)
Parameters (6): see parameters(syn_EE)
g_max
τ
E_rev
V_th
⋮, Compartment{NoMorphology, NoGeometry, Float64}(Model pop_E:
Subsystems (8): see hierarchy(pop_E)
cap
injector
syn_injector
p
⋮
Equations (136):
120 standard: see equations(pop_E)
16 connecting: see equations(expand_connections(pop_E))
Unknowns (51): see unknowns(pop_E)
cap₊v(t)
cap₊i(t)
cap₊p₊v(t)
cap₊p₊i(t)
⋮
Parameters (7): see parameters(pop_E)
cap₊C
na₊g
na₊E_rev
k₊g
⋮, (V = (pop_E₊cap₊v(t))[1:15], p_pin = Model pop_E₊p:
Equations (2):
2 connecting: see equations(expand_connections(pop_E₊p))
Unknowns (2): see unknowns(pop_E₊p)
v(t)
i(t), n_pin = Model pop_E₊n:
Equations (2):
2 connecting: see equations(expand_connections(pop_E₊n))
Unknowns (2): see unknowns(pop_E₊n)
v(t)
i(t), I_ext = (pop_E₊injector₊I₊u(t))[1:15], I_syn = (pop_E₊syn_injector₊I₊u(t))[1:15], cap_name = :cap), -65.0, Vectorized(15), NoGeometry(), NoMorphology()))
SynapseSpec((pop_E₊cap₊v(t))[1:15], (pop_I₊cap₊v(t))[1:5], (pop_I₊syn_injector₊I₊u(t))[1:5], Model syn_EI:
Equations (2):
2 standard: see equations(syn_EI)
Unknowns (4): see unknowns(syn_EI)
s(t)
I_syn(t)
V_pre(t)
V_post(t)
Parameters (6): see parameters(syn_EI)
g_max
τ
E_rev
V_th
⋮, Compartment{NoMorphology, NoGeometry, Float64}(Model pop_I:
Subsystems (8): see hierarchy(pop_I)
cap
injector
syn_injector
p
⋮
Equations (76):
60 standard: see equations(pop_I)
16 connecting: see equations(expand_connections(pop_I))
Unknowns (51): see unknowns(pop_I)
cap₊v(t)
cap₊i(t)
cap₊p₊v(t)
cap₊p₊i(t)
⋮
Parameters (7): see parameters(pop_I)
cap₊C
na₊g
na₊E_rev
k₊g
⋮, (V = (pop_I₊cap₊v(t))[1:5], p_pin = Model pop_I₊p:
Equations (2):
2 connecting: see equations(expand_connections(pop_I₊p))
Unknowns (2): see unknowns(pop_I₊p)
v(t)
i(t), n_pin = Model pop_I₊n:
Equations (2):
2 connecting: see equations(expand_connections(pop_I₊n))
Unknowns (2): see unknowns(pop_I₊n)
v(t)
i(t), I_ext = (pop_I₊injector₊I₊u(t))[1:5], I_syn = (pop_I₊syn_injector₊I₊u(t))[1:5], cap_name = :cap), -65.0, Vectorized(5), NoGeometry(), NoMorphology()))
SynapseSpec((pop_I₊cap₊v(t))[1:5], (pop_E₊cap₊v(t))[1:15], (pop_E₊syn_injector₊I₊u(t))[1:15], Model syn_IE:
Equations (2):
2 standard: see equations(syn_IE)
Unknowns (4): see unknowns(syn_IE)
s(t)
I_syn(t)
V_pre(t)
V_post(t)
Parameters (6): see parameters(syn_IE)
g_max
τ
E_rev
V_th
⋮, Compartment{NoMorphology, NoGeometry, Float64}(Model pop_E:
Subsystems (8): see hierarchy(pop_E)
cap
injector
syn_injector
p
⋮
Equations (136):
120 standard: see equations(pop_E)
16 connecting: see equations(expand_connections(pop_E))
Unknowns (51): see unknowns(pop_E)
cap₊v(t)
cap₊i(t)
cap₊p₊v(t)
cap₊p₊i(t)
⋮
Parameters (7): see parameters(pop_E)
cap₊C
na₊g
na₊E_rev
k₊g
⋮, (V = (pop_E₊cap₊v(t))[1:15], p_pin = Model pop_E₊p:
Equations (2):
2 connecting: see equations(expand_connections(pop_E₊p))
Unknowns (2): see unknowns(pop_E₊p)
v(t)
i(t), n_pin = Model pop_E₊n:
Equations (2):
2 connecting: see equations(expand_connections(pop_E₊n))
Unknowns (2): see unknowns(pop_E₊n)
v(t)
i(t), I_ext = (pop_E₊injector₊I₊u(t))[1:15], I_syn = (pop_E₊syn_injector₊I₊u(t))[1:15], cap_name = :cap), -65.0, Vectorized(15), NoGeometry(), NoMorphology()))
SynapseSpec((pop_I₊cap₊v(t))[1:5], (pop_I₊cap₊v(t))[1:5], (pop_I₊syn_injector₊I₊u(t))[1:5], Model syn_II:
Equations (2):
2 standard: see equations(syn_II)
Unknowns (4): see unknowns(syn_II)
s(t)
I_syn(t)
V_pre(t)
V_post(t)
Parameters (6): see parameters(syn_II)
g_max
τ
E_rev
V_th
⋮, Compartment{NoMorphology, NoGeometry, Float64}(Model pop_I:
Subsystems (8): see hierarchy(pop_I)
cap
injector
syn_injector
p
⋮
Equations (76):
60 standard: see equations(pop_I)
16 connecting: see equations(expand_connections(pop_I))
Unknowns (51): see unknowns(pop_I)
cap₊v(t)
cap₊i(t)
cap₊p₊v(t)
cap₊p₊i(t)
⋮
Parameters (7): see parameters(pop_I)
cap₊C
na₊g
na₊E_rev
k₊g
⋮, (V = (pop_I₊cap₊v(t))[1:5], p_pin = Model pop_I₊p:
Equations (2):
2 connecting: see equations(expand_connections(pop_I₊p))
Unknowns (2): see unknowns(pop_I₊p)
v(t)
i(t), n_pin = Model pop_I₊n:
Equations (2):
2 connecting: see equations(expand_connections(pop_I₊n))
Unknowns (2): see unknowns(pop_I₊n)
v(t)
i(t), I_ext = (pop_I₊injector₊I₊u(t))[1:5], I_syn = (pop_I₊syn_injector₊I₊u(t))[1:5], cap_name = :cap), -65.0, Vectorized(5), NoGeometry(), NoMorphology()))5. Driving Stimuli & Network Assembly
Give the excitatory population a constant current kick to start the activity
drivers = [(1, 25.0)]
net = build_acausal_network([pop_E, pop_I]; synapse_specs=synapse_specs, drivers=drivers)
println("Compiling vectorized network...")
sys = mtkcompile(net.sys)Model network:
Equations (120):
120 standard: see equations(network)
Unknowns (120): see unknowns(network)
(syn_II₊s(t))[5]
(syn_II₊s(t))[4]
(syn_II₊s(t))[3]
(syn_II₊s(t))[2]
⋮
Parameters (38): see parameters(network)
pop_E₊cap₊C
pop_E₊na₊g
pop_E₊na₊E_rev
pop_E₊k₊g
⋮
Observed (1210): see observed(network)Because the underlying equations are array-based, the Jacobian of this system is highly sparse: most neurons only affect their own gating variables, with off-diagonal elements coming only from the synapse blocks.
Passing jac=true, sparse=true tells the solver to compute an analytical Jacobian and leverage sparse linear algebra. This speeds simulation, as the solver only computes non-zero derivatives instead of a dense matrix. Sparse Jacobians are also essential for fast automatic differentiation (AD), which we will need later for parameter estimation.
prob = ODEProblem(sys, [], (0.0, 100.0), jac=true, sparse=true)ODEProblem with uType Vector{Float64} and tType Float64. In-place: true
Initialization status: FULLY_DETERMINED
Non-trivial mass matrix: false
timespan: (0.0, 100.0)
u0: 120-element Vector{Float64}:
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
⋮
-65.0
-65.0
-65.0
-65.0
-65.0
-65.0
-65.0
-65.0
-65.0Let's visualize the sparsity pattern using Plots.spy. You'll see a heavy block-diagonal structure (intrinsic dynamics) with off-diagonal bands representing the synaptic couplings between the E and I populations.
println("Plotting Jacobian sparsity pattern...")
p_jac = spy(prob.f.jac_prototype,
title="Jacobian Sparsity Pattern",
legend=false)
println("Solving...")
sol = solve(prob, Rosenbrock23())retcode: Success
Interpolation: specialized 2nd order "free" stiffness-aware interpolation
t: 170-element Vector{Float64}:
0.0
0.00022109928137169384
0.006322968558070122
0.012908472245188929
0.027592911075486504
0.04340941923912327
0.06852261961711464
0.09473710411660585
0.12858911267378498
0.1628765472243065
⋮
46.796609907475926
49.40383636050432
52.37612250627013
55.85307084547516
60.05542870066455
65.36109704350486
72.49247892179994
83.07758871178262
100.0
u: 170-element Vector{Vector{Float64}}:
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 … -65.0, -65.0, -65.0, -65.0, -65.0, -65.0, -65.0, -65.0, -65.0, -65.0]
[3.741348999327893e-14, 3.741348999327893e-14, 3.741348999327893e-14, 3.741348999327893e-14, 3.741348999327893e-14, 3.741348999327893e-14, 3.741348999327893e-14, 3.741348999327893e-14, 3.741348999327893e-14, 3.741348999327893e-14 … -64.99376962888171, -64.99376962888171, -64.99376962888171, -64.99376962888171, -64.99376962888171, -64.99376962888171, -64.99376962888171, -64.99376962888171, -64.99376962888171, -64.99376962888171]
[1.0744902781608568e-12, 1.0744902781608548e-12, 1.074490278160883e-12, 1.0744902781609523e-12, 1.0744902781608814e-12, 1.0744902781608568e-12, 1.0744902781608548e-12, 1.074490278160883e-12, 1.0744902781609523e-12, 1.0744902781608814e-12 … -64.82198761113345, -64.82198761113384, -64.8219876111336, -64.82198761113368, -64.82198761113372, -64.82198761113378, -64.82198761113386, -64.82198761113368, -64.82198761113375, -64.8219876111333]
[2.2036710858445703e-12, 2.203671085844381e-12, 2.2036710858444257e-12, 2.203671085844793e-12, 2.203671085844109e-12, 2.2036710858445703e-12, 2.203671085844381e-12, 2.2036710858444257e-12, 2.203671085844793e-12, 2.203671085844109e-12 … -64.6369426305452, -64.63694263054705, -64.63694263054623, -64.63694263054684, -64.63694263054725, -64.6369426305477, -64.63694263054828, -64.6369426305477, -64.6369426305482, -64.63694263054651]
[4.7589367172986e-12, 4.758936717282059e-12, 4.758936717267898e-12, 4.758936717257087e-12, 4.758936717235552e-12, 4.7589367172986e-12, 4.758936717282059e-12, 4.758936717267898e-12, 4.758936717257087e-12, 4.758936717235552e-12 … -64.22564082712458, -64.22564082714207, -64.22564082714662, -64.22564082715824, -64.22564082716877, -64.22564082717938, -64.22564082719084, -64.22564082719657, -64.22564082720751, -64.22564082720797]
[7.569957671979513e-12, 7.569957671741674e-12, 7.569957671513511e-12, 7.569957671299097e-12, 7.569957671040703e-12, 7.569957671979513e-12, 7.569957671741674e-12, 7.569957671513511e-12, 7.569957671299097e-12, 7.569957671040703e-12 … -63.78465250876654, -63.78465250888663, -63.784652508972485, -63.78465250907716, -63.78465250917874, -63.78465250928032, -63.78465250938466, -63.78465250947366, -63.784652509576155, -63.7846525096514]
[1.2161204179496547e-11, 1.2161204176036161e-11, 1.2161204172617771e-11, 1.2161204169260203e-11, 1.2161204165708453e-11, 1.2161204179496547e-11, 1.2161204176036161e-11, 1.2161204172617771e-11, 1.2161204169260203e-11, 1.2161204165708453e-11 … -63.08874139931318, -63.08874140027298, -63.088741401137135, -63.0887414020543, -63.08874140296196, -63.08874140386861, -63.088741404785274, -63.08874140565839, -63.08874140656775, -63.088741407403305]
[1.7126294724993033e-11, 1.71262947029551e-11, 1.7126294681040303e-11, 1.7126294659308042e-11, 1.7126294636994726e-11, 1.7126294724993033e-11, 1.71262947029551e-11, 1.7126294681040303e-11, 1.7126294659308042e-11, 1.7126294636994726e-11 … -62.3678816018723, -62.36788160629804, -62.367881610516584, -62.36788161485086, -62.36788161916265, -62.36788162347001, -62.367881627804216, -62.36788163204256, -62.36788163635613, -62.367881640515066]
[2.3807608330620574e-11, 2.3807608205740532e-11, 2.3807608081218582e-11, 2.3807607957242616e-11, 2.380760783153432e-11, 2.3807608330620574e-11, 2.3807608205740532e-11, 2.3807608081218582e-11, 2.3807607957242616e-11, 2.380760783153432e-11 … -61.44533984025383, -61.44533985834181, -61.44533987597528, -61.44533989386511, -61.44533991170027, -61.44533992951922, -61.44533994741184, -61.44533996508994, -61.445339982923485, -61.445340000432964]
[3.0897266033488875e-11, 3.089726556880902e-11, 3.089726510498638e-11, 3.089726464250666e-11, 3.089726417577863e-11, 3.0897266033488875e-11, 3.089726556880902e-11, 3.089726510498638e-11, 3.089726464250666e-11, 3.089726417577863e-11 … -60.520415965418714, -60.52041601809715, -60.52041606989516, -60.520416122194476, -60.52041617437776, -60.52041622651736, -60.52041627882764, -60.5204163307144, -60.52041638288409, -60.520416434454745]
⋮
[0.0001624163186627216, 0.00014447026108849943, 0.031769164599961486, 4.9904803514510405, 0.00020405550956191187, 0.0001624163186627216, 0.00014447026108849943, 0.031769164599961486, 4.9904803514510405, 0.00020405550956191187 … -60.991067017965214, -63.566976334742954, -41.62082547800239, -50.30307508220297, -56.11606433702249, -57.63242381713405, -56.37764917349946, -48.06311069464399, -39.61566864894115, -4.215034990483083]
[9.582947536925707e-5, 8.761645573728094e-5, 0.03179699064571945, 4.99164372669057, 0.00012044919724276507, 9.582947536925707e-5, 8.761645573728094e-5, 0.03179699064571945, 4.99164372669057, 0.00012044919724276507 … -60.99399568243333, -63.56712837999493, -41.620486939807634, -50.305051844554185, -56.117144590164685, -57.63239052941331, -56.378380923808365, -48.063670196472906, -39.614884536335275, -4.236869506105834]
[5.2398601293909055e-5, 5.053314583786381e-5, 0.031823342909218844, 4.992347920757826, 6.591738340473133e-5, 5.2398601293909055e-5, 5.053314583786381e-5, 0.031823342909218844, 4.992347920757826, 6.591738340473133e-5 … -60.995816243500016, -63.567104886688135, -41.62013323677682, -50.3061851464182, -56.11771962257995, -57.632245692957476, -56.378726386584134, -48.06388554245371, -39.614299791785534, -4.250567538421499]
[2.5749129450929492e-5, 2.777826470006057e-5, 0.0318466289528062, 4.992728280999096, 3.245623999041034e-5, 2.5749129450929492e-5, 2.777826470006057e-5, 0.0318466289528062, 4.992728280999096, 3.245623999041034e-5 … -60.996839809792654, -63.5669694519263, -41.61977760193019, -50.306723611999516, -56.117941230091624, -57.632031788099496, -56.37880526953983, -48.06386582330264, -39.613853553935606, -4.258585857578371]
[1.0808914270272886e-5, 1.5021466336794015e-5, 0.03186588852243806, 4.992893941443233, 1.3697231629879646e-5, 1.0808914270272886e-5, 1.5021466336794015e-5, 0.03186588852243806, 4.992893941443233, 1.3697231629879646e-5 … -60.997322210703786, -63.5667787901686, -41.61944263364391, -50.306875204079965, -56.117939181143846, -57.63179131668903, -56.37872166339439, -48.0637084173989, -39.61351215609318, -4.262827300398757]
[3.526238391010662e-6, 8.80339355724135e-6, 0.03188060692272243, 4.992932968234592, 4.553049868344502e-6, 3.526238391010662e-6, 8.80339355724135e-6, 0.03188060692272243, 4.992932968234592, 4.553049868344502e-6 … -60.99747427145324, -63.566582923902146, -41.6191558284053, -50.306813832798625, -56.117824126147376, -57.63156502206536, -56.37856557330866, -48.06349935605549, -39.61326029241735, -4.26474403313707]
[7.184244252453129e-7, 6.406455169084106e-6, 0.03189062154240156, 4.99291428744531, 1.0275334688220505e-6, 7.184244252453129e-7, 6.406455169084106e-6, 0.03189062154240156, 4.99291428744531, 1.0275334688220505e-6 … -60.997464643541036, -63.566422843602936, -41.618943371663605, -50.30667923276882, -56.117686000706186, -57.631387935548, -56.37841060116474, -48.06330985206549, -39.61309241006781, -4.265403900394342]
[3.431557205988831e-8, 5.822862036993118e-6, 0.031896150512016656, 4.992886408299242, 1.6856295233553676e-7, 3.431557205988831e-8, 5.822862036993118e-6, 0.031896150512016656, 4.992886408299242, 1.6856295233553676e-7 … -60.997415047122466, -63.5663247047783, -41.61882069484385, -50.30656929210111, -56.11758703878485, -57.63128209338925, -56.3783068022658, -48.063187761837376, -39.613002868622, -4.265528870011105]
[-2.762162237113372e-9, 5.791467653011223e-6, 0.03189795090538837, 4.9928740108859, 1.2201128187813394e-7, -2.762162237113372e-9, 5.791467653011223e-6, 0.03189795090538837, 4.9928740108859, 1.2201128187813394e-7 … -60.99739052887798, -63.5662916085663, -41.61878107618241, -50.30652738225308, -56.117551211864544, -57.63124705512299, -56.37827035177949, -48.06314570328087, -39.61297594856141, -4.265522958015287]6. Plot the Results
We splat the voltage array (...) to plot all individual elements in the population. Couldn't find a way to add numberings with MTK but I assume it exists...?
p1 = plot(sol, idxs=[sys.pop_E.cap.v...],
title="Excitatory Population", legend=false, ylabel="V (mV)")
p2 = plot(sol, idxs=[sys.pop_I.cap.v...],
title="Inhibitory Population", legend=false, ylabel="V (mV)", xlabel="Time (ms)")Combine the simulation plots with the sparsity plot
plot(p1, p2, p_jac, layout=(3,1), size=(800, 800))This page was generated using Literate.jl.