cdFBA 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cdfba-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vivarium Collective
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cdfba-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.2
2
+ Name: cdFBA
3
+ Version: 0.0.1
4
+ Home-page: https://github.com/vivarium-collective/cdFBA
5
+ Author: Tasnif Rahman
6
+ Author-email: trahman@uchc.edu
7
+ License: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: vivarium-interface
21
+ Requires-Dist: cobra
22
+ Requires-Dist: matplotlib
23
+ Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: license
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+
33
+ # cdFBA
34
+ Community Dynamic Flux Balance Analysis using process-bigraph.
35
+
36
+
37
+ #### This project is a work in progress
cdfba-0.0.1/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # cdFBA
2
+ Community Dynamic Flux Balance Analysis using process-bigraph.
3
+
4
+
5
+ #### This project is a work in progress
@@ -0,0 +1,71 @@
1
+ from cdFBA.processes import register_processes
2
+
3
+ def apply_non_negative(schema, current, update, core):
4
+ new_value = current + update
5
+ return max(0, new_value)
6
+
7
+
8
+ def set_update(schema, current, update, core):
9
+ return update
10
+
11
+ def volumetric_update(schema, current, update, core):
12
+ counts = update.get('counts')
13
+ volume = current.get('volume')
14
+
15
+ new_counts = current['counts']
16
+ new_concentrations = current['concentrations']
17
+ if counts:
18
+ for key, value in counts.items():
19
+ new = new_counts[key] + value
20
+ new_counts[key] = new
21
+ new_concentrations[key] = new/volume
22
+
23
+ return {
24
+ 'counts': new_counts,
25
+ 'concentrations': new_concentrations,
26
+ 'volume': volume,
27
+ }
28
+
29
+ positive_float = {
30
+ '_type': 'positive_float',
31
+ '_inherit': 'float',
32
+ '_apply': apply_non_negative}
33
+
34
+ set_float = {
35
+ '_type': 'set_float',
36
+ '_inherit': 'float',
37
+ '_apply': set_update
38
+ }
39
+
40
+ bounds_type = {
41
+ 'lower': 'maybe[float]',
42
+ 'upper': 'maybe[float]'}
43
+
44
+
45
+ particle_type = {
46
+ 'id': 'string',
47
+ 'position': 'tuple[float,float]',
48
+ 'size': 'float',
49
+ 'local': 'map[float]',
50
+ 'exchange': 'map[float]', # {mol_id: delta_value}
51
+ }
52
+
53
+ volumetric_type = {
54
+ 'concentrations':'map[float]',
55
+ 'counts':'map[float]',
56
+ 'volume': 'float',
57
+ '_apply': volumetric_update
58
+ }
59
+
60
+ chemostat_type = {
61
+ 'concentration': 'float'
62
+ }
63
+
64
+ def register_types(core):
65
+ core.register('positive_float', positive_float)
66
+ core.register('set_float', set_float)
67
+ core.register('bounds', bounds_type)
68
+ core.register('particle', particle_type)
69
+ core.register('volumetric', volumetric_type)
70
+
71
+ return register_processes(core)
@@ -0,0 +1,6 @@
1
+ from cdFBA.processes.dfba import DFBA
2
+
3
+ def register_processes(core):
4
+ core.register_process('DFBA', DFBA)
5
+
6
+ return core
@@ -0,0 +1,363 @@
1
+ import random
2
+ import pprint
3
+ import math
4
+
5
+ from process_bigraph.composite import ProcessTypes
6
+ from process_bigraph import Process, Step, Composite
7
+
8
+ from cdFBA.utils import model_from_file, get_objective_reaction, get_injector_spec, get_wave_spec, get_chemo_spec
9
+ from cdFBA.utils import get_single_dfba_spec, dfba_config, environment_spec, initial_environment
10
+
11
+ from matplotlib import pyplot as plt
12
+
13
+
14
+ class DFBA(Process):
15
+ """Performs single time-step of dynamic FBA
16
+
17
+ Parameters:
18
+ -----------
19
+ model_file: string, math to cobra model file
20
+ """
21
+ config_schema = {
22
+ "model_file": {
23
+ '_type': 'string',
24
+ '_default': 'iAF1260',
25
+ },
26
+ "name": 'string',
27
+ "kinetics": "any",
28
+ "reaction_map": "any",
29
+ "biomass_identifier": "any",
30
+ "bounds": "maybe[map[bounds]]",
31
+ }
32
+
33
+ def __init__(self, config, core):
34
+ super().__init__(config, core)
35
+
36
+ # TODO -- load model here rather than passing it in
37
+ self.model = model_from_file(self.config['model_file'])
38
+
39
+
40
+ if self.config["bounds"] is not None:
41
+ if len(self.config["bounds"]) != 0:
42
+ for reaction_id, bounds in self.config["bounds"].items():
43
+ if bounds["lower"] is not None:
44
+ self.model.reactions.get_by_id(reaction_id).lower_bound = bounds["lower"]
45
+ if bounds["upper"] is not None:
46
+ self.model.reactions.get_by_id(reaction_id).upper_bound = bounds["upper"]
47
+
48
+ # def initial_state(self):
49
+ # # TODO -- get the initial state from the load model, self.model
50
+ # return {
51
+ # "dfba_update": {
52
+ # 'Biomass_Ecoli_core': 0,
53
+ # 'acetate': 0,
54
+ # 'glucose': 0}}
55
+
56
+ def inputs(self):
57
+ return {
58
+ "shared_environment": "volumetric", #initial conditions for time-step
59
+ "current_update": "map[map[set_float]]",
60
+ }
61
+
62
+ def outputs(self):
63
+ return {
64
+ "dfba_update": "map[set_float]"
65
+ }
66
+
67
+
68
+ def update(self, inputs, interval):
69
+ current_state = {key:inputs["shared_environment"]["counts"][key] for key, value in self.config["reaction_map"].items()}
70
+ current_state[self.config["name"]] = inputs["shared_environment"]["counts"][self.config["name"]]
71
+ state_update = current_state.copy()
72
+
73
+ for substrate_id, reaction_id in self.config["reaction_map"].items():
74
+ Km, Vmax = self.config["kinetics"][substrate_id]
75
+ substrate_concentration = inputs["shared_environment"]["concentrations"][substrate_id]
76
+
77
+ # calculate michaelis-menten flux
78
+ flux = Vmax * substrate_concentration / (Km + substrate_concentration)
79
+
80
+ # use the flux to constrain fba
81
+ self.model.reactions.get_by_id(reaction_id).lower_bound = -flux
82
+
83
+ # solve fba under these constraints
84
+ solution = self.model.optimize()
85
+
86
+ # gather the results
87
+ ## update biomass
88
+ biomass_growth_rate = solution.fluxes[self.config["biomass_identifier"]]
89
+ current_biomass = current_state[self.config["name"]]
90
+ state_update[self.config['name']] = biomass_growth_rate * current_biomass * interval
91
+
92
+ ## update substrates
93
+ for substrate_id, reaction_id in self.config["reaction_map"].items():
94
+ flux = solution.fluxes[reaction_id]
95
+ state_update[substrate_id] = (flux * current_biomass * interval)
96
+
97
+ return {"dfba_update": state_update}
98
+
99
+ class UpdateEnvironment(Step): #TODO =:
100
+ config_schema = {}
101
+
102
+ def __init__(self, config, core):
103
+ super().__init__(config, core)
104
+
105
+ def inputs(self):
106
+ return {
107
+ "shared_environment": "volumetric",
108
+ "species_updates": "map[map[set_float]]" #TODO add a species_update type
109
+ }
110
+
111
+ def outputs(self):
112
+ return {
113
+ "shared_environment": "map[float]",
114
+ }
115
+
116
+ def update(self, inputs):
117
+ species_updates = inputs["species_updates"]
118
+ shared_environment = inputs["shared_environment"]["counts"]
119
+ env_volume = inputs["shared_environment"]["volume"]
120
+
121
+ species_list = [species for species in species_updates]
122
+ random.shuffle(species_list)
123
+
124
+ update = shared_environment.copy()
125
+
126
+ for species in species_list:
127
+ for substrate_id in species_updates[species]:
128
+ if (shared_environment[substrate_id] + species_updates[species][substrate_id]) > 0:
129
+ update[substrate_id] = species_updates[species][substrate_id]
130
+ else:
131
+ update[substrate_id] = -shared_environment[substrate_id]
132
+
133
+ return {
134
+ "shared_environment": {'counts': update}
135
+ }
136
+
137
+ class Chemostat(Process):
138
+ """The Chemostat process maintains the concentration of given substrates at a fixed value at each time-step
139
+ """
140
+ config_schema = {
141
+ "substrate_concentrations" : "map[float]",
142
+ }
143
+
144
+ def __init__(self, config, core):
145
+ super().__init__(config, core)
146
+
147
+ def inputs(self):
148
+ return {
149
+ "shared_environment": "volumetric",
150
+ "global_time": "float"
151
+ }
152
+
153
+ def outputs(self):
154
+ return {
155
+ "shared_environment": "map[float]"
156
+ }
157
+
158
+ def update(self, inputs, interval):
159
+ shared_environment = inputs["shared_environment"]["counts"]
160
+
161
+ update = {}
162
+
163
+ for substrate, values in self.config["substrate_concentrations"].items():
164
+ update[substrate] = (values * inputs["shared_environment"]["volume"]) - shared_environment[substrate]
165
+
166
+ return {
167
+ "shared_environment": {'counts': update}
168
+ }
169
+
170
+ class WaveFunction(Process):
171
+ """The WaveFunction process maintains the concentration of given substrates based on a wave-function
172
+ """
173
+ config_schema = {
174
+ "substrate_params" : "map[map[float]]",
175
+ }
176
+
177
+ """
178
+ substrate_params = {
179
+ "substrate_name": {
180
+ "amplitude": "float",
181
+ "angular_frequency": "float",
182
+ "base_concentration": "float",
183
+ "phase_shift": "float"
184
+ }
185
+ }
186
+
187
+ amplitude : float, amplitude of wave function
188
+ angular_frequency : float, angular frequency of wave function
189
+ base_concentration : float, base concentration substrate
190
+ phase_shift : float, phase shift of wave function (when wave starts)
191
+ """
192
+
193
+ def __init__(self, config, core):
194
+ super().__init__(config, core)
195
+
196
+ def inputs(self):
197
+ return {
198
+ "shared_environment": "volumetric",
199
+ "global_time": "float"
200
+ }
201
+
202
+ def outputs(self):
203
+ return {
204
+ "shared_environment": "map[float]"
205
+ }
206
+
207
+ def update(self, inputs, interval):
208
+ shared_environment = inputs["shared_environment"]["counts"]
209
+ t = inputs["global_time"]
210
+ update = {}
211
+ for substrate in self.config["substrate_params"]:
212
+ A = self.config["substrate_params"][substrate]["amplitude"]
213
+ w = self.config["substrate_params"][substrate]["angular_frequency"]
214
+ B = self.config["substrate_params"][substrate]["base_concentration"]
215
+ phi = self.config["substrate_params"][substrate]["phase_shift"]
216
+
217
+ current_count = (A*math.sin(w*t+phi) + B) * inputs["shared_environment"]["volume"]
218
+
219
+ update[substrate] = (current_count - shared_environment[substrate])
220
+
221
+ return {
222
+ "shared_environment": {'counts': update}
223
+ }
224
+
225
+ class Injector(Process):
226
+ """The Injector process injects a given amount of a given substrate at regular intervals into the shared environment
227
+ """
228
+ config_schema = {
229
+ "injection_params" : "map[map[float]]",
230
+ }
231
+
232
+ # injection_params = {
233
+ # "substrate_name" : {
234
+ # "amount": "float",
235
+ # "interval": "float",
236
+ # }
237
+ # }
238
+
239
+ def __init__(self, config, core):
240
+ super().__init__(config, core)
241
+
242
+ def inputs(self):
243
+ return {
244
+ "shared_environment": "volumetric",
245
+ "global_time": "float"
246
+ }
247
+
248
+ def outputs(self):
249
+ return {
250
+ "shared_environment": "map[float]"
251
+ }
252
+
253
+ def update(self, inputs, interval):
254
+ shared_environment = inputs["shared_environment"]["counts"]
255
+ t = inputs["global_time"]
256
+ update = {}
257
+ for substrate in self.config["injection_params"]:
258
+ if ((t % self.config["injection_params"][substrate]["interval"]) == 0) & (t!=0.0):
259
+ update[substrate] = self.config["injection_params"][substrate]["amount"]
260
+ return {
261
+ "shared_environment": {'counts': update}
262
+ }
263
+
264
+ def run_environment(core):
265
+ """This tests that the environment runs"""
266
+ name1 = "E.coli"
267
+ name2 = "S.flexneri"
268
+ # define a single dFBA model
269
+ spec = {
270
+ name1: get_single_dfba_spec(model_file= "iAF1260", name=name1)
271
+ }
272
+
273
+ spec[name2] = get_single_dfba_spec(model_file = "iSFxv_1172", name=name2)
274
+
275
+ spec['shared environment'] = initial_environment(volume=2, species_list=[name1, name2])
276
+
277
+ spec['dFBA Results'] = {name1:
278
+ {
279
+ "glucose": 0,
280
+ "acetate": 0,
281
+ spec[name1]['config']['name']: 0,
282
+ },
283
+ name2:
284
+ {
285
+ "glucose": 0,
286
+ "acetate": 0,
287
+ spec[name2]['config']['name']: 0,
288
+ }
289
+ }
290
+
291
+ spec['update environment'] = environment_spec()
292
+
293
+ injector_config = {
294
+ "injection_params": {
295
+ "glucose": {
296
+ "amount": 80,
297
+ "interval": 5,
298
+ }
299
+ }
300
+ }
301
+
302
+ spec['environment dynamics'] = get_injector_spec(config=injector_config)
303
+
304
+ pprint.pprint(spec)
305
+
306
+ # put it in a composite
307
+ sim = Composite({
308
+ "state": spec,
309
+ "emitter": {'mode': 'all'}},
310
+ core=core
311
+ )
312
+
313
+ # run the simulation
314
+ sim.run(40)
315
+ results = sim.gather_results()[('emitter',)]
316
+
317
+ # print the results
318
+ timepoints = []
319
+ for timepoint in results:
320
+ time = timepoint.pop('global_time')
321
+ timepoints.append(time)
322
+ dfba_spec = timepoint.pop(name1)
323
+ print(f'TIME: {time}')
324
+ print(f'STATE: {timepoint}')
325
+
326
+ env = [timepoint['shared environment']['concentrations'] for timepoint in results]
327
+ env_combined = {}
328
+ for d in env:
329
+ for key, value in d.items():
330
+ if key not in env_combined:
331
+ env_combined[key] = []
332
+ env_combined[key].append(value)
333
+
334
+
335
+ fig, ax = plt.subplots(dpi=300)
336
+ for key, value in env_combined.items():
337
+ # if not key == 'glucose':
338
+ # continue
339
+ ax.plot(timepoints, env_combined[key], label=key)
340
+ plt.xlabel('Time')
341
+ plt.ylabel('Substrate Concentration')
342
+ plt.legend()
343
+ plt.tight_layout()
344
+ plt.show()
345
+
346
+ if __name__ == "__main__":
347
+ from cdFBA import register_types
348
+
349
+ # create a core
350
+ core = ProcessTypes()
351
+ core = register_types(core)
352
+
353
+ core.register_process('DFBA', DFBA)
354
+ core.register_process('UpdateEnvironment', UpdateEnvironment)
355
+ core.register_process('Chemostat', Chemostat)
356
+ core.register_process('WaveFunction', WaveFunction)
357
+ core.register_process('Injector', Injector)
358
+
359
+ # print(get_single_dfba_spec())
360
+ # test_dfba_alone(core)
361
+ # test_dfba(core)
362
+ run_environment(core)
363
+ # test_composite()
@@ -0,0 +1,478 @@
1
+ """This module contains some methods to obtain the reaction map, initial conditions, and kinetic parameters needed
2
+ for dFBA simulations from the minimal medium requirements of the wild-type species.
3
+
4
+ CAUTION: The initial conditions, and kinetics dataframes provide default parameter values and must be changed as needed
5
+ CAUTION: Substrate names are different in BiGG and AGORA databases. These functions will not work with two models form
6
+ different sources
7
+ """
8
+ from cobra.io import load_model, read_sbml_model, load_json_model, load_yaml_model, load_matlab_model
9
+ from cobra.medium import minimal_medium
10
+ import pprint
11
+ import re
12
+
13
+ #single species functions
14
+ def model_from_file(model_file='textbook'):
15
+ """Returns a cobra model from a model file path or BiGG Model ID
16
+ Parameters:
17
+ model_file: str, file path or BiGG Model ID
18
+ Returns:
19
+ model: cobra model
20
+ """
21
+ if ".xml" in model_file:
22
+ model = read_sbml_model(model_file)
23
+ elif ".json" in model_file:
24
+ model = load_json_model(model_file)
25
+ elif ".yaml" in model_file:
26
+ model = load_yaml_model(model_file)
27
+ elif ".mat" in model_file:
28
+ model = load_matlab_model(model_file)
29
+ elif isinstance(model_file, str):
30
+ model = load_model(model_file)
31
+ else:
32
+ # error handling
33
+ raise ValueError("Invalid model file")
34
+ return model
35
+
36
+ def get_exchanges(model_file='textbook', medium_type='exchange'):
37
+ """
38
+
39
+ Parameters:
40
+ model_file: str, file path or BiGG Model ID
41
+ medium_type:
42
+ 'default' uses the default cobra model medium
43
+ 'minimal' uses the minimal medium for the model
44
+ 'exchange' uses all exchange fluxes for the model
45
+ defaults to 'exchange'
46
+ Returns:
47
+ exchanges: list of exchange reaction IDs
48
+ """
49
+ model=model_from_file(model_file)
50
+ if medium_type == 'default':
51
+ medium = model.medium
52
+ if medium_type == 'minimal':
53
+ medium = minimal_medium(model, model.slim_optimize()).to_dict()
54
+ if medium_type == 'exchange':
55
+ medium = {reaction.id: reaction.upper_bound for reaction in model.exchanges}
56
+ medium.update(model.medium)
57
+ medium = medium
58
+ return list(medium.keys())
59
+
60
+ def get_substrates(model_file='textbook', exchanges=None):
61
+ """Returns a list of substrates from the model.
62
+ Parameters:
63
+ model_file : str, file path or BiGG Model ID
64
+ exchanges : lst, a list of exchange reaction ids
65
+ Returns:
66
+ substrates : lst, list of names of substrates required by the model organism
67
+ """
68
+ if exchanges is None:
69
+ exchanges = get_exchanges(model_file)
70
+ model = model_from_file(model_file)
71
+ substrates = []
72
+
73
+ for item in [getattr(model.reactions, i).name for i in exchanges if hasattr(model.reactions, i)]:
74
+
75
+ match = re.match(r"(.*) exchange|exchange reaction for (.*)|Exchange of (.*)|echange reaction for (.*)", item, re.IGNORECASE)
76
+
77
+ if match:
78
+ substrates.append(match.group(1) or match.group(2) or match.group(3) or match.group(4))
79
+ else:
80
+ substrates.append(item)
81
+ return substrates
82
+
83
+ def get_reaction_map(model_file='textbook', exchanges=None):
84
+ """Returns a reaction_name_map dictionary from a medium dictionary as obtained
85
+ from model.medium or cobra.medium.minimum_medium()
86
+ Parameters:
87
+ model_file : str, file path or BiGG Model ID
88
+ exchanges : lst, list of names of substrates required by the model organism
89
+ Returns:
90
+ reaction_name_map : dict, maps substrate names to reactions
91
+ """
92
+ if exchanges is None:
93
+ exchanges = get_exchanges(model_file)
94
+ model = model_from_file(model_file)
95
+ substrates = get_substrates(model_file, exchanges)
96
+ ids = exchanges
97
+ reaction_name_map = {}
98
+ for i in range(len(substrates)):
99
+ reaction_name_map[substrates[i]] = ids[i]
100
+ return reaction_name_map
101
+
102
+ def get_kinetics(model_file='textbook', exchanges=None):
103
+ """Returns default kinetic parameters dictionary. Values are tuples of the form (km, vmax)
104
+ Parameters:
105
+ model_file : str, file path or BiGG Model ID
106
+ exchanges : lst, list of exchange reaction ids
107
+ """
108
+ if exchanges is None:
109
+ exchanges = get_exchanges(model_file)
110
+ model = model_from_file(model_file)
111
+ kinetics = {key: (0.5, 2.0) for key in get_substrates(model_file, exchanges)}
112
+ return kinetics
113
+
114
+ def get_bounds(reaction_map, upper=1000, lower=-1000):
115
+ """Return dict of default upper and lower bounds for each substrate exchange reaction
116
+ Parameters:
117
+ reaction_map : dict, maps substrate names to reactions
118
+ upper : float, upper bound, default 1000
119
+ lower : float, lower bound, default -1000
120
+ Returns:
121
+ bounds : dict, default bounds for all exchange reactions
122
+ """
123
+ return {reaction_map[key]: {'lower': lower, 'upper': upper} for key in reaction_map.keys()}
124
+
125
+ def get_objective_reaction(model_file = 'textbook'):
126
+ """get a string with the name of the objective function of a cobra model
127
+ Parameters:
128
+ model: cobrapy model
129
+ Returns:
130
+ objective_reaction: str, name of the objective reaction (biomass reaction by default)
131
+ """
132
+ model = model_from_file(model_file)
133
+ expression = f"{model.objective.expression}"
134
+ match = re.search(r'1\.0\*([^\s]+)', expression)
135
+
136
+ if match:
137
+ objective_reaction = match.group(1)
138
+
139
+ return objective_reaction
140
+
141
+ def dfba_config(
142
+ model_file="textbook",
143
+ name=None,
144
+ kinetics=None,
145
+ reaction_map=None,
146
+ biomass_identifier=None,
147
+ bounds=None
148
+ ):
149
+ """Construct a configuration dictionary for a single cobra model
150
+ Parameters:
151
+ model_file: str, file path or BiGG Model ID
152
+ name: str, name of the process
153
+ kinetics: dict, kinetic parameters for shared substrates
154
+ reaction_map: dict, maps substrate names to reaction ids
155
+ biomass_identifier: str, name of the biomass reaction
156
+ bounds: dict, bounds for exchange reactions
157
+ Returns:
158
+ config: dict, config dictionary for a single species dFBA
159
+ """
160
+ model = model_from_file(model_file)
161
+ if name is None:
162
+ name = model.id
163
+ if reaction_map is None:
164
+ reaction_map = {
165
+ "glucose": "EX_glc__D_e",
166
+ "acetate": "EX_ac_e"
167
+ }
168
+ if bounds is None:
169
+ bounds = {
170
+ "EX_o2_e": {"lower": -2, "upper": None},
171
+ "ATPM": {"lower": 1, "upper": 1}
172
+ }
173
+ if kinetics is None:
174
+ kinetics = {
175
+ "glucose": (0.02, 15),
176
+ "acetate": (0.5, 7)}
177
+ if biomass_identifier is None:
178
+ biomass_identifier = get_objective_reaction(model_file=model_file)
179
+
180
+ return {
181
+ "model_file": model_file,
182
+ "name": name,
183
+ "kinetics": kinetics,
184
+ "reaction_map": reaction_map,
185
+ "biomass_identifier": biomass_identifier,
186
+ "bounds": bounds,
187
+ }
188
+
189
+ def get_single_dfba_spec(
190
+ model_file="textbook",
191
+ name="species",
192
+ config=None,
193
+ interval=1.0
194
+ ):
195
+ """Constructs a configuration dictionary for a dynamic FBA process
196
+ Parameters:
197
+ model_file: str, file path or BiGG Model ID
198
+ name: str, identifier for the model, usually species/strain name
199
+ config: dict, config for DFBA Process. If none provided, uses default generated using `dfba_config()`
200
+ interval: float, interval between consecutive dFBA calculations
201
+ Returns:
202
+ dict: dict, specification dictionary for a single species dFBA
203
+ """
204
+
205
+ if config is None:
206
+ config = dfba_config(model_file=model_file, name=name)
207
+
208
+ return {
209
+ "_type": "process",
210
+ "address": "local:DFBA",
211
+ "config": config,
212
+ "inputs": {
213
+ "shared_environment": ["shared environment"],
214
+ "current_update": ["dFBA Results"]
215
+ },
216
+ "outputs": {
217
+ "dfba_update": ["dFBA Results", name]
218
+ },
219
+ "interval": interval
220
+ }
221
+
222
+ #multi-species functions
223
+ def make_cdfba_composite(model_dict, medium_type=None, exchanges=None, volume=1, interval=1.0):
224
+ """Construct a cdfba composite spec with all exhange metabolites included.
225
+ Parameters:
226
+ model_dict : dict, dictionary with cdfba process names as keys and model name/path as values
227
+ medium_type : str/lst, if str, pick one of:
228
+ 'default' uses the default cobra model medium
229
+ 'minimal' uses the minimal medium for the model
230
+ 'exchange' uses all exchange fluxes for the model
231
+ MUST be None if exchanges is provided
232
+ exchanges: a list of exchange reaction ids. MUST be None if medium_type is provided
233
+ volume: float, volume of cdfba composite
234
+ interval: float, interval between consecutive dFBA calculations
235
+ Returns:
236
+ spec : dict, cdfba composite spec
237
+ """
238
+ spec = {'dFBA Results': {}}
239
+ if medium_type is None:
240
+ if exchanges is None:
241
+ raise ValueError("Must provide medium_type or exchanges list")
242
+
243
+ if medium_type is not None:
244
+ if exchanges is not None:
245
+ raise ValueError("Provide only on of medium_type or exchanges list")
246
+
247
+ if exchanges is None:
248
+ env_exchanges = []
249
+ for name, model_file in model_dict.items():
250
+ env_exchanges.extend(get_exchanges(model_file=model_file, medium_type=medium_type))
251
+
252
+ env_exchanges = list(set(env_exchanges))
253
+ else:
254
+ env_exchanges = exchanges
255
+
256
+ initial_counts = get_initial_counts(model_dict, exchanges=env_exchanges)
257
+ initial_env = initial_environment(volume=volume, initial_counts=initial_counts, species_list=model_dict.keys())
258
+ spec['shared environment'] = initial_env
259
+
260
+ for model_name, model_file in model_dict.items():
261
+ if exchanges is None:
262
+ model_exchanges = get_exchanges(model_file=model_file, medium_type=medium_type)
263
+ else:
264
+ model_exchanges = exchanges
265
+ substrates = get_substrates(model_file=model_file, exchanges=model_exchanges)
266
+ kinetics = get_kinetics(model_file=model_file, exchanges=model_exchanges)
267
+ reaction_map = get_reaction_map(model_file=model_file, exchanges=model_exchanges)
268
+ biomass_identifier = get_objective_reaction(model_file=model_file)
269
+ bounds = {}
270
+
271
+ config = dfba_config(
272
+ model_file=model_file,
273
+ name=model_name,
274
+ kinetics=kinetics,
275
+ reaction_map=reaction_map,
276
+ biomass_identifier=biomass_identifier,
277
+ bounds=bounds
278
+ )
279
+ model_spec = get_single_dfba_spec(model_file=model_file, name=model_name, config=config, interval=interval)
280
+ spec[model_name] = model_spec
281
+
282
+ spec['dFBA Results'][model_name] = {substrate: 0 for substrate in substrates}
283
+ spec['dFBA Results'][model_name].update({model_name: 0})
284
+ spec['update environment'] = environment_spec()
285
+ return spec
286
+
287
+ def get_initial_counts(model_dict, biomass=0.5, initial_value=20, exchanges=None):
288
+ """Returns an initial condition dict based on medium
289
+ Parameters:
290
+ model_dict: dict, dictionary with cdfba process names as keys and model name/path as values
291
+ biomass : float, initial biomass for all species
292
+ initial_value : float, initial counts of all species
293
+ exchanges: lst, list of exchange reaction ids
294
+ Returns:
295
+ conditions : dict, initial conditions dictionary
296
+ """
297
+ all_substrates = []
298
+ for model_name in model_dict.keys():
299
+ model_file=model_dict[model_name]
300
+ substrates = get_substrates(model_file, exchanges)
301
+ all_substrates.extend(substrates)
302
+ all_substrates = list(set(all_substrates))
303
+ conditions = {substrate:initial_value for substrate in all_substrates}
304
+ biomasses = {model:biomass for model in model_dict.keys()}
305
+ conditions = conditions | biomasses
306
+ return conditions
307
+
308
+ def initial_environment(volume=1, initial_counts=None, species_list=None):
309
+ """Construct initial shared environment store
310
+ Parameters:
311
+ volume : float, volume of the environment
312
+ initial_counts : dict, initial counts of each substrate and species biomass in the environment
313
+ species_list : list of strings, list of dfba species names)
314
+ Returns:
315
+ initial shared environment store spec
316
+ """
317
+ if initial_counts is None:
318
+ if species_list is None:
319
+ raise ValueError("Error: Please provide initial_counts or species_list")
320
+ initial_counts = {
321
+ "glucose": 80,
322
+ "acetate": 0,
323
+ }
324
+ for species in species_list:
325
+ initial_counts[species] = 0.5
326
+
327
+ initial_concentration = {key:(count/volume) for key, count in initial_counts.items()}
328
+
329
+ return {
330
+ "volume": volume,
331
+ "counts": initial_counts,
332
+ "concentrations": initial_concentration
333
+ }
334
+
335
+ #environmental process/step related functions
336
+ def environment_spec():
337
+ """Construct spec dictionary for UpdateEnvironment step"""
338
+ return {
339
+ "_type": "process",
340
+ "address": "local:UpdateEnvironment",
341
+ "config": {},
342
+ "inputs": {
343
+ "species_updates": ["dFBA Results"],
344
+ "shared_environment": ["shared environment"]
345
+ },
346
+ "outputs": {
347
+ "shared_environment": ["shared environment"],
348
+ }
349
+ }
350
+
351
+ def get_chemo_spec(config=None):
352
+ """Constructs a configuration dictionary for the Chemostat process.
353
+ Parameters:
354
+ config: dict, Chemostat configuration dictionary
355
+ Returns:
356
+ dict, spec for Chemostat process
357
+ """
358
+ if config is None:
359
+ raise ValueError("Error: Please provide config")
360
+ return {
361
+ "_type": "process",
362
+ "address": "local:Chemostat",
363
+ "config": config,
364
+ "inputs": {
365
+ "shared_environment": ["shared environment"],
366
+ "global_time": ["global_time"],
367
+ },
368
+ "outputs": {
369
+ "shared_environment": ["shared environment"],
370
+ },
371
+ }
372
+
373
+ def get_wave_spec(config=None):
374
+ """Constructs a configuration dictionary for the WaveFunction process.
375
+ Parameters:
376
+ config: dict, WaveFunction configuration dictionary
377
+ Returns
378
+ dict, spec for WaveFunction process
379
+ """
380
+ if config is None:
381
+ raise ValueError("Error: Please provide config")
382
+ return {
383
+ "_type": "process",
384
+ "address": "local:WaveFunction",
385
+ "config": config,
386
+ "inputs": {
387
+ "shared_environment": ["shared environment"],
388
+ "global_time": ["global_time"],
389
+ },
390
+ "outputs": {
391
+ "shared_environment": ["shared environment"],
392
+ },
393
+ }
394
+
395
+ def get_injector_spec(config=None):
396
+ """Constructs a configuration dictionary for the Injector process.
397
+ Parameters:
398
+ config: dict, Injector configuration dictionary
399
+ Returns:
400
+ dict, spec for Injector process
401
+ """
402
+ if config is None:
403
+ raise ValueError("Error: Please provide config")
404
+ return {
405
+ "_type": "process",
406
+ "address": "local:Injector",
407
+ "config": config,
408
+ "inputs": {
409
+ "shared_environment": ["shared environment"],
410
+ "global_time": ["global_time"],
411
+ },
412
+ "outputs": {
413
+ "shared_environment": ["shared environment"],
414
+ },
415
+ }
416
+
417
+ def run_single_dfba_spec(model_file="textbook"):
418
+ model = model_from_file(model_file)
419
+ exchanges = get_exchanges(model_file=model_file, medium_type='exchange')
420
+ substrates = get_substrates(model_file=model_file, exchanges=exchanges)
421
+ reaction_map = get_reaction_map(model_file=model_file, exchanges=exchanges)
422
+ kinetics = get_kinetics(model_file=model_file, exchanges=exchanges)
423
+ bounds=None
424
+ biomass_identifier = get_objective_reaction(model_file=model_file)
425
+ config = dfba_config(
426
+ model_file=model_file,
427
+ name='E.coli Core',
428
+ kinetics=kinetics,
429
+ reaction_map=reaction_map,
430
+ biomass_identifier=biomass_identifier,
431
+ bounds=bounds,
432
+ )
433
+ spec = get_single_dfba_spec(
434
+ model_file=model_file,
435
+ name='E.coli Core',
436
+ config=config,
437
+ interval=1.0
438
+ )
439
+
440
+ pprint.pprint(spec)
441
+ print("")
442
+ print(f"Exchanges: {exchanges}")
443
+ print(f"Substrates: {substrates}")
444
+ print(f"Reaction Map: {reaction_map}")
445
+ print(f"Kinetics: {kinetics}")
446
+ print(f"Biomass Identifier: {biomass_identifier}")
447
+ print(f"Bounds: {bounds}")
448
+
449
+ def run_initial_counts(model_file="textbook"):
450
+ exchanges = get_exchanges(model_file=model_file, medium_type='exchange')
451
+ initial_counts = get_initial_counts(
452
+ model_dict={'E.coli Core': 'textbook'},
453
+ biomass=0.1,
454
+ initial_value=20,
455
+ exchanges=exchanges
456
+ )
457
+
458
+ pprint.pprint(initial_counts)
459
+
460
+ def run_cdfba_spec():
461
+ model_dict = {
462
+ 'E.coli' : 'iAF1260',
463
+ 'S. flexneri' : 'iSFxv_1172'
464
+ }
465
+
466
+ pprint.pprint(make_cdfba_composite(
467
+ model_dict=model_dict,
468
+ medium_type='exchange',
469
+ exchanges=None,
470
+ volume=1,
471
+ interval=1.0
472
+ )
473
+ )
474
+
475
+ if __name__ == '__main__':
476
+ # run_single_dfba_spec(model_file="textbook")
477
+ # run_initial_counts(model_file="textbook")
478
+ run_cdfba_spec()
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.2
2
+ Name: cdFBA
3
+ Version: 0.0.1
4
+ Home-page: https://github.com/vivarium-collective/cdFBA
5
+ Author: Tasnif Rahman
6
+ Author-email: trahman@uchc.edu
7
+ License: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: vivarium-interface
21
+ Requires-Dist: cobra
22
+ Requires-Dist: matplotlib
23
+ Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: license
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+
33
+ # cdFBA
34
+ Community Dynamic Flux Balance Analysis using process-bigraph.
35
+
36
+
37
+ #### This project is a work in progress
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ cdFBA/__init__.py
5
+ cdFBA/utils.py
6
+ cdFBA.egg-info/PKG-INFO
7
+ cdFBA.egg-info/SOURCES.txt
8
+ cdFBA.egg-info/dependency_links.txt
9
+ cdFBA.egg-info/requires.txt
10
+ cdFBA.egg-info/top_level.txt
11
+ cdFBA/processes/__init__.py
12
+ cdFBA/processes/dfba.py
@@ -0,0 +1,3 @@
1
+ vivarium-interface
2
+ cobra
3
+ matplotlib
@@ -0,0 +1 @@
1
+ cdFBA
cdfba-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
cdfba-0.0.1/setup.py ADDED
@@ -0,0 +1,53 @@
1
+ import re
2
+ from setuptools import setup, find_packages
3
+
4
+
5
+ VERSION = '0.0.1'
6
+
7
+
8
+ with open("README.md", "r") as readme:
9
+ description = readme.read()
10
+ # Patch the relative links to absolute URLs that will work on PyPI.
11
+ description2 = re.sub(
12
+ r']\(([\w/.-]+\.png)\)',
13
+ r'](https://github.com/vivarium-collective/cdFBA/raw/main/\1)',
14
+ description)
15
+ long_description = re.sub(
16
+ r']\(([\w/.-]+)\)',
17
+ r'](https://github.com/vivarium-collective/cdFBA/blob/main/\1)',
18
+ description2)
19
+
20
+ setup(
21
+ name="cdFBA",
22
+ version=VERSION,
23
+ author="Tasnif Rahman",
24
+ author_email="trahman@uchc.edu",
25
+ description="",
26
+ long_description=long_description,
27
+ long_description_content_type="text/markdown",
28
+ license="MIT",
29
+ license_files=["LICENSE"],
30
+ url="https://github.com/vivarium-collective/cdFBA",
31
+ # packages=find_packages(),
32
+ packages=[
33
+ 'cdFBA',
34
+ 'cdFBA.processes',
35
+ ],
36
+ classifiers=[
37
+ "Development Status :: 3 - Alpha",
38
+ "Intended Audience :: Developers",
39
+ "License :: OSI Approved :: MIT License",
40
+ "Operating System :: OS Independent",
41
+ "Programming Language :: Python",
42
+ "Programming Language :: Python :: 3",
43
+ "Programming Language :: Python :: 3.9",
44
+ "Programming Language :: Python :: 3.10",
45
+ "Programming Language :: Python :: 3.11",
46
+ ],
47
+ python_requires=">=3.9",
48
+ install_requires=[
49
+ "vivarium-interface",
50
+ "cobra",
51
+ "matplotlib"
52
+ ]
53
+ )