Sensitivities of the EFMs of a toy model
The optimal flux distribution of any metabolic model can be written as a weighted sum of the EFMs of that model. We are interested in calculating the sensitivity of these weightings to the model parameters, and can use DifferentiableMetabolism.jl to efficiently do so.
using ElementaryFluxModes
import AbstractFBCModels as A
import FastDifferentiation as F
const Ex = F.Node
import DifferentiableMetabolism as D
using COBREXA
import COBREXA as X
using JSON
import Tulip as T
Build a simple enzyme constrained model
The code used to construct the model is located in test/simple_model.jl
, but it is not shown here for brevity.
model
parameter_values = Dict{Symbol,Float64}()
reaction_isozymes = Dict{String,Dict{String,X.IsozymeT{Ex}}}() # a mapping from reaction IDs to isozyme IDs to isozyme structs.
for rid in A.reactions(model)
grrs = A.reaction_gene_association_dnf(model, rid)
isnothing(grrs) && continue # skip if no grr available
haskey(rid_kcat, rid) || continue # skip if no kcat data available
for (i, grr) in enumerate(grrs)
kcat = rid_kcat[rid]
parameter_values[Symbol(rid)] = kcat
d = get!(reaction_isozymes, rid, Dict{String,X.IsozymeT{Ex}}())
d["isozyme_$i"] = X.IsozymeT{Ex}(
gene_product_stoichiometry = Dict(grr .=> fill(float(1.0), size(grr))), # assume subunit stoichiometry of 1 for all isozymes
kcat_forward = Ex(Symbol(rid)),
kcat_reverse = 0.0,
)
end
end
km = enzyme_constrained_flux_balance_constraints( # kinetic model
model;
reaction_isozymes,
gene_product_molar_masses,
capacity,
)
ec_solution = D.optimized_values(
km,
parameter_values;
objective = km.objective.value,
optimizer = T.Optimizer,
settings = [X.set_optimizer_attribute("IPM_IterationsLimit", 10_000)],
)
ec_solution.tree.fluxes
ConstraintTrees.Tree{Float64} with 6 elements:
:r1 => 50.0
:r2 => 75.0
:r3 => 50.0
:r4 => 50.0
:r5 => 25.0
:r6 => 75.0
We have a solution that uses every reaction, and the enzyme capacities are both full. Therefore, we may calculate the EFMs of this solution and directly differentiate them, with no pruning required.
Calculate EFMs of the optimal solution
We need to input the stoichiometric matrix N
into ElementaryFluxModes.jl
N = A.stoichiometry(model)
4×6 SparseArrays.SparseMatrixCSC{Float64, Int64} with 10 stored entries:
1.0 ⋅ -1.0 ⋅ ⋅ ⋅
⋅ 1.0 -1.0 ⋅ -1.0 ⋅
⋅ ⋅ 1.0 -1.0 ⋅ ⋅
⋅ ⋅ ⋅ 1.0 1.0 -1.0
Calculate a flux matrix of the EFMs, the size of which is (n,k), for n reactions and k EFMs
E = get_efms(Matrix(N))
6×2 Matrix{Float64}:
1.0 0.0
1.0 1.0
1.0 0.0
1.0 0.0
0.0 1.0
1.0 1.0
Make a dictionary out of the EFM result
EFM_dict = Dict(A.reactions(model) .=> eachrow(E))
EFMs = [
Dict(k => v[1] / EFM_dict["r6"][1] for (k, v) in EFM_dict),
Dict(k => v[2] / EFM_dict["r6"][2] for (k, v) in EFM_dict),
]
2-element Vector{Dict{String, Float64}}:
Dict("r1" => 1.0, "r2" => 1.0, "r5" => 0.0, "r6" => 1.0, "r3" => 1.0, "r4" => 1.0)
Dict("r1" => 0.0, "r2" => 1.0, "r5" => 1.0, "r6" => 1.0, "r3" => 0.0, "r4" => 0.0)
The optimal solution, v, can be written as λ₁EFM₁+λ₂EFM₂=v so that the λ give us the weightings of the two EFMs.
Let's calculate λ₁ and λ₂, using reactions r1
and r5
, as these are not shared by the EFMs
M = [
EFM_dict["r1"][1] EFM_dict["r1"][2]
EFM_dict["r5"][1] EFM_dict["r5"][2]
]
v = [
ec_solution.tree.fluxes["r1"]
ec_solution.tree.fluxes["r5"]
]
λ = M \ v
2-element Vector{Float64}:
49.999999958301736
24.99999999842566
The optimal solution is therefore made up of 50 units of flux through EFM₁ and 25 units of flux through EFM₂.
Differentiate the EFMs
We have calculated the EFMs, and now wish to differentiate their weightings, λ
, with respect to the model parameters
parameters = Ex.(collect(keys(parameter_values)))
p_vals = collect(values(parameter_values))
rid_pid =
Dict(rid => [iso.kcat_forward for (k, iso) in v][1] for (rid, v) in reaction_isozymes)
sens_efm = differentiate_efm(
EFMs,
parameters,
rid_pid,
parameter_values,
rid_gcounts,
capacity,
gene_product_molar_masses,
T.Optimizer,
)
2×3 Matrix{Float64}:
2.5 -0.0 2.5
-0.0 5.0 -0.0
This page was generated using Literate.jl.