Using a custom model data structure
This notebooks shows how to utilize the generic accessors and modification functions in COBREXA.jl to run the analysis on any custom model type. We will create a simple dictionary-style structure that describes the model, allow COBREXA to run a FVA on it, and create a simple reaction-removing modification.
First, let's define a very simple stoichiometry-only structure for the model:
using COBREXA
mutable struct MyReaction
max_rate::Float64 # maximum absolute conversion rate
stoi::Dict{String,Float64} # stoichimetry of the reaction
MyReaction() = new(0.0, Dict{String,Float64}())
end
mutable struct MyModel <: MetabolicModel
optimization_target::String # the "objective" reaction name
reactions::Dict{String,MyReaction} # dictionary of reactions
MyModel() = new("", Dict{String,MyReaction}())
MyModel(o, r) = new(o, r)
end
With this, we can start defining the accessors:
COBREXA.n_reactions(m::MyModel) = length(m.reactions)
COBREXA.reactions(m::MyModel) = sort(collect(keys(m.reactions)))
Metabolites are defined only very implicitly, so let's just make a function that collects all names. n_metabolites
can be left at the default definition that just measures the output of metabolites
.
function COBREXA.metabolites(m::MyModel)
mets = Set{String}()
for (_, r) in m.reactions
for (m, _) in r.stoi
push!(mets, m)
end
end
return sort(collect(mets))
end
Now, the extraction of the linear model. Remember the order of element in the vectors must match the order in the output of reactions
and metabolites
.
using SparseArrays
function COBREXA.bounds(m::MyModel)
max_rates = [m.reactions[r].max_rate for r in reactions(m)]
(sparse(-max_rates), sparse(max_rates))
end
function COBREXA.objective(m::MyModel)
if m.optimization_target in keys(m.reactions)
c = spzeros(n_reactions(m))
c[first(indexin([m.optimization_target], reactions(m)))] = 1.0
c
else
throw(
DomainError(
m.optimization_target,
"The target reaction for flux optimization not found",
),
)
end
end
function COBREXA.stoichiometry(m::MyModel)
sparse([
get(m.reactions[rxn].stoi, met, 0.0) for met in metabolites(m), rxn in reactions(m)
])
end
Now the model is complete! We can generate a random one and see how it performs
import Random
Random.seed!(123)
rxn_names = ["Reaction $i" for i = 'A':'Z'];
metabolite_names = ["Metabolite $i" for i = 1:20];
m = MyModel();
for i in rxn_names
m.reactions[i] = MyReaction()
end
for i = 1:50
rxn = rand(rxn_names)
met = rand(metabolite_names)
m.reactions[rxn].stoi[met] = rand([-3, -2, -1, 1, 2, 3])
m.reactions[rxn].max_rate = rand()
end
Let's see what the model looks like now:
m
Metabolic model of type Main.ex-8_custom_model.MyModel sparse([1, 4, 8, 2, 5, 4, 16, 5, 15, 11 … 10, 8, 12, 6, 8, 13, 18, 18, 5, 15], [1, 1, 1, 2, 2, 3, 3, 4, 6, 7 … 19, 20, 20, 21, 22, 22, 23, 24, 26, 26], [1.0, -3.0, 1.0, -1.0, 2.0, 2.0, -2.0, -3.0, 1.0, 3.0 … 3.0, 3.0, 1.0, 2.0, 2.0, -1.0, 2.0, -1.0, 1.0, -3.0], 19, 26) Number of reactions: 26 Number of metabolites: 19
We can run most of the standard function on the model data right away:
using Tulip
m.optimization_target = "Reaction A"
flux_balance_analysis_dict(m, Tulip.Optimizer)
Dict{String, Float64} with 26 entries: "Reaction E" => -0.0 "Reaction F" => -0.422872 "Reaction T" => -0.0013608 "Reaction G" => 0.0 "Reaction B" => -0.0 "Reaction J" => 0.000486989 "Reaction K" => 0.000486989 "Reaction P" => -3.52357e-11 "Reaction R" => -0.0 "Reaction U" => 1.28067e-9 "Reaction Y" => -0.0 "Reaction N" => -0.0 "Reaction Z" => -0.14112 "Reaction D" => -0.0470399 "Reaction W" => 0.000120942 "Reaction M" => -0.0 "Reaction X" => 0.000241884 "Reaction C" => 1.10241e-10 "Reaction A" => 1.38911e-11 ⋮ => ⋮
To be able to use the model conveniently in functions such as screen
, you usually want to be able to easily specify the modifications. In this example, we enable use of with_removed_reactions
by overloading the internal remove_reactions
for this specific model type:
We need to make an as-shallow-as-possible copy of the model that allows us to remove the reactions without breaking the original model.
function COBREXA.remove_reactions(m::MyModel, rxns::AbstractVector{String})
m = MyModel(m.optimization_target, copy(m.reactions))
delete!.(Ref(m.reactions), rxns)
return m
end
The screening is ready now!
reactions_to_remove = ("Reaction $i" for i = 'B':'Z')
reactions_to_remove .=> screen_variants(
m,
[[with_removed_reactions([r])] for r in reactions_to_remove],
m -> flux_balance_analysis_dict(m, Tulip.Optimizer)["Reaction A"],
)
25-element Vector{Pair{String, Float64}}: "Reaction B" => 1.3891081053642988e-11 "Reaction C" => -6.053049545510096e-11 "Reaction D" => 1.2248420082882448e-11 "Reaction E" => 1.3891081053642988e-11 "Reaction F" => 1.3891081053642988e-11 "Reaction G" => 1.3891081053642988e-11 "Reaction H" => 1.6597522344141004e-11 "Reaction I" => 1.3891081053642988e-11 "Reaction J" => 1.7764900680738363e-11 "Reaction K" => 1.3247412533182346e-11 ⋮ "Reaction R" => 1.3891081053642988e-11 "Reaction S" => 5.991945842008422e-10 "Reaction T" => 1.3686861323282557e-11 "Reaction U" => 1.2424891707562945e-9 "Reaction V" => 1.6597522344141004e-11 "Reaction W" => 1.3595610516296605e-11 "Reaction X" => 8.980140092800575e-12 "Reaction Y" => 1.3891081053642988e-11 "Reaction Z" => 1.3891081053642988e-11
This page was generated using Literate.jl.