CycleFreeFlux
CycleFreeFlux essentially defines a L1-parsimonious model which can be used to run a cycle-free FBA and FVA. In COBREXA, this is best done by reusing linear_parsimonious_flux_balance_analysis
.
First, let's get a model, create a constraint tree with the model, and ask for explicitly materializing constraints for the exchanges:
using COBREXA
download_model(
"http://bigg.ucsd.edu/static/models/e_coli_core.json",
"e_coli_core.json",
"7bedec10576cfe935b19218dc881f3fb14f890a1871448fc19a9b4ee15b448d8",
)
import JSONFBCModels, HiGHS
model = load_model("e_coli_core.json")
cs = flux_balance_constraints(model, interface = :identifier_prefixes)
ConstraintTrees.Tree{ConstraintTrees.Constraint} with 5 elements:
:coupling => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 0 …
:flux_stoichiometry => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 72…
:fluxes => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 95…
:interface => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 3 …
:objective => ConstraintTrees.Constraint(ConstraintTrees.LinearValue…
We will also need some existing solution of the model – CycleFreeFlux algorithm uses this one as a reference for fixing the exchange reaction flux.
some_flux =
optimized_values(cs, objective = cs.objective.value, optimizer = HiGHS.Optimizer)
ConstraintTrees.Tree{Float64} with 5 elements:
:coupling => ConstraintTrees.Tree{Float64}(#= 0 elements =#)
:flux_stoichiometry => ConstraintTrees.Tree{Float64}(#= 72 elements =#)
:fluxes => ConstraintTrees.Tree{Float64}(#= 95 elements =#)
:interface => ConstraintTrees.Tree{Float64}(#= 3 elements =#)
:objective => 0.873922
(Ideally, we should use a solving method that gives a more unique flux, but for this example a simple FBA optimum will do.)
With this in hand, we can start the CycleFreeFlux workflow by placing constraints on exchange reactions in a linear-parsimonious model:
import ConstraintTrees as C
cs = linear_parsimonious_flux_balance_constraints(model)
cs *=
:fixed_exchanges^C.ConstraintTree(
k => C.Constraint(cs.fluxes[k].value, relative_tolerance_bound(0.999)(v)) for
(k, v) in some_flux.interface.exchanges
)
ConstraintTrees.Tree{ConstraintTrees.Constraint} with 8 elements:
:coupling => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#…
:fixed_exchanges => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#…
:flux_stoichiometry => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#…
:fluxes => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#…
:fluxes_forward => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#…
:fluxes_reverse => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#…
:objective => ConstraintTrees.Constraint(ConstraintTrees.LinearV…
:parsimonious_objective => ConstraintTrees.Constraint(ConstraintTrees.LinearV…
(We purposefully made the constraints a little less strict by using relative_tolerance_bound
– the toy E. coli model would otherwise display no variability at all.)
Now we can get a L1-parsimonious (thus cycle-free) solution of the model with the predefined exchanges:
cycle_free_flux = parsimonious_optimized_values(
cs,
objective = cs.objective.value,
objective_value = some_flux.objective,
parsimonious_objective = cs.parsimonious_objective.value,
optimizer = HiGHS.Optimizer,
)
cycle_free_flux.fluxes
ConstraintTrees.Tree{Float64} with 95 elements:
:ACALD => 0.0
:ACALDt => 0.0
:ACKr => 0.0
:ACONTa => 6.00725
:ACONTb => 6.00725
:ACt2r => 0.0
:ADK1 => 0.0
:AKGDH => 5.06438
:AKGt2r => 0.0
:ALCD2x => 0.0
:ATPM => 8.39
:ATPS4r => 45.514
:BIOMASS_Ecoli_core_w_GAM => 0.873922
:CO2t => -22.8098
:CS => 6.00725
:CYTBD => 43.599
:D_LACt2 => 0.0
:ENO => 14.7161
:ETOHt2r => 0.0
⋮ => ⋮
CycleFreeFVA
With this in hand, we can also run the cycle-free flux variability analysis (again with an added bit of tolerances in both the objective and parsimonious bounds):
cs.objective.bound = C.Between(cycle_free_flux.objective * 0.999, Inf)
cs.parsimonious_objective.bound =
C.Between(0, cycle_free_flux.parsimonious_objective * 1.001)
var = constraints_variability(cs, cs.fluxes, optimizer = HiGHS.Optimizer)
ConstraintTrees.Tree{Tuple{Union{Nothing, Float64}, Union{Nothing, Float64}}} with 95 elements:
:ACALD => (-0.0, 0.0)
:ACALDt => (-0.0, 0.0)
:ACKr => (-0.0, 0.0)
:ACONTa => (5.92071, 6.08235)
:ACONTb => (5.92071, 6.08235)
:ACt2r => (-0.0, 0.0)
:ADK1 => (-0.0, 0.108535)
:AKGDH => (4.48889, 5.14042)
:AKGt2r => (-0.0, 0.0)
:ALCD2x => (-0.0, 0.0)
:ATPM => (8.39, 8.51247)
:ATPS4r => (45.3147, 45.6379)
:BIOMASS_Ecoli_core_w_GAM => (0.873048, 0.873922)
:CO2t => (-22.8311, -22.7898)
:CS => (5.92071, 6.08235)
:CYTBD => (43.561, 43.6426)
:D_LACt2 => (-0.0, 6.0633e-14)
:ENO => (14.6209, 14.7825)
:ETOHt2r => (-0.0, 0.0)
⋮ => ⋮
CycleFree sampling
Naturally, we can also run flux sampling from the above model. To implement this, we follow the implementation of flux_sample
–- first we generate the warmup:
warmup = vcat(
(
transpose(v) for (_, vs) in constraints_variability(
cs,
cs.fluxes,
optimizer = HiGHS.Optimizer,
output = (_, om) -> variable_vector(om),
output_type = Vector{Float64},
) for v in vs
)...,
)
190×190 Matrix{Float64}:
7.3821 -0.0 4.57806 -15.927 … -0.0 -0.0 -0.0 -0.0
7.3821 -0.0 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.3821 -0.0 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.3821 -0.0 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.3821 -0.0 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.3821 -0.0 4.57806 -15.927 … -0.0 -0.0 -0.0 -0.0
7.3821 -0.0 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.54579 -0.0 5.0732 -16.0886 -0.0 -0.0 6.21725e-15 -0.0
7.3821 -0.0 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.54579 -0.0 5.0732 -16.0886 -0.0 -0.0 6.21725e-15 -0.0
⋮ ⋱
7.3821 -0.0 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.40268 -0.0 4.63538 -15.9497 -0.0 -0.0 -0.0 -0.0
7.54579 -0.0 5.0732 -16.0886 -0.0 -0.0 -0.0 -0.0
7.54579 -0.0 5.0732 -16.0886 -0.0 -0.0 -0.0 -0.0
7.3821 -5.86198e-14 4.57806 -15.927 … -0.0 -0.0 -0.0 -0.0
7.54579 -0.0 5.0732 -16.0886 -0.0 -0.0 -0.0 -0.0
7.3821 -5.86198e-14 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.3821 -0.0 4.57806 -15.927 -0.0 -0.0 -0.0 -0.0
7.54579 -0.0 5.0732 -16.0886 -0.0 -0.0 -0.0 -0.0
Next, we can run the sampling:
sample = sample_constraints(
sample_chain_achr,
cs,
start_variables = warmup,
seed = UInt(1234),
output = cs.fluxes,
n_chains = 10,
collect_iterations = collect(10:15),
)
ConstraintTrees.Tree{Vector{Float64}} with 95 elements:
:ACALD => [-2.86477e-15, -5.80857e-15, -6.303e-15, -4.2159…
:ACALDt => [-1.69876e-15, -2.82671e-15, -3.48804e-15, 8.023…
:ACKr => [-5.1976e-15, -4.94937e-15, -4.77809e-15, -4.416…
:ACONTa => [6.0017, 5.99204, 5.99008, 5.98695, 6.00876, 5.9…
:ACONTb => [6.0017, 5.99204, 5.99008, 5.98695, 6.00876, 5.9…
:ACt2r => [-5.26414e-15, -4.94501e-15, -4.80578e-15, -4.51…
:ADK1 => [0.0041622, 0.00376928, 0.00249937, 0.00247738, …
:AKGDH => [4.99378, 4.91115, 4.90038, 4.89263, 5.06269, 4.…
:AKGt2r => [2.03481e-15, 3.56293e-15, 4.05086e-15, 2.00581e…
:ALCD2x => [-1.39845e-15, -3.11776e-15, -2.98776e-15, -5.18…
:ATPM => [8.39128, 8.39161, 8.39152, 8.3916, 8.39043, 8.3…
:ATPS4r => [45.5167, 45.529, 45.5312, 45.5407, 45.5107, 45.…
:BIOMASS_Ecoli_core_w_GAM => [0.873185, 0.873134, 0.873139, 0.873156, 0.87326…
:CO2t => [-22.8194, -22.821, -22.8213, -22.8214, -22.8177…
:CS => [6.0017, 5.99204, 5.99008, 5.98695, 6.00876, 5.9…
:CYTBD => [43.6198, 43.6232, 43.6237, 43.624, 43.6162, 43.…
:D_LACt2 => [-2.25102e-14, -2.26823e-14, -1.8228e-14, -2.554…
:ENO => [14.7032, 14.6931, 14.6912, 14.6882, 14.7111, 14…
:ETOHt2r => [-1.55563e-15, -3.30731e-15, -3.15731e-15, -5.35…
⋮ => ⋮
The results can be observed (and usually plotted) from the sample vectors, such as the one for oxygen exchange:
sample.EX_o2_e
11400-element Vector{Float64}:
-21.809910675462316
-21.811593820339848
-21.81186604603884
-21.81199036604807
-21.808086247677952
-21.8117797245191
-21.811417309355413
-21.811108656969214
-21.81115053554036
-21.811432268848144
⋮
-21.811507003496565
-21.812028633557663
-21.81105149470561
-21.81086295219901
-21.810914110886465
-21.811205956007473
-21.813364572555663
-21.80963333194993
-21.81300524702459
This page was generated using Literate.jl.