1- # # Enzyme constrained models
1+ # # Community FBA models
22
33using COBREXA
44
5- # Here we will construct an enzyme constrained variant of the *E. coli* "core"
6- # model. We will need the model, which we can download if it is not already present.
5+ # Here we will construct a community FBA model of two *E. coli* "core" models
6+ # that can interact by exchanging selected metabolites. To do this, we will need
7+ # the model, which we can download if it is not already present.
78
89import Downloads: download
910
@@ -15,59 +16,124 @@ import Downloads: download
1516
1617import JSONFBCModels
1718import Tulip
19+ import AbstractFBCModels as A
20+ import ConstraintTrees as C
1821
1922model = load_model(" e_coli_core.json" )
2023
21- # Enzyme constrained models require parameters that are usually not used by
22- # conventional constraint based models. These include reaction specific turnover
23- # numbers, molar masses of enzymes, and capacity bounds.
24+ # Community models work by joining its members together through their exchange
25+ # reactions, weighted by the abundance of each microbe. These exchange reactions
26+ # are then linked to an environmental exchange. For more theoretical details,
27+ # see "Gottstein, et al, 2016, Constraint-based stoichiometric modelling from
28+ # single organisms to microbial communities, Journal of the Royal Society
29+ # Interface".
2430
25- import AbstractFBCModels as A
31+ # ## Building a community of two *E. coli*s
32+
33+ # Here we will construct a simple community of two interacting microbes. To do
34+ # this, we need to import the models. We import the models are ConstraintTrees,
35+ # because it is easier to build the model explicitly than rely on an opaque
36+ # one-shot function.
2637
27- m1 = fbc_model_constraints(model)
28- m2 = fbc_model_constraints(model)
38+ ecoli1 = fbc_model_constraints(model)
39+ ecoli2 = fbc_model_constraints(model)
2940
41+ # Since the models are joined through their individual exchange reactions to an
42+ # environmental exchange reactionq, we need to identify all possible exchange
43+ # reactions in the community. Since the models are the same, this is
44+ # straightforward here. Additionally, we need to specify the upper and lower
45+ # bounds of these environmental exchange reactions.
3046lbs, ubs = A. bounds(model)
31- env_ex_rxns = Dict(rid => (lbs[i], ubs[i]) for (i, rid) in enumerate(A. reactions(model)) if startswith(rid, " EX_" ))
3247
48+ env_ex_rxns = Dict(
49+ rid => (lbs[i], ubs[i]) for
50+ (i, rid) in enumerate(A. reactions(model)) if startswith(rid, " EX_" )
51+ )
52+
53+ # Now we simply create an blank model that only includes environmental exchange reactions.
3354
3455m = build_community_environment(env_ex_rxns)
35- m += :bug1^ m1
36- m += :bug2^ m2
3756
57+ # Next we join each member microbe to the model.
58+ m += :bug1^ ecoli1
59+ m += :bug2^ ecoli2
60+
61+ # We also need to specify the abundances of each member, as this weights the
62+ # flux of each metabolite each member microbe can share with other members or
63+ # the environment.
3864member_abundances = [(:bug1, 0.2 ), (:bug2, 0.8 )]
39- m *=
40- :environmental_exchange_balances^ link_environmental_exchanges(
41- m,
42- [(:bug1, 0.2 ), (:bug2, 0.8 )],
43- )
4465
66+ m *= :environmental_exchange_balances^ link_environmental_exchanges(m, member_abundances)
67+
68+ # Finally, the most sensible community FBA simulation involves assuming the
69+ # growth rate of the models is the same. In this case, we simply set the growth
70+ # rate flux of each member to be the same.
4571m *=
46- :equal_growth_rate_constraint^ equal_growth_rate_constraints(
47- [(:bug1, m. bug1. fluxes.:BIOMASS_Ecoli_core_w_GAM. value), (:bug2, m. bug2. fluxes.:BIOMASS_Ecoli_core_w_GAM. value)]
48- )
72+ :equal_growth_rate_constraint^ equal_growth_rate_constraints([
73+ (:bug1, m. bug1. fluxes.:BIOMASS_Ecoli_core_w_GAM. value),
74+ (:bug2, m. bug2. fluxes.:BIOMASS_Ecoli_core_w_GAM. value),
75+ ])
76+
77+ # Since each growth rate is the same, we can pick any of the growth rates as the
78+ # objective for the simulation.
79+ m *= :objective^ C. Constraint(m. bug1. fluxes.:BIOMASS_Ecoli_core_w_GAM. value)
4980
81+ # Since the models are usually used in a mono-culture context, the glucose input
82+ # for each individual member is limited. We need to undo this limitation, and
83+ # rather rely on the constrained environmental exchange reaction (and the bounds
84+ # we set for it earlier).
5085m. bug1. fluxes. EX_glc__D_e. bound = (- 1000.0 , 1000.0 )
5186m. bug2. fluxes. EX_glc__D_e. bound = (- 1000.0 , 1000.0 )
52- m. bug1. fluxes. CYTBD. bound = (- 10.0 , 10.0 ) # respiration limited
5387
54- m *= :objective ^ C . Constraint(m . bug1 . fluxes.:BIOMASS_Ecoli_core_w_GAM . value)
55-
56- using Gurobi
88+ # We can also be interesting, and limit respiration in one of the members, to
89+ # see what effect this has on the community.
90+ m . bug1 . fluxes . CYTBD . bound = ( - 10.0 , 10.0 )
5791
58- sol = optimized_constraints(m; objective = m. objective. value, optimizer= Gurobi. Optimizer)
92+ # Finally, we can simulate the system!
93+ sol = optimized_constraints(
94+ m;
95+ objective = m. objective. value,
96+ optimizer = Tulip. Optimizer,
97+ modifications = [set_optimizer_attribute(" IPM_IterationsLimit" , 1000 )],
98+ )
5999
60- Dict(k => v for (k, v) in sol. bug1. fluxes if startswith(string(k), " EX_" ))
61- Dict(k => v for (k, v) in sol. bug2. fluxes if startswith(string(k), " EX_" ))
100+ @test isapprox(sol.:objective, 0.66686196344 , atol = TEST_TOLERANCE) # src
62101
63- # exchange cytosolic metabolites
102+ # At the moment the members cannot really exchange any metabolites. We can
103+ # change this by changing their individual exchange bounds.
64104mets = [:EX_akg_e, :EX_succ_e, :EX_pyr_e, :EX_acald_e, :EX_fum_e, :EX_mal__L_e]
65105for met in mets
66106 m. bug1. fluxes[met]. bound = (- 1000.0 , 1000.0 )
67107 m. bug2. fluxes[met]. bound = (- 1000.0 , 1000.0 )
68108end
69109
70- sol = optimized_constraints(m; objective = m. objective. value, optimizer= Tulip. Optimizer, modifications= [set_optimizer_attribute(" IPM_IterationsLimit" , 100000 )])
71-
72- Dict(k => v for (k, v) in sol. bug1. fluxes if startswith(string(k), " EX_" ))
73- Dict(k => v for (k, v) in sol. bug2. fluxes if startswith(string(k), " EX_" ))
110+ sol = optimized_constraints(
111+ m;
112+ objective = m. objective. value,
113+ optimizer = Tulip. Optimizer,
114+ modifications = [set_optimizer_attribute(" IPM_IterationsLimit" , 1000 )],
115+ )
116+
117+
118+ # We can see that by allowing the microbes to share metabolites, the growth rate
119+ # of the system as a whole increased! We can inspect the individual exchanges to
120+ # see which metabolites are being shared (pyruvate in this case).
121+ bug1_ex_fluxes = Dict(k => v for (k, v) in sol. bug1. fluxes if startswith(string(k), " EX_" ))
122+ bug2_ex_fluxes = Dict(k => v for (k, v) in sol. bug2. fluxes if startswith(string(k), " EX_" ))
123+
124+ # !!! warning "Flux units"
125+ # The unit of the environmental exchange reactions (mmol/gDW_total_biomass/h) is
126+ # different to the unit of the individual species fluxes
127+ # (mmol/gDW_species_biomass/h). This is because the mass balance needs to take
128+ # into account the abundance of each species for the simulation to make sense.
129+ # In this specific case, look at the flux of pyruvate (EX_pyr_e). There is no
130+ # environmental exchange flux, so the two microbes share the metabolite.
131+ # However, `bug1_ex_fluxes[:EX_pyr_e] != bug2_ex_fluxes[:EX_pyr_e]`, but rather
132+ # `abundance_bug1 * bug1_ex_fluxes[:EX_pyr_e] != abundance_bug2 *
133+ # bug2_ex_fluxes[:EX_pyr_e]`. Take care of this when comparing fluxes!
134+
135+ @test isapprox(
136+ abs(0.2 * bug1_ex_fluxes[:EX_pyr_e] + 0.8 * bug2_ex_fluxes[:EX_pyr_e]),
137+ 0.0 ,
138+ atol = TEST_TOLERANCE,
139+ ) # src
0 commit comments