Skip to content

Commit b5b7ce3

Browse files
authored
Merge pull request #324 from Neuroblox/cleanup04
cleanup PR for Neuroblox 0.3
2 parents 702b0fd + e6aee0b commit b5b7ce3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+780
-2485
lines changed

examples/jansen_rit_liu_et_al.jl

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# A basic example using delayed differential equations to introduce optional delays in interregional connections.
2+
# The system being built here is the same as in Liu et al. (2020). DOI: 10.1016/j.neunet.2019.12.021.
3+
4+
using Neuroblox # Core functionality
5+
using DifferentialEquations # Needed for solver
6+
using MetaGraphs # Set up graph of systems
7+
8+
τ_factor = 1000 #needed because the paper units were in seconds, and we need ms to be consistent
9+
10+
# Create Jansen-Rit blocks with the same parameters as the paper and store them in a list
11+
@named Str = JansenRit=0.0022*τ_factor, H=20, λ=300, r=0.3)
12+
@named GPE = JansenRit=0.04*τ_factor, cortical=false) # all default subcortical except τ
13+
@named STN = JansenRit=0.01*τ_factor, H=20, λ=500, r=0.1)
14+
@named GPI = JansenRit(cortical=false) # default parameters subcortical Jansen Rit blox
15+
@named Th = JansenRit=0.002*τ_factor, H=10, λ=20, r=5)
16+
@named EI = JansenRit=0.01*τ_factor, H=20, λ=5, r=5)
17+
@named PY = JansenRit(cortical=true) # default parameters cortical Jansen Rit blox
18+
@named II = JansenRit=2.0*τ_factor, H=60, λ=5, r=5)
19+
blox = [Str, GPE, STN, GPI, Th, EI, PY, II]
20+
21+
# test graphs
22+
g = MetaDiGraph()
23+
add_blox!.(Ref(g), blox)
24+
25+
# Store parameters to be passed later on
26+
params = @parameters C_Cor=60 C_BG_Th=60 C_Cor_BG_Th=5 C_BG_Th_Cor=5
27+
28+
# Add the edges as specified in Table 2 of Liu et al.
29+
# This is a subset of the edges selected to run a shorter version of the model.
30+
# If you want to add more edges in a batch, you can create an adjacency matrix and then call create_adjacency_edges!(g, adj_matrix).
31+
add_edge!(g, 2, 1, Dict(:weight => -0.5*C_BG_Th))
32+
add_edge!(g, 2, 2, Dict(:weight => -0.5*C_BG_Th))
33+
add_edge!(g, 2, 3, Dict(:weight => C_BG_Th))
34+
add_edge!(g, 3, 2, Dict(:weight => -0.5*C_BG_Th))
35+
add_edge!(g, 3, 7, Dict(:weight => C_Cor_BG_Th))
36+
add_edge!(g, 4, 2, Dict(:weight => -0.5*C_BG_Th))
37+
add_edge!(g, 4, 3, Dict(:weight => C_BG_Th))
38+
add_edge!(g, 5, 4, Dict(:weight => -0.5*C_BG_Th))
39+
add_edge!(g, 6, 5, Dict(:weight => C_BG_Th_Cor))
40+
add_edge!(g, 6, 7, Dict(:weight => 6*C_Cor))
41+
add_edge!(g, 7, 6, Dict(:weight => 4.8*C_Cor))
42+
add_edge!(g, 7, 8, Dict(:weight => -1.5*C_Cor))
43+
add_edge!(g, 8, 7, Dict(:weight => 1.5*C_Cor))
44+
add_edge!(g, 8, 8, Dict(:weight => 3.3*C_Cor))
45+
46+
# Create the ODE system. This will have some warnings as delays are set to 0 - ignore those for now.
47+
@named final_system = system_from_graph(g, params)
48+
final_system_sys = structural_simplify(final_system)
49+
50+
# Collect the graph delays and create a DDEProblem. This will all be zero in this case.
51+
final_delays = graph_delays(g)
52+
sim_dur = 1000.0 # Simulate for 1 second
53+
prob = DDEProblem(final_system_sys,
54+
[],
55+
(0.0, sim_dur),
56+
constant_lags = final_delays)
57+
58+
# Select the algorihm. MethodOfSteps will return the same as Vern7() in this case because there are no non-zero delays, but is required since this is a DDEProblem.
59+
alg = MethodOfSteps(Vern7())
60+
sol_dde_no_delays = solve(prob, alg, saveat=1)
61+
62+
63+
# Example of delayed connections
64+
65+
# First, recreate the graph to remove previous connections
66+
g = MetaDiGraph()
67+
add_blox!.(Ref(g), blox)
68+
69+
# Now, add the edges with the specified delays. Again, if you prefer, there's a version using adjacency and delay matrices to assign all at once.
70+
add_edge!(g, 2, 1, Dict(:weight => -0.5*60, :delay => 1))
71+
add_edge!(g, 2, 2, Dict(:weight => -0.5*60, :delay => 2))
72+
add_edge!(g, 2, 3, Dict(:weight => 60, :delay => 1))
73+
add_edge!(g, 3, 2, Dict(:weight => -0.5*60, :delay => 1))
74+
add_edge!(g, 3, 7, Dict(:weight => 5, :delay => 1))
75+
add_edge!(g, 4, 2, Dict(:weight => -0.5*60, :delay => 4))
76+
add_edge!(g, 4, 3, Dict(:weight => 60, :delay => 1))
77+
add_edge!(g, 5, 4, Dict(:weight => -0.5*60, :delay => 2))
78+
add_edge!(g, 6, 5, Dict(:weight => 5, :delay => 1))
79+
add_edge!(g, 6, 7, Dict(:weight => 6*60, :delay => 2))
80+
add_edge!(g, 7, 6, Dict(:weight => 4.8*60, :delay => 3))
81+
add_edge!(g, 7, 8, Dict(:weight => -1.5*60, :delay => 1))
82+
add_edge!(g, 8, 7, Dict(:weight => 1.5*60, :delay => 4))
83+
add_edge!(g, 8, 8, Dict(:weight => 3.3*60, :delay => 1))
84+
85+
# Now you can run the same code as above, but it will handle the delays automatically.
86+
@named final_system = system_from_graph(g, params)
87+
final_system_sys = structural_simplify(final_system)
88+
89+
# Collect the graph delays and create a DDEProblem.
90+
final_delays = graph_delays(g)
91+
sim_dur = 1000.0 # Simulate for 1 second
92+
prob = DDEProblem(final_system_sys,
93+
[],
94+
(0.0, sim_dur),
95+
constant_lags = final_delays)
96+
97+
# Select the algorihm. MethodOfSteps is now needed because there are non-zero delays.
98+
alg = MethodOfSteps(Vern7())
99+
sol_dde_with_delays = solve(prob, alg, saveat=1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# This is a tutorial for how to run a whole-brain simulation using the Larter-Breakspear model.
2+
# This approach is the same as that taken in Antal et al. (2023) and van Nieuwenhuizen et al. (2023), with the exception that a different DTI dataset is used.
3+
# The data used in those papers is available upon request from the authors where it was validated in Endo et al. (2020).
4+
# As we'd like to make this tutorial as accessible as possible, we'll use a different dataset that is publicly available.
5+
# The dataset used here is from Rosen and Halgren (2021) and is available at https://zenodo.org/records/10150880.
6+
7+
# References:
8+
# 1. Antal et al. (2023). DOI: 10.48550/arXiv.2303.13746.
9+
# 2. van Nieuwenhuizen et al. (2023). DOI: 10.1101/2023.05.10.540257.
10+
# 3. Endo et al. (2020). DOI: 10.3389/fncom.2019.00091.
11+
# 4. Rosen and Halgren (2021). DOI: 10.1523/ENEURO.0416-20.2020.
12+
13+
using Neuroblox # Core functionality
14+
using CSV, MAT, DataFrames # Import/export data functions
15+
using MetaGraphs # Set up graph of systems
16+
using DifferentialEquations # Needed for solver TODO: make a new simulate that can handle system_from_graph
17+
using StatsBase # Needed for rescaling the DTI matrix
18+
#using Plots # Only uncomment if you actually want to do plotting, otherwise save yourself the overhead
19+
20+
# A note on the data: this is a 360-region parcellation of the brain. For this example, we'll extract and rescale the left hemisphere default mode network.
21+
# If you want a true rescaling method, you'd need to do some additional corrections (e.g., volume correction) for the number of streamlines computed.
22+
# For this tutorial though, the rescaling is approximate to just give us a working example.
23+
# See Endo et al. for more details on how to do a better DTI rescaling.
24+
25+
# Load the data
26+
# As mentioned above, this data is from Rosen and Halgren (2021). You can download the original at https://zenodo.org/records/10150880.
27+
# For convenience, we have included the average connectivity matrix (log scaled probability from streamline counts) available from that link within this repository.
28+
data = matread("averageConnectivity_Fpt.mat")
29+
30+
# Extract the data
31+
adj = data["Fpt"]
32+
adj[findall(isnan, adj)] .= 0 # Replace NaNs with 0s
33+
adj = StatsBase.transform(StatsBase.fit(UnitRangeTransform, adj, dims=2), adj) # Equivalent of Matlab rescale function. Resamples to unit range.
34+
35+
# For the purpose of this simulation, we want something that will run relatively quickly.
36+
# In this parcellation, there are 40 regions per hemisphere in the default mode network, so we'll extract the left hemisphere DMN.
37+
# Original indices with networks are listed in allTables.xlsx at the same download link from the Rosen and Halgren (2021) dataset listed above.
38+
left_indices = [30, 31, 32, 33, 34, 35, 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 76, 87, 88, 90, 93, 94, 118, 119, 120, 126, 130, 131, 132, 134, 150, 151, 155, 161, 162, 164, 165, 176, 177]
39+
adj = adj[left_indices, left_indices]
40+
41+
# Extract the names of the regions
42+
names = data["parcelIDs"]
43+
names = names[left_indices]
44+
45+
# Plot the connectivity matrix
46+
# This is optional but gives you a sense of what the overall connectivity looks like.
47+
# If you want to do plotting remember to uncomment the Plots import above.
48+
#heatmap(adj)
49+
50+
# Number of regions in this particular dataset
51+
N = 40
52+
53+
# Create list of all blocks
54+
blocks = Vector{LarterBreakspear}(undef, N)
55+
56+
for i in 1:N
57+
# Since we're creating an ODESystem inside of the blox, we need to use a symbolic name
58+
# Why the extra noise in connectivity? The DTI scaling is arbitrary in this demo, so adding stochasticity to this parameter helps things from just immediately synchronizing.
59+
blocks[i] = LarterBreakspear(name=Symbol(names[i]), C=rand()*0.3)
60+
end
61+
62+
# Create a graph using the blocks and the DTI defined adjacency matrix
63+
g = MetaDiGraph()
64+
add_blox!.(Ref(g), blocks)
65+
create_adjacency_edges!(g, adj)
66+
67+
# Create the ODE system
68+
# This may take a minute, as it is compiling the whole system
69+
@named sys = system_from_graph(g)
70+
sys = structural_simplify(sys)
71+
72+
# Simulate for 100ms
73+
sim_dur = 1e3
74+
75+
# Create random initial conditions because uniform initial conditions are no bueno. There are 3 states per node.
76+
v₀ = 0.9*rand(N) .- 0.6
77+
z₀ = 0.9*rand(N) .- 0.9
78+
w₀ = 0.4*rand(N) .+ 0.11
79+
u₀ = [v₀ z₀ w₀]'[:] # Trick to interleave vectors based on column-major ordering
80+
81+
# Create the ODEProblem to run all the final system of equations
82+
prob = ODEProblem(sys, u₀, (0.0, sim_dur), [])
83+
84+
# Run the simulation and save every 2ms
85+
@time sol = solve(prob, AutoVern7(Rodas4()), saveat=2)
86+
87+
# Visualizing the results
88+
# Again, to use any of these commands be sure to uncomment the Plots import above.
89+
# This plot shows all of the states computed. As such, it is very messy, but useful to make sure that no out-of-bounds behavior (due to bad connectivity) occured.
90+
# plot(sol)
91+
92+
# More interesting is to choose the plot from a specific region and see the results. Here, we'll plot a specific region's average voltage.
93+
# First, confirm the region (left orbitofrontal cortex)
94+
states(sys)[64] # Should give L_OFC₊V(t)
95+
# Next plot just this variable
96+
#plot(sol, idxs=(64))
97+
98+
# Suppose we want to run a simulation with different initial conditions. We can do that by remaking the problem to avoid having to recompile the entire system.
99+
# For example, let's say we want to run the simulation with a different initial condition for the voltage.
100+
v₀ = 0.9*rand(N) .- 0.61
101+
z₀ = 0.9*rand(N) .- 0.89
102+
w₀ = 0.4*rand(N) .+ 0.1
103+
u₀ = [v₀ z₀ w₀]'[:] # Trick to interleave vectors based on column-major ordering
104+
105+
# Now remake and re-solve
106+
prob2 = remake(prob; u0=u₀)
107+
@time sol2 = solve(prob2, AutoVern7(Rodas4()), saveat=2)
108+
109+
# Running a longer simulation
110+
# Now that we've confirmed this runs, let's go ahead and do a 10min (600s) simulation.
111+
# Takes <1min to simulate this run. If you save more frequently it'll take longer to run, but you'll have more data to work with.
112+
# Remember to update the dt below for the BOLD signal if you don't save at the same frequency.
113+
# If you find this is taking an *excessively* long time (i.e., more than a couple minutes), you likely happened upon a parameter set that has put you into a very stiff area.
114+
# In that case, you can try to re-run the simulation with a different initial condition or parameter set.
115+
# In a real study, you'd allow even these long runs to finish, but for the sake of this tutorial we'll just stop it early.
116+
sim_dur = 6e5
117+
prob = remake(prob; tspan=(0.0, sim_dur))
118+
@time sol = solve(prob, AutoVern7(Rodas4()), saveat=2)
119+
120+
# Instead of plotting the data, let's save it out to a CSV file for later analysis
121+
CSV.write("example_output.csv", DataFrame(sol))
122+
123+
# You should see a noticeable difference in speed compared to the first time, and notice you save the overhead of structural_simplify.
124+
125+
# Now let's do a simulation that creates an fMRI signal out of the neural simulation run above.
126+
# This example will use the Balloon model with slightly adjusted parameters - see Endo et al. for details.
127+
128+
TR = 800 # Desired repetition time (TR) in ms
129+
dt = 2 # Time step of the simulation in ms
130+
bold = boldsignal_endo_balloon(sol.t/1000, sol[1:3:end, :], TR, dt) # Note this returns the signal in units of TR
131+
132+
# Remember that the BOLD signal takes a while to equilibrate, so drop the first 90 seconds of the signal.
133+
omit_idx = Int(round(90/(TR/1000)))
134+
bold = bold[:, omit_idx:end]
135+
136+
# Plot an example region to get a sense of what the BOLD signal looks like
137+
# plot(bold[1, :])

examples/wholebrainWC/DATA/brainamp.loc

-63
This file was deleted.
-3.38 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
-37.4 KB
Binary file not shown.
Binary file not shown.

examples/wholebrainWC/Project.toml

-10
This file was deleted.

0 commit comments

Comments
 (0)