Screening through many model variants
screen
is a function that you can use to run many different simulations on many different variants of the model efficiently in parallel.
using COBREXA
download_model(
"http://bigg.ucsd.edu/static/models/e_coli_core.json",
"e_coli_core.json",
"7bedec10576cfe935b19218dc881f3fb14f890a1871448fc19a9b4ee15b448d8",
)
import JSONFBCModels, HiGHS
import ConstraintTrees as C
model = load_model("e_coli_core.json")
JSONFBCModels.JSONFBCModel(#= 95 reactions, 72 metabolites =#)
Screening is done as follows: We specify a range of values to examine (in the example below to an actual range of -10
to 0
), but any vector-like list of things can be used), and write a short "analysis" function that takes one of the values from the range as an argument and runs an analysis for that given value.
Here, we solve 11 different optimization problems for different bounds of the oxygen exchange:
screen(-10:0) do o2_bound
c = flux_balance_constraints(model)
c.fluxes.EX_o2_e.bound = C.EqualTo(o2_bound)
optimized_values(
c,
objective = c.objective.value,
optimizer = HiGHS.Optimizer,
output = c.objective,
)
end
11-element Vector{Float64}:
0.5590505997120256
0.5265539556675367
0.4940573116230476
0.4615606675785586
0.4276455515060357
0.3916484512109149
0.35565135091579403
0.31965425062067315
0.28365715032555233
0.24766005003043148
0.2116629497353106
Screening in multiple dimensions
The simplest way to screen through multi-dimensional arrays is to use an iterator product. In the result, we receive a whole matrix of results instead of a vector.
screen(Iterators.product(-10:2:0, 0:2:10)) do (o2_bound, co2_bound)
c = flux_balance_constraints(model)
c.fluxes.EX_o2_e.bound = C.EqualTo(o2_bound)
c.fluxes.EX_co2_e.bound = C.EqualTo(co2_bound)
optimized_values(
c,
objective = c.objective.value,
optimizer = HiGHS.Optimizer,
output = c.objective,
)
end
6×6 Matrix{Float64}:
0.411525 0.505191 0.549994 0.559051 0.559051 0.556413
0.451394 0.494057 0.492882 0.49012 0.487358 0.484596
0.42659 0.423828 0.421066 0.418304 0.415542 0.412779
0.354774 0.352012 0.349249 0.346487 0.343725 0.340963
0.282957 0.280195 0.277433 0.274671 0.271909 0.269146
0.211141 0.208378 0.205616 0.202854 0.200092 0.19733
Screening through non-numeric properties
If the problem at hand can not be expressed with mere ranges, we can specify vectors with any values. The following code examines the inter-dependency of blocking selected transport reactions together with selected exchanges, with 2 different bounds that implement the block. Because of 3-dimensional input, the result is expectably a 3-dimensional array:
screen(
Iterators.product(
[:H2Ot, :CO2t, :O2t, :NH4t], # a set of transport reactions
[:EX_h2o_e, :EX_co2_e, :EX_o2_e, :EX_nh4_e], # a set of exchanges
[C.Between(-0.1, 0.1), C.Between(-1, 1)], # bounds
),
) do (transport, exchange, bound)
c = flux_balance_constraints(model)
c.fluxes[transport].bound = 5 * bound
c.fluxes[exchange].bound = 3 * bound
optimized_values(
c,
objective = c.objective.value,
optimizer = HiGHS.Optimizer,
output = c.objective,
)
end
4×4×2 Array{Float64, 3}:
[:, :, 1] =
0.390761 0.394089 0.204711 0.0550176
0.390029 0.46905 0.222462 0.0550176
0.212193 0.229509 0.222462 0.0550176
0.091696 0.091696 0.091696 0.0550176
[:, :, 2] =
0.454097 0.494176 0.319654 0.494176
0.454097 0.530039 0.319654 0.550176
0.391648 0.391648 0.319654 0.391648
0.454097 0.530039 0.319654 0.550176
Annotating the results
For reasons of tidyness, it is adviseable to annotate all values with their actual meaning directly in the arrays.
With screen
, the simplest (and usually sufficiently effective) way to do that is to return Pair
s with annotation keys instead of plain values:
screen(
Iterators.product(
[:H2Ot, :CO2t, :O2t, :NH4t],
[:EX_h2o_e, :EX_co2_e, :EX_o2_e, :EX_nh4_e],
[C.Between(-0.1, 0.1), C.Between(-1, 1)],
),
) do (transport, exchange, bound)
c = flux_balance_constraints(model)
c.fluxes[transport].bound = 5 * bound
c.fluxes[exchange].bound = 3 * bound
(transport, exchange, bound.upper) => optimized_values(
c,
objective = c.objective.value,
optimizer = HiGHS.Optimizer,
output = c.objective,
)
end
4×4×2 Array{Pair{Tuple{Symbol, Symbol, Float64}, Float64}, 3}:
[:, :, 1] =
(:H2Ot, :EX_h2o_e, 0.1)=>0.390761 … (:H2Ot, :EX_nh4_e, 0.1)=>0.0550176
(:CO2t, :EX_h2o_e, 0.1)=>0.390029 (:CO2t, :EX_nh4_e, 0.1)=>0.0550176
(:O2t, :EX_h2o_e, 0.1)=>0.212193 (:O2t, :EX_nh4_e, 0.1)=>0.0550176
(:NH4t, :EX_h2o_e, 0.1)=>0.091696 (:NH4t, :EX_nh4_e, 0.1)=>0.0550176
[:, :, 2] =
(:H2Ot, :EX_h2o_e, 1.0)=>0.454097 … (:H2Ot, :EX_nh4_e, 1.0)=>0.494176
(:CO2t, :EX_h2o_e, 1.0)=>0.454097 (:CO2t, :EX_nh4_e, 1.0)=>0.550176
(:O2t, :EX_h2o_e, 1.0)=>0.391648 (:O2t, :EX_nh4_e, 1.0)=>0.391648
(:NH4t, :EX_h2o_e, 1.0)=>0.454097 (:NH4t, :EX_nh4_e, 1.0)=>0.550176
Notably, this approach makes various indexing and ordering errors quite improbable.
This page was generated using Literate.jl.