Define experiments using @experiment
Say you have a model of a brain circuit, and you would like to see its outputs with various sets of parameters. The @experiment macro provides an easy interface for iterating on a problem by changing its parameters, initial conditions, and timespan.
First let's create a circuit and then a simulation.
@graph g begin
@nodes begin
Brainstem = NGNMM_theta()
Cortex = Cortex(
N_wta=20,
N_exci=5,
density=0.05,
weight=1.0,
I_bg=4,
G_syn_inhi=4.0,
τ_inhi=70,
G_syn_ff_inhi=1.5
)
end
@connections begin
Brainstem => Cortex, (weight = 20)
end
end
tspan = (0., 500.)
prob = ODEProblem(g, [], tspan)Modifying parameters
Let's say we apply a drug whose known effect is to scale the timescale of the GABA synapses. Let's also say that this effect is limited to the cortex. Then we can write:
prob_drugged = @experiment prob begin
@setup begin
scale = 2.0
end
GABA_A_Synapse in Cortex, τ₂ -> τ₂ * scale
endInside the experiment macro, the @setup block is optionally used to define variables.
The next line gives a rule for changing the parameters of the problem. This has two parts:
- Blox selection:
GABA_A_Synapse in Cortexis used to select all theGABA_A_Synapseinside the graph forCortex(reference the API to see the full list of recognized selections) - Parameter modification:
τ₂ -> τ₂ * scalesets theτ₂parameter of all of these synapses to its original value timesscale. This should be read as a function.
Any number of these parameter modification lines may be included inside the macro.
Note also that prob_drugged is a copy of the original prob; if we wanted to simulate the original prob, we could.
Modifying connection weights and synapse parameters
You can also target connections directly using a => pair. This updates both the connection weight property (if the argument name matches, e.g. weight) and any matching parameter on all intermediate synapse/receptor blox involved in that connection:
prob_scaled = @experiment prob begin
@setup begin
scale = 2.0
end
# Scale the weight of every connection from Brainstem to Cortex,
# and also any matching parameter on intermediate synapses:
Brainstem => Cortex, weight -> weight * scale
endTo restrict the update to a specific synapse type only:
prob_drugged = @experiment prob begin
@setup begin
scale = 2.0
end
(GABA_A_Synapse ∈ (Brainstem => Cortex)), τ₂ -> τ₂ * scale
endBoth Brainstem and Cortex here may be composite blox (brain regions) or simple blox. The macro traverses the flat graph to find all connections between their constituent neurons.
Selecting blox by tag
Every blox has a tags::Set{Symbol} field, which marks a subset of blox of one type without defining a new type. Set it with the tags keyword argument when constructing a blox. Connection rules forward tags to the synapse, receptor and transmitter blox they create, so you can tag specific routes of a circuit:
@graph g begin
@nodes begin
itcvm = ITCvm()
itcdm = ITCdm()
cem = CeM()
end
@connections begin
# these GABA-A synapses are γ1-subunit-containing
itcvm => cem, HypergeometricRule(; density=0.07, weight=0.1, tags=:gamma1)
# an ordinary GABA-A projection from the same neurons
itcvm => itcdm, HypergeometricRule(; density=0.1, weight=0.05)
end
endAny blox selection except a bare src => dst pair can be restricted with a where clause. The clause is a Bool expression of tags, the tag set of each candidate blox. For example, a γ1 positive allosteric modulator slows the decay of only the tagged synapses:
prob_pam = @experiment prob begin
(GABA_A_Synapse where :gamma1 ∈ tags), τ₂ -> τ₂ * 2
endBecause the predicate is ordinary Julia, you get any/all/exclusion for free, and variables from the surrounding scope can be used:
subunit = :gamma1
prob_2 = @experiment prob begin
(AbstractReceptor where subunit ∈ tags || :gamma2 ∈ tags), τ₂ -> τ₂ * 2 # either tag
(GABA_A_Synapse where :gamma1 ∉ tags), G_syn -> G_syn * 0.5 # untagged ones
endA where clause also composes with the composite ((T ∈ composite)) and typed-connection ((T ∈ (src => dst))) selections. Parenthesise the selection before attaching where:
prob_3 = @experiment prob begin
((GABA_A_Synapse ∈ (itcvm => cem)) where :gamma1 ∈ tags), τ₂ -> τ₂ * 2
endThe synapses of a connection between two composites belong to the enclosing graph, not to either composite. So select them with the connection form above, not with (GABA_A_Synapse ∈ CeM).
A where line that selects no blox raises an error, so a misspelled tag can't silently turn an intervention into a no-op. Tags on a composite blox are stored on the composite only. They are not copied onto its components, so a where clause, which looks at the simple blox of the flat graph, does not see them.
Modifying initial conditions and timespan
Let's see how to change initial conditions and timespans for the problem. Let's say we want to dose the drug only after an initial warmup period (that is, simulate the undrugged problem for some time t, then apply the drug effect). We also don't want the drugged simulation to run quite as long.
sol_undrugged = sol(prob, Vern7())
prob_drugged = @experiment prob begin
@initial_conditions sol_undrugged.u[end]
@tspan (0., 300.)
@setup begin
scale = 2.0
end
GABA_A_Synapse in Cortex, τ₂ -> τ₂ * scale
end