Thermodynamic models

using COBREXA

Here we will solve the max min driving force analysis problem using the glycolysis pathway of E. coli. In essence, the method attempts to find metabolite concentrations (NB: not fluxes) that maximize the smallest thermodynamic driving force through each reaction. See Noor, et al., "Pathway thermodynamics highlights kinetic obstacles in central metabolism.", PLoS computational biology, 2014, for more details.

To do this, we will first need a model that includes glycolysis, which we can download if it is not already present.

using COBREXA

download_model(
    "http://bigg.ucsd.edu/static/models/e_coli_core.json",
    "e_coli_core.json",
    "7bedec10576cfe935b19218dc881f3fb14f890a1871448fc19a9b4ee15b448d8",
)
"e_coli_core.json"

Additionally to COBREXA, and the model format package, we will need a solver – let's use HiGHS here:

import JSONFBCModels
import HiGHS

model = load_model("e_coli_core.json")
JSONFBCModels.JSONFBCModel(#= 95 reactions, 72 metabolites =#)

Thermodynamic data

We will need ΔᵣG⁰ data for each reaction we want to include in the thermodynamic model. To generate this data manually, use eQuilibrator. To generate automatically, it is possible to use the eQuilibrator.jl package.

reaction_standard_gibbs_free_energies = Dict{String,Float64}( # units of the energies are kJ/mol
    "ENO" => -3.8108376097261782,
    "FBA" => 23.376920310319235,
    "GAPD" => 0.5307809794271634,
    "GLCpts" => -45.42430981510088,
    "LDH_D" => 20.04059765689044,
    "PFK" => -18.546314942995934,
    "PGI" => 2.6307087407442395,
    "PGK" => 19.57192102020454,
    "PGM" => -4.470553692565886,
    "PYK" => -24.48733600711958,
    "TPI" => 5.621932460512994,
)
Dict{String, Float64} with 11 entries:
  "PFK"    => -18.5463
  "PGI"    => 2.63071
  "GLCpts" => -45.4243
  "PGK"    => 19.5719
  "TPI"    => 5.62193
  "LDH_D"  => 20.0406
  "ENO"    => -3.81084
  "PYK"    => -24.4873
  "FBA"    => 23.3769
  "GAPD"   => 0.530781
  "PGM"    => -4.47055

Running basic max min driving force analysis

If a reference flux is not specified, it is assumed that every reaction in the model should be included in the thermodynamic model, and that each reaction proceeds in the forward direction. This is usually not intended, and can be prevented by inputting a reference flux dictionary as shown below. This dictionary can be a flux solution. The sign of each flux is used to determine if the reaction runs forward or backward.

Using a reference solution

Frequently it is useful to check the max-min driving force of a specific FBA solution. In this case, one is usually only interested in a subset of all the reactions in a model. These reactions can be specified as a the reference_flux, to only compute the MMDF of these reactions, and ignore all other reactions.

reference_flux = Dict(
    "ENO" => 1.0,
    "FBA" => 1.0,
    "GAPD" => 1.0,
    "GLCpts" => 1.0,
    "LDH_D" => -1.0,
    "PFK" => 1.0,
    "PGI" => 1.0,
    "PGK" => -1.0,
    "PGM" => 0.0,
    "PYK" => 1.0,
    "TPI" => 1.0,
)
Dict{String, Float64} with 11 entries:
  "PFK"    => 1.0
  "PGI"    => 1.0
  "GLCpts" => 1.0
  "PGK"    => -1.0
  "TPI"    => 1.0
  "LDH_D"  => -1.0
  "ENO"    => 1.0
  "PYK"    => 1.0
  "FBA"    => 1.0
  "GAPD"   => 1.0
  "PGM"    => 0.0
Only the signs are extracted from the reference solution

It is most convenient to pass a flux solution into reference_flux, but take care about the fluxes with value near 0: Their desired sign may be a subject to floating-point robustness error. By default, max_min_driving_force_analysis considers everything that is approximately zero (via isapprox) to have zero flux, with the appropriate implications to concentration balance.

Solving the MMDF problem

mmdf_solution = max_min_driving_force_analysis(
    model;
    reaction_standard_gibbs_free_energies,
    reference_flux,
    constant_concentrations = Dict("g3p_c" => exp(-8.5)),
    concentration_ratios = Dict(
        "atp" => ("atp_c", "adp_c", 10.0),
        "nadh" => ("nadh_c", "nad_c", 0.13),
    ),
    proton_metabolites = ["h_c"],
    water_metabolites = ["h2o_c"],
    concentration_lower_bound = 1e-6, # mol/L
    concentration_upper_bound = 1e-1, # mol/L
    T = 298.15, # Kelvin
    R = 8.31446261815324e-3, # kJ/K/mol
    optimizer = HiGHS.Optimizer,
)
ConstraintTrees.Tree{Float64} with 6 elements:
  :concentration_ratio_constraints => ConstraintTrees.Tree{Float64}(#= 2 elemen…
  :driving_forces                  => ConstraintTrees.Tree{Float64}(#= 11 eleme…
  :log_concentration_stoichiometry => ConstraintTrees.Tree{Float64}(#= 11 eleme…
  :log_concentrations              => ConstraintTrees.Tree{Float64}(#= 17 eleme…
  :min_driving_force               => 1.88051
  :min_driving_force_thresholds    => ConstraintTrees.Tree{Float64}(#= 11 eleme…

One may be also interested in seeing the FVA-like feasible concentration ranges in such model. The most straightforward way to find these is to use the associated constraint-system-building function max_min_driving_force_constraints together with constraints_variability as follows:

mmdf_system = max_min_driving_force_constraints(
    model;
    reaction_standard_gibbs_free_energies,
    reference_flux,
    constant_concentrations = Dict("g3p_c" => exp(-8.5)),
    concentration_ratios = Dict(
        "atp" => ("atp_c", "adp_c", 10.0),
        "nadh" => ("nadh_c", "nad_c", 0.13),
    ),
    proton_metabolites = ["h_c"],
    water_metabolites = ["h2o_c"],
    concentration_lower_bound = 1e-6, # mol/L
    concentration_upper_bound = 1e-1, # mol/L
    T = 298.15, # Kelvin
    R = 8.31446261815324e-3, # kJ/K/mol
)

cva_solution = constraints_variability(
    mmdf_system,
    mmdf_system.log_concentrations,
    objective = mmdf_system.min_driving_force.value,
    optimizer = HiGHS.Optimizer,
)
ConstraintTrees.Tree{Tuple{Union{Nothing, Float64}, Union{Nothing, Float64}}} with 17 elements:
  Symbol("13dpg_c") => (-13.8155, -13.0569)
  Symbol("2pg_c")   => (-13.8155, -4.66251)
  Symbol("3pg_c")   => (-12.0121, -2.85911)
  :adp_c            => (-11.5129, -2.30259)
  :atp_c            => (-13.8155, -4.60517)
  :dhap_c           => (-6.23214, -3.23273)
  :f6p_c            => (-10.4809, -3.3638)
  :fdp_c            => (-5.30199, -2.30259)
  :g3p_c            => (-8.5, -8.5)
  :g6p_c            => (-9.41969, -2.30259)
  :glc__D_e         => (-13.8155, -2.30259)
  :lac__D_c         => (-13.8155, -2.30259)
  :nad_c            => (-13.8155, -4.34281)
  :nadh_c           => (-11.7753, -2.30259)
  :pep_c            => (-13.8155, -3.12524)
  :pi_c             => (-3.06118, -2.30259)
  :pyr_c            => (-13.8155, -2.30259)

This page was generated using Literate.jl.