Simplified resource balance analysis

Resource balance analysis (RBA) models are extensions of enzyme constrained models that additionally incorporate other cellular mechanisms, such as translation, transcription and replication. This requires much more mechanistic knowledge about the processes, but also dramatically improves the predictive capability of the model.

Here we demonstrate the approach for building such extensions with COBREXA, over a demonstrational simplified RBA model that accounts for the major translation costs (synthesis of proteins and ribosomes).

For comprehensiveness, we use the full genome-scale model of E. coli (iML1515):

using COBREXA

download_model(
    "http://bigg.ucsd.edu/static/models/iML1515.json",
    "iML1515.json",
    "b0f9199f048779bb08a14dfa6c09ec56d35b8750d2f99681980d0f098355fbf5",
)
"iML1515.json"

We use several packages as usual; additionally we import ConstraintTrees for later modifications.

import AbstractFBCModels as A
import JSONFBCModels
import HiGHS
import ConstraintTrees as C

Data and parameters for the RBA model

For the purposes of this example, COBREXA comes with example data for the whole iML1515 model, aggregated from several publications and databases. In this section we simply load the data into suitable Julia structures. Other data formats may work just as well.

The loading is hidden by default for brevity:

Loading the RBA model parameters
import CSV

data_dir = joinpath(dirname(pathof(COBREXA)), "..", "docs", "src", "examples", "data");

e_coli_gp_mass = Dict{String,Float64}(
    x.gene_product => x.mass for
    x in CSV.File(joinpath(data_dir, "e_coli_gp_mass.tsv"), delim = '\t')
);

kcat_scale = 3600 / 1e3;
e_coli_rxn_kcat_isozyme = Dict{String,Isozyme}(
    x.reaction => Isozyme(
        kcat_forward = x.kcat * kcat_scale,
        kcat_reverse = x.kcat * kcat_scale,
        gene_product_stoichiometry = Dict(),
    ) for x in CSV.File(joinpath(data_dir, "e_coli_reaction_kcat.tsv"), delim = '\t')
);

e_coli_rxn_isozymes = Dict{String,Dict{String,Isozyme}}();
for x in CSV.File(joinpath(data_dir, "e_coli_isozyme_gp.tsv"), delim = '\t')
    haskey(e_coli_rxn_kcat_isozyme, x.reaction) || continue
    rxn = get!(e_coli_rxn_isozymes, x.reaction, Dict{String,Isozyme}())
    iso = get!(rxn, x.isozyme, deepcopy(e_coli_rxn_kcat_isozyme[x.reaction]))
    iso.gene_product_stoichiometry[x.gene_product] = x.stoichiometry
end;

e_coli_gp_aas = Dict{String,Dict{Symbol,Int}}(
    begin
        d = Dict(keys(x) .=> values(x))
        gp = d[:gene_product]
        delete!(d, :gene_product)
        gp => d
    end for x in CSV.File(joinpath(data_dir, "e_coli_gp_aa.tsv"), delim = '\t')
);

amino_acids = Set(Symbol(aa) for (k, v) in e_coli_gp_aas for (aa, _) in v);

In the end, we have gene product weight data (just like in the enzyme-constrained model example):

e_coli_gp_mass
Dict{String, Float64} with 1517 entries:
  "b1329" => 59.9
  "b3236" => 32.337
  "b0688" => 58.361
  "b2052" => 36.141
  "b0832" => 33.238
  "b0586" => 141.991
  "b2245" => 28.916
  "b1759" => 15.046
  "b3772" => 56.195
  "b1692" => 54.58
  "b3850" => 21.226
  "b1006" => 45.557
  "b3428" => 93.173
  "b0635" => 70.857
  "b1387" => 73.003
  "b1588" => 89.987
  "b2541" => 28.5
  "b4478" => 42.523
  "b2143" => 31.54
  ⋮       => ⋮

... as well as isozyme data with kcats:

e_coli_rxn_isozymes
Dict{String, Dict{String, Isozyme}} with 2266 entries:
  "PACOAT"        => Dict("iso1"=>Isozyme(Dict("b1396"=>4.0), 33.9551, 33.9551))
  "Zn2tex"        => Dict("iso3"=>Isozyme(Dict("b1377"=>3.0), 648.0, 648.0), "i…
  "GUI1"          => Dict("iso1"=>Isozyme(Dict("b3092"=>1.0), 21.4406, 21.4406))
  "DXYLK"         => Dict("iso1"=>Isozyme(Dict("b3564"=>2.0), 28.7528, 28.7528))
  "CBL1tonex"     => Dict("iso1"=>Isozyme(Dict("b3005"=>1.0, "b3006"=>4.0, "b39…
  "FE3DCITtonex"  => Dict("iso1"=>Isozyme(Dict("b3005"=>3.0, "b3006"=>6.0, "b42…
  "FACOAL180t2pp" => Dict("iso1"=>Isozyme(Dict("b1701"=>2.0), 28.4896, 28.4896)…
  "METSOXR1"      => Dict("iso3"=>Isozyme(Dict("b3781"=>1.0, "b3551"=>1.0), 115…
  "LIPOtex"       => Dict("iso3"=>Isozyme(Dict("b1377"=>3.0), 648.0, 648.0), "i…
  "NTD11"         => Dict("iso1"=>Isozyme(Dict("b2744"=>1.0), 24.6906, 24.6906)…
  "GLUNpp"        => Dict("iso1"=>Isozyme(Dict("b2957"=>4.0), 18.2572, 18.2572))
  "ORNDC"         => Dict("iso1"=>Isozyme(Dict("b0693"=>1.0), 130.25, 130.25), …
  "ALAGLUE"       => Dict("iso1"=>Isozyme(Dict("b1325"=>1.0), 24.1219, 24.1219))
  "UAGCVT"        => Dict("iso1"=>Isozyme(Dict("b3189"=>1.0), 11.88, 11.88))
  "I2FE2ST"       => Dict("iso1"=>Isozyme(Dict("b2529"=>1.0, "b2528"=>1.0), 11.…
  "6D6SPA"        => Dict("iso1"=>Isozyme(Dict("b3881"=>1.0), 149.289, 149.289))
  "BMOCOS"        => Dict("iso1"=>Isozyme(Dict("b0827"=>2.0), 0.072, 0.072))
  "GLCURt2rpp"    => Dict("iso1"=>Isozyme(Dict("b3093"=>1.0), 648.0, 648.0), "i…
  "PGSA141"       => Dict("iso1"=>Isozyme(Dict("b1912"=>1.0), 40.7639, 40.7639))
  ⋮               => ⋮

We additionally need a list of amino acids in the model:

amino_acids
Set{Symbol} with 20 elements:
  :pro__L_c
  :gln__L_c
  :asn__L_c
  :glu__L_c
  :cys__L_c
  :tyr__L_c
  :asp__L_c
  :phe__L_c
  :arg__L_c
  :gly_c
  :met__L_c
  :ser__L_c
  :thr__L_c
  :his__L_c
  :val__L_c
  :ile__L_c
  :trp__L_c
  :leu__L_c
  :ala__L_c
  :lys__L_c

...together with a list of how much amino acids there are in which gene product:

e_coli_gp_aas
Dict{String, Dict{Symbol, Int64}} with 4259 entries:
  "b1329" => Dict(:ala__L_c=>51, :lys__L_c=>39, :pro__L_c=>34, :gln__L_c=>27, :…
  "b2531" => Dict(:ala__L_c=>13, :lys__L_c=>6, :pro__L_c=>4, :gln__L_c=>7, :asn…
  "b3236" => Dict(:ala__L_c=>35, :lys__L_c=>21, :pro__L_c=>13, :gln__L_c=>14, :…
  "b0688" => Dict(:ala__L_c=>67, :lys__L_c=>29, :pro__L_c=>28, :gln__L_c=>18, :…
  "b3834" => Dict(:ala__L_c=>27, :lys__L_c=>11, :pro__L_c=>8, :gln__L_c=>8, :as…
  "b1604" => Dict(:ala__L_c=>46, :lys__L_c=>26, :pro__L_c=>11, :gln__L_c=>12, :…
  "b0832" => Dict(:ala__L_c=>39, :lys__L_c=>4, :pro__L_c=>14, :gln__L_c=>8, :as…
  "b1339" => Dict(:ala__L_c=>23, :lys__L_c=>11, :pro__L_c=>14, :gln__L_c=>29, :…
  "b3609" => Dict(:ala__L_c=>14, :lys__L_c=>3, :pro__L_c=>7, :gln__L_c=>14, :as…
  "b1282" => Dict(:ala__L_c=>8, :lys__L_c=>14, :pro__L_c=>3, :gln__L_c=>3, :asn…
  "b4754" => Dict(:ala__L_c=>1, :lys__L_c=>7, :pro__L_c=>0, :gln__L_c=>0, :asn_…
  "b0586" => Dict(:ala__L_c=>160, :lys__L_c=>25, :pro__L_c=>92, :gln__L_c=>83, …
  "b3772" => Dict(:ala__L_c=>60, :lys__L_c=>21, :pro__L_c=>21, :gln__L_c=>19, :…
  "b1759" => Dict(:ala__L_c=>20, :lys__L_c=>3, :pro__L_c=>8, :gln__L_c=>9, :asn…
  "b1884" => Dict(:ala__L_c=>23, :lys__L_c=>7, :pro__L_c=>11, :gln__L_c=>15, :a…
  "b3505" => Dict(:ala__L_c=>28, :lys__L_c=>19, :pro__L_c=>12, :gln__L_c=>22, :…
  "b3850" => Dict(:ala__L_c=>16, :lys__L_c=>12, :pro__L_c=>7, :gln__L_c=>7, :as…
  "b1006" => Dict(:ala__L_c=>53, :lys__L_c=>8, :pro__L_c=>20, :gln__L_c=>9, :as…
  "b4283" => Dict(:ala__L_c=>7, :lys__L_c=>12, :pro__L_c=>3, :gln__L_c=>6, :asn…
  ⋮       => ⋮

To make the RBA problem working, we also need to assume some constant parameters (many of such can be found via https://bionumbers.hms.harvard.edu):

atp_polymerization_cost = 0.042;
protein_polymerization_atp_per_gDW = 12.0;
ribosome_speed_aa_per_hour = 12.0 * 3600;
ribosome_molar_mass = 2700.0;

We also need a stoichiometry for "energy consumption" reaction, which we will use to simulate the energy cost of translation:

energy_stoichiometry = Dict(:atp_c => -1, :h2o_c => -1, :adp_c => 1, :h_c => 1, :pi_c => 1)
Dict{Symbol, Int64} with 5 entries:
  :atp_c => -1
  :h_c   => 1
  :pi_c  => 1
  :h2o_c => -1
  :adp_c => 1

Model assembly

Enzyme-constrained base model

First, we load the model in a format that is suitable for doing small changes:

model = load_model("iML1515.json")
JSONFBCModels.JSONFBCModel(#= 2712 reactions, 1877 metabolites =#)

We will require some access to the stoichiometry of the biomass reaction (in essence, we copy a part of it, but replace the part that uses amino acids as a building material, and slightly enhance the energy consumption part). So we save it here:

biomass = Dict(
    Symbol(k) => v for
    (k, v) in A.reaction_stoichiometry(model, "BIOMASS_Ec_iML1515_core_75p37M")
)
Dict{Symbol, Float64} with 70 entries:
  :sheme_c          => -0.000223
  :gtp_c            => -0.215096
  :ppi_c            => 0.773903
  :mg2_c            => -0.008675
  :pydx5p_c         => -0.000223
  :fe3_c            => -0.007808
  :zn2_c            => -0.000341
  :nh4_c            => -0.013013
  :phe__L_c         => -0.185265
  :ni2_c            => -0.000323
  :nadp_c           => -0.000447
  :met__L_c         => -0.153686
  :thr__L_c         => -0.253687
  :atp_c            => -75.5522
  :cl_c             => -0.005205
  Symbol("2ohph_c") => -0.000223
  :dctp_c           => -0.027017
  :k_c              => -0.195193
  :mlthf_c          => -0.000223
  ⋮                 => ⋮

We can create the enzyme-constrained model for iML1515. This will be extended later.

ec_constraints = enzyme_constrained_flux_balance_constraints(
    model;
    reaction_isozymes = e_coli_rxn_isozymes,
    gene_product_molar_masses = e_coli_gp_mass,
    capacity = 550.0,
)
ConstraintTrees.ConstraintTree with 12 elements:
  :coupling                     => ConstraintTrees.ConstraintTree(#= 0 elements…
  :flux_stoichiometry           => ConstraintTrees.ConstraintTree(#= 1877 eleme…
  :fluxes                       => ConstraintTrees.ConstraintTree(#= 2712 eleme…
  :fluxes_forward               => ConstraintTrees.ConstraintTree(#= 2712 eleme…
  :fluxes_reverse               => ConstraintTrees.ConstraintTree(#= 2712 eleme…
  :gene_product_amounts         => ConstraintTrees.ConstraintTree(#= 1496 eleme…
  :gene_product_capacity        => ConstraintTrees.ConstraintTree(#= 1 element …
  :isozyme_flux_forward_balance => ConstraintTrees.ConstraintTree(#= 2266 eleme…
  :isozyme_flux_reverse_balance => ConstraintTrees.ConstraintTree(#= 2266 eleme…
  :isozyme_forward_amounts      => ConstraintTrees.ConstraintTree(#= 2266 eleme…
  :isozyme_reverse_amounts      => ConstraintTrees.ConstraintTree(#= 2266 eleme…
  :objective                    => ConstraintTrees.Constraint(ConstraintTrees.L…

Before we continue, we apply a small quirk to remove an artificial limit on the glucose intake. (The limit is required to prevent "infinite" growth in simplistic FBA-style analysis; in our case the enzyme capacity serves as a sufficient and more realistic limiter. We have to unblock both the bidirectional reaction and the "reversed" view, since both carry the bound.)

ec_constraints.fluxes.EX_glc__D_e.bound.lower = -1000;
ec_constraints.fluxes_reverse.EX_glc__D_e.bound.upper = 1000;

To avoid the model from growing in unexpected modes, we will constraint the original biomass reaction to zero:

ec_constraints.fluxes.BIOMASS_Ec_iML1515_core_75p37M.bound = C.EqualTo(0);
ec_constraints.fluxes.BIOMASS_Ec_iML1515_WT_75p37M.bound = C.EqualTo(0);

RBA translation machinery

A common issue with RBA formulations is that the biomass-based growth formula depends on a determined optimal composition of the enzyme pool and actual production of metabolites; which makes the underlying constrained problem quadratic.

A common way to dodge the need for quadratic solvers is to solve the problem for a fixed growth rate, which we is the approach that we choose here. Alternatively, one might state the full quadratic problem and solve it, with some performance cost stemming from use of QP solvers.

Let's first make a utility function that prepares the connection to the metabolite pool, and adds several useful variables atop a given enzyme-constrained model:

function with_translation_variables(ec_constraints::C.ConstraintTree)

Create a "resource pool" and connect it to the stoichiometry of the intracellular (and other) metabolites. (This effectively creates new exchange reactions in ec_constraints.)

    resources = C.variables(
        keys = Symbol.(
            collect(union(amino_acids, keys(energy_stoichiometry), keys(biomass)))
        ),
    )
    (cs, rs) =
        inject_interface(ec_constraints, :flux_stoichiometry^resources, multiplier = -1)

Also add a single new variable for the production of ribosomes by ribosomes.

    return cs * :resources^rs.flux_stoichiometry +
           :ribosome_production^C.variable(; bound = (0, Inf))
end
with_translation_variables (generic function with 1 method)

To make the construction nicer, we'll make a helper for summing up constraint-tree values:

sum_values(x...) = C.sum(x..., init = zero(C.LinearValue));

...and another helper for adding values in constraint trees together:

add_trees(ts...) =
    C.preduce(ts, init = C.ConstraintTree()) do t1, t2
        z(::Missing) = zero(C.LinearValue)
        z(x) = C.value(x)
        return C.merge(t1, t2) do c1, c2
            C.Constraint(z(c1) + z(c2))
        end
    end;

Since we have to solve the problem for multiple growth rates to be able to scan for optimum, we will wrap the growth-dependent part in a reusable function:

function translation_constraints(
    resources::C.ConstraintTree,
    ribos_required_for_ribos::C.Constraint,
    gene_product_amounts::C.ConstraintTree,
    growth::Float64,
)

First we can calculate how much amino acids we need to build the expected amount of gene products:

    aas_required_for_gps = C.imap(gene_product_amounts) do (i,), gp
        C.ConstraintTree(
            aa => C.Constraint(gp.value * v * growth * 0.001) for
            (aa, v) in e_coli_gp_aas[String(i)]
        )
    end

This allows us to calculate how much ribosome we have to produce to make the production of all of the above enzymes possible:

    ribo_required_for_gps = C.ConstraintTree(
        i => C.Constraint(
            sum_values(aa.value for (_, aa) in aas) / ribosome_speed_aa_per_hour,
        ) for (i, aas) in aas_required_for_gps
    )

Now we know the total amount of ribosome to produce (both for the above protein production and for production of ribosomes itself) so we can see how much amino acids in total are required for production of ribosomes:

    total_ribos_required =
        ribos_required_for_ribos.value +
        sum_values(c.value for (_, c) in ribo_required_for_gps)
    aas_required_for_ribos = C.ConstraintTree(
        aa => C.Constraint(total_ribos_required * v) for
        (aa, v) in e_coli_gp_aas["ribosome"]
    )

Now we solve a "rocket equation" – the ribosomes need to produce both the protein-producing ribosomes and themselves, so we add a constraint that ensures there's enough ribosomes for both.

    ribosome_balance_constraint = equal_value_constraint(
        sum_values(aa.value for (_, aa) in aas_required_for_ribos),
        ribos_required_for_ribos.value * ribosome_speed_aa_per_hour,
    )

With the AA requirements solved, we can estimate how much energy we need for polymerization of the proteins and ribosomes:

    energy_required =
        atp_polymerization_cost *
        sum_values(aa.value for (_, aas) in aas_required_for_gps for (_, aa) in aas) +
        sum_values(v.value for (_, v) in aas_required_for_ribos)

With all that in hand, we can put together the final resource consumption:

    resource_consumption = add_trees(
        C.values(aas_required_for_gps)...,
        aas_required_for_ribos,
        C.map(stoi -> -stoi * energy_required, C.Tree{Int}(energy_stoichiometry), C.Value),
    )

...and make a stoichiometry out of that, with exact cases for amino acids (these are completely replaced in the original biomass), energy metabolites (these are partially re-used from the original biomass, but with an adjustment that tries to remove the polymerization cost portion in the original model), and everything other scaled for growth:

    resource_stoichiometry = C.imap(resources) do (resource,), input
        if resource in amino_acids # AA case
            equal_value_constraint(resource_consumption[resource], input)
        elseif resource in keys(energy_stoichiometry) # energy case
            equal_value_constraint(
                -growth * (
                    biomass[resource] -
                    energy_stoichiometry[resource] * protein_polymerization_atp_per_gDW
                ) -
                resource_consumption[resource].value * energy_stoichiometry[resource],
                input,
            )
        else # everything else
            C.Constraint(input.value, -growth * biomass[resource])
        end
    end

Finally, let's wrap all the constraints and some useful derived helper values in one big tree:

    return C.ConstraintTree(
        :resource_stoichiometry => resource_stoichiometry,
        :ribosome_balance => ribosome_balance_constraint,
        :gene_product_production => ribo_required_for_gps,
        :total_ribosome_mass => C.Constraint(
            ribosome_molar_mass * (
                sum_values(v.value for (_, v) in ribo_required_for_gps) +
                ribos_required_for_ribos.value
            ),
        ),
        :amino_acid_use => (aas_required_for_gps * :ribosome^aas_required_for_ribos),
        :polymerization_energy => C.Constraint(energy_required),
        :translation_resource_consumption => resource_consumption,
    )
end
translation_constraints (generic function with 1 method)

Running the resource-balanced simulation

With the above functions, assembling a resource-balanced model amounts to adding new variables and connecting them with the rest of the enzyme-constrained model. We assemble a model for growth value of 0.6 gDW/gDWh:

rb_constraints = with_translation_variables(ec_constraints)
rb_constraints *= translation_constraints(
    rb_constraints.resources,
    rb_constraints.ribosome_production,
    rb_constraints.gene_product_amounts,
    0.9,
)
ConstraintTrees.ConstraintTree with 21 elements:
  :amino_acid_use               => ConstraintTrees.ConstraintTree(#= 1497 eleme…
  :coupling                     => ConstraintTrees.ConstraintTree(#= 0 elements…
  :flux_stoichiometry           => ConstraintTrees.ConstraintTree(#= 1877 eleme…
  :fluxes                       => ConstraintTrees.ConstraintTree(#= 2712 eleme…
  :fluxes_forward               => ConstraintTrees.ConstraintTree(#= 2712 eleme…
  :fluxes_reverse               => ConstraintTrees.ConstraintTree(#= 2712 eleme…
  :gene_product_amounts         => ConstraintTrees.ConstraintTree(#= 1496 eleme…
  :gene_product_capacity        => ConstraintTrees.ConstraintTree(#= 1 element …
  :gene_product_production      => ConstraintTrees.ConstraintTree(#= 1496 eleme…
  :isozyme_flux_forward_balance => ConstraintTrees.ConstraintTree(#= 2266 eleme…
  :isozyme_flux_reverse_balance => ConstraintTrees.ConstraintTree(#= 2266 eleme…
  :isozyme_forward_amounts      => ConstraintTrees.ConstraintTree(#= 2266 eleme…
  :isozyme_reverse_amounts      => ConstraintTrees.ConstraintTree(#= 2266 eleme…
  :objective                    => ConstraintTrees.Constraint(ConstraintTrees.L…
  :polymerization_energy        => ConstraintTrees.Constraint(ConstraintTrees.L…
  :resource_stoichiometry       => ConstraintTrees.ConstraintTree(#= 70 element…
  :resources                    => ConstraintTrees.ConstraintTree(#= 70 element…
  :ribosome_balance             => ConstraintTrees.Constraint(ConstraintTrees.L…
  :ribosome_production          => ConstraintTrees.Constraint(ConstraintTrees.L…
  ⋮                             => ⋮

The model may be slightly under-constrained for less-than-extreme values of growth; to obtain a realistic solution for we can ask the solver to minimize the mass of used resources. Accordingly, we re-constraint the total mass of the model:

rb_constraints.gene_product_capacity.total_capacity.bound = nothing;
rb_constraints.total_capacity = C.Constraint(
    rb_constraints.gene_product_capacity.total_capacity.value +
    rb_constraints.total_ribosome_mass.value,
    (0.0, 550.0),
);

We can optimize the model now, minimizing the mass:

res = optimized_values(
    rb_constraints,
    objective = rb_constraints.total_capacity.value,
    sense = Minimal,
    optimizer = HiGHS.Optimizer,
)
ConstraintTrees.Tree{Float64} with 22 elements:
  :amino_acid_use               => ConstraintTrees.Tree{Float64}(#= 1497 elemen…
  :coupling                     => ConstraintTrees.Tree{Float64}(#= 0 elements …
  :flux_stoichiometry           => ConstraintTrees.Tree{Float64}(#= 1877 elemen…
  :fluxes                       => ConstraintTrees.Tree{Float64}(#= 2712 elemen…
  :fluxes_forward               => ConstraintTrees.Tree{Float64}(#= 2712 elemen…
  :fluxes_reverse               => ConstraintTrees.Tree{Float64}(#= 2712 elemen…
  :gene_product_amounts         => ConstraintTrees.Tree{Float64}(#= 1496 elemen…
  :gene_product_capacity        => ConstraintTrees.Tree{Float64}(#= 1 element =…
  :gene_product_production      => ConstraintTrees.Tree{Float64}(#= 1496 elemen…
  :isozyme_flux_forward_balance => ConstraintTrees.Tree{Float64}(#= 2266 elemen…
  :isozyme_flux_reverse_balance => ConstraintTrees.Tree{Float64}(#= 2266 elemen…
  :isozyme_forward_amounts      => ConstraintTrees.Tree{Float64}(#= 2266 elemen…
  :isozyme_reverse_amounts      => ConstraintTrees.Tree{Float64}(#= 2266 elemen…
  :objective                    => 0.0
  :polymerization_energy        => 1.09189
  :resource_stoichiometry       => ConstraintTrees.Tree{Float64}(#= 70 elements…
  :resources                    => ConstraintTrees.Tree{Float64}(#= 70 elements…
  :ribosome_balance             => 3.33067e-16
  :ribosome_production          => 2.10408e-5
  ⋮                             => ⋮

The model can be used to observe various interesting effects. For example, how much building material is required to reach such growth?

res.total_capacity
538.778495150974

What is the resource composition used for building up biomass?

sort(collect(res.resources), by = last)
70-element Vector{Pair{Symbol, Union{Float64, ConstraintTrees.Tree{Float64}}}}:
            :adp_c => -55.94761600595841
              :h_c => -55.9476160059584
             :pi_c => -55.94401600595841
            :ppi_c => -0.6965127
            :btn_c => 1.8e-6
           :mobd_c => 6.3e-6
        :cobalt2_c => 2.25e-5
 Symbol("2fe2s_c") => 2.34e-5
         :udcpdp_c => 4.9500000000000004e-5
         :succoa_c => 8.82e-5
                   ⋮
         :asp__L_c => 0.2868797917530791
         :glu__L_c => 0.3135021356844055
         :ile__L_c => 0.32275794515577283
         :val__L_c => 0.39727031873391333
            :gly_c => 0.45084868440163167
         :leu__L_c => 0.5038252398350951
         :ala__L_c => 0.5237625785614599
            :h2o_c => 53.3177713940416
            :atp_c => 58.28889799404162

How much of that comes into (and out of) translation?

res.translation_resource_consumption
ConstraintTrees.Tree{Float64} with 25 elements:
  :adp_c    => -1.09189
  :ala__L_c => 0.523763
  :arg__L_c => 0.254827
  :asn__L_c => 0.217669
  :asp__L_c => 0.28688
  :atp_c    => 1.09189
  :cys__L_c => 0.0490187
  :gln__L_c => 0.188324
  :glu__L_c => 0.313502
  :gly_c    => 0.450849
  :h2o_c    => 1.09189
  :h_c      => -1.09189
  :his__L_c => 0.117845
  :ile__L_c => 0.322758
  :leu__L_c => 0.503825
  :lys__L_c => 0.25248
  :met__L_c => 0.159176
  :phe__L_c => 0.221654
  :pi_c     => -1.09189
  ⋮         => ⋮

How much arginine is used to build the ribosomes?

res.amino_acid_use.ribosome.arg__L_c
0.04971935935347408

This solution is not necessarily optimal though. To find an optimal growth, one may use e.g. screen to run the same simulation over many growth values, and pick the largest feasible growth.


This page was generated using Literate.jl.