1
- # # Enzyme constrained models
1
+ # # Community FBA models
2
2
3
3
using COBREXA
4
4
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.
7
8
8
9
import Downloads: download
9
10
@@ -15,59 +16,124 @@ import Downloads: download
15
16
16
17
import JSONFBCModels
17
18
import Tulip
19
+ import AbstractFBCModels as A
20
+ import ConstraintTrees as C
18
21
19
22
model = load_model (" e_coli_core.json" )
20
23
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".
24
30
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.
26
37
27
- m1 = fbc_model_constraints (model)
28
- m2 = fbc_model_constraints (model)
38
+ ecoli1 = fbc_model_constraints (model)
39
+ ecoli2 = fbc_model_constraints (model)
29
40
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.
30
46
lbs, 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_" ))
32
47
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.
33
54
34
55
m = build_community_environment (env_ex_rxns)
35
- m += :bug1 ^ m1
36
- m += :bug2 ^ m2
37
56
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.
38
64
member_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
- )
44
65
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.
45
71
m *=
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)
49
80
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).
50
85
m. bug1. fluxes. EX_glc__D_e. bound = (- 1000.0 , 1000.0 )
51
86
m. bug2. fluxes. EX_glc__D_e. bound = (- 1000.0 , 1000.0 )
52
- m. bug1. fluxes. CYTBD. bound = (- 10.0 , 10.0 ) # respiration limited
53
87
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 )
57
91
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
+ )
59
99
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
62
101
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.
64
104
mets = [:EX_akg_e , :EX_succ_e , :EX_pyr_e , :EX_acald_e , :EX_fum_e , :EX_mal__L_e ]
65
105
for met in mets
66
106
m. bug1. fluxes[met]. bound = (- 1000.0 , 1000.0 )
67
107
m. bug2. fluxes[met]. bound = (- 1000.0 , 1000.0 )
68
108
end
69
109
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