Skip to content

Commit 8a83e2a

Browse files
authored
Merge pull request #78 from Fxe/dev
Unit tests ATP method and Gap fill
2 parents b6c3328 + fca9f00 commit 8a83e2a

File tree

5 files changed

+163
-26
lines changed

5 files changed

+163
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
2+
@eaDir
23

34
# Byte-compiled / optimized / DLL files
45
__pycache__/

modelseedpy/core/msatpcorrection.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class MSATPCorrection:
1919

2020
DEBUG = False
2121

22-
def __init__(self, model, core_template, atp_medias, compartment="c0",
22+
def __init__(self, model, core_template, atp_medias: list, compartment="c0",
2323
max_gapfilling=None, gapfilling_delta=0, atp_hydrolysis_id=None):
2424
"""
2525
@@ -45,8 +45,8 @@ def __init__(self, model, core_template, atp_medias, compartment="c0",
4545
self.atp_hydrolysis = output["reaction"]
4646
self.atp_medias = []
4747
for media in atp_medias:
48-
if isinstance(media,MSMedia):
49-
self.atp_medias.append([media,0.01])
48+
if isinstance(media, MSMedia):
49+
self.atp_medias.append([media, 0.01])
5050
else:
5151
self.atp_medias.append(media)
5252
self.max_gapfilling = max_gapfilling
@@ -163,25 +163,26 @@ def evaluate_growth_media(self):
163163
with self.model:
164164
self.model.objective = self.atp_hydrolysis.id
165165
#self.model.objective = self.model.problem.Objective(Zero,direction="max")
166-
#self.atp_hydrolysis.update_variable_bounds()
166+
167167
logger.debug(f'ATP bounds: ({self.atp_hydrolysis.lower_bound}, {self.atp_hydrolysis.upper_bound})')
168168
#self.model.objective.set_linear_coefficients({self.atp_hydrolysis.forward_variable:1})
169169
pkgmgr = MSPackageManager.get_pkg_mgr(self.model)
170-
for media_tuple in self.atp_medias:
171-
media = media_tuple[0]
170+
for media, minimum_obj in self.atp_medias:
172171
logger.debug('evaluate media %s', media)
173172
pkgmgr.getpkg("KBaseMediaPkg").build_package(media)
174173
logger.debug('model.medium %s', self.model.medium)
175174
solution = self.model.optimize()
176175
logger.debug('evaluate media %s - %f (%s)', media.id, solution.objective_value, solution.status)
177176
self.media_gapfill_stats[media] = None
178177
output[media.id] = solution.objective_value
179-
if solution.objective_value < media_tuple[1] or solution.status != 'optimal':
180-
self.media_gapfill_stats[media] = self.msgapfill.run_gapfilling(media, self.atp_hydrolysis.id,media_tuple[1])
178+
if solution.objective_value < minimum_obj or solution.status != 'optimal':
179+
self.media_gapfill_stats[media] = self.msgapfill.run_gapfilling(media,
180+
self.atp_hydrolysis.id,
181+
minimum_obj)
181182
#IF gapfilling fails - need to activate and penalize the noncore and try again
182-
elif solution.objective_value >= media_tuple[1]:
183+
elif solution.objective_value >= minimum_obj:
183184
self.media_gapfill_stats[media] = {'reversed': {}, 'new': {}}
184-
logger.debug('gapfilling stats:',json.dumps(self.media_gapfill_stats[media],indent=2))
185+
logger.debug('gapfilling stats: %s', json.dumps(self.media_gapfill_stats[media], indent=2))
185186

186187
if MSATPCorrection.DEBUG:
187188
with open('debug.json', 'w') as outfile:
@@ -335,10 +336,13 @@ def run_atp_correction(self):
335336
self.evaluate_growth_media()
336337
self.determine_growth_media()
337338
self.apply_growth_media_gapfilling()
339+
self.evaluate_growth_media()
338340
self.expand_model_to_genome_scale()
341+
return self.build_tests()
339342

340343
@staticmethod
341-
def atp_correction(model,coretemplate,atp_medias = None,atp_objective = "bio2",max_gapfilling = None,gapfilling_delta = 0):
342-
msatpobj = MSATPCorrection(model,coretemplate,atp_medias,atp_objective,max_gapfilling,gapfilling_delta)
343-
msatpobj.run_atp_correction()
344-
return msatpobj
344+
def atp_correction(model, core_template, atp_medias=None, atp_objective="bio2",
345+
max_gapfilling=None, gapfilling_delta=0):
346+
atp_correction = MSATPCorrection(model, core_template, atp_medias, atp_hydrolysis_id=atp_objective,
347+
max_gapfilling=max_gapfilling, gapfilling_delta=gapfilling_delta)
348+
return atp_correction.run_atp_correction()

tests/core/test_msatpcorreption.py

Lines changed: 142 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ def template():
1111
return MSTemplateBuilder.from_dict(json.load(fh)).build()
1212

1313

14+
@pytest.fixture
15+
def template_genome_scale():
16+
with open('./tests/test_data/template_genome_scale_bigg.json', 'r') as fh:
17+
return MSTemplateBuilder.from_dict(json.load(fh)).build()
18+
19+
1420
@pytest.fixture
1521
def get_model():
1622

17-
def _method(ko=None):
23+
def _method(ko=None, added_compounds=None, added_reactions=None):
1824
if ko is None:
1925
ko = []
2026
with open('./tests/test_data/e_coli_core.json', 'r') as fh:
@@ -36,6 +42,13 @@ def _method(ko=None):
3642
r['id'] += '_' + 'c0' # hack cause there is only combo between e0 and c0
3743

3844
model_json['reactions'] = [x for x in model_json['reactions'] if x['id'] not in ko]
45+
46+
if added_compounds:
47+
for o in added_compounds:
48+
model_json['metabolites'].append(o)
49+
if added_reactions:
50+
for o in added_reactions:
51+
model_json['reactions'].append(o)
3952
model = cobra.io.from_json(json.dumps(model_json))
4053
model.reactions.ATPM_c0.lower_bound = 0
4154
model.reactions.ATPM_c0.upper_bound = 1000
@@ -68,25 +81,143 @@ def media_acetate_aerobic():
6881
return media
6982

7083

84+
@pytest.fixture
85+
def media_genome_scale_glucose_aerobic():
86+
media = MSMedia.from_dict({
87+
'glc__D': (-10, 1000),
88+
'o2': (-1000, 1000),
89+
'h': (-1000, 1000),
90+
'h2o': (-1000, 1000),
91+
'pi': (-1000, 1000),
92+
'co2': (-1000, 1000),
93+
'nh4': (-1000, 1000),
94+
'k': (-1000, 1000),
95+
})
96+
return media
97+
98+
7199
@pytest.fixture
72100
def media_all_aerobic(media_glucose_aerobic, media_acetate_aerobic):
73101
return [media_glucose_aerobic, media_acetate_aerobic]
74102

75103

104+
@pytest.fixture
105+
def get_model_with_infinite_atp_loop(get_model, template_genome_scale):
106+
107+
def _method(ko=None):
108+
added_compounds = [{
109+
'id': 'k_c0',
110+
'name': 'K [c]',
111+
'compartment': 'c0',
112+
'charge': 1,
113+
'formula': 'K',
114+
'notes': {},
115+
'annotation': {'sbo': 'SBO:0000247'}
116+
}, {
117+
'id': 'k_e0',
118+
'name': 'K [e]',
119+
'compartment': 'e0',
120+
'charge': 1,
121+
'formula': 'K',
122+
'notes': {},
123+
'annotation': {'sbo': 'SBO:0000247'}
124+
}]
125+
added_reactions = [{
126+
'id': 'EX_k_e0',
127+
'name': 'K exchange',
128+
'metabolites': {'k_e0': -1.0},
129+
'lower_bound': -1000,
130+
'upper_bound': 1000.0,
131+
'gene_reaction_rule': '',
132+
'subsystem': 'Extracellular exchange',
133+
'notes': {},
134+
'annotation': {}
135+
}]
136+
model = get_model(ko, added_compounds, added_reactions)
137+
model.reactions.get_by_id('BIOMASS_Ecoli_core_w_GAM_c0').add_metabolites({
138+
model.metabolites.get_by_id('k_c0'): 0.01
139+
})
140+
model.add_reactions([template_genome_scale.reactions.Kt2r_c.to_reaction(model)])
141+
model.add_reactions([template_genome_scale.reactions.Ktex_c.to_reaction(model)])
142+
model.medium = {
143+
'EX_co2_e0': 1000.0,
144+
'EX_glc__D_e0': 10.0,
145+
'EX_h_e0': 1000.0,
146+
'EX_h2o_e0': 1000.0,
147+
'EX_nh4_e0': 1000.0,
148+
'EX_o2_e0': 1000.0,
149+
'EX_pi_e0': 1000.0,
150+
'EX_k_e0': 1000.0
151+
}
152+
153+
return model
154+
155+
return _method
156+
157+
158+
def test_infinite_atp_model_growth_boost(get_model_with_infinite_atp_loop, template, template_genome_scale, media_glucose_aerobic):
159+
model = get_model_with_infinite_atp_loop()
160+
solution = model.optimize()
161+
assert solution.objective_value > 1.2 # should be around 1.316
162+
assert solution.status == 'optimal'
163+
164+
76165
def test_ms_atp_correction1(get_model, template, media_all_aerobic):
77-
model = get_model(["GLCpts_c0", "NADH16_c0", "GLCpts_c0", "O2t_c0"])
166+
model = get_model(["GLCpts_c0", "NADH16_c0", "CYTBD_c0", "O2t_c0"])
78167
atp_correction = MSATPCorrection(model, template, media_all_aerobic, atp_hydrolysis_id='ATPM_c0')
79168
atp_correction.evaluate_growth_media()
80-
print('non_core_reactions', len(atp_correction.noncore_reactions),
81-
'other_compartments', len(atp_correction.other_compartments),
82-
'original_bounds', len(atp_correction.original_bounds))
83-
for media in atp_correction.media_gapfill_stats:
84-
print(media, atp_correction.media_gapfill_stats[media])
169+
assert len(atp_correction.noncore_reactions) == 1 # the biomass
170+
assert len(atp_correction.other_compartments) == 0 # none
171+
assert len(atp_correction.original_bounds) == 70 # 70 reactions
172+
173+
"""
174+
glc/o2 {'reversed': {}, 'new': {'GLCpts_c0': '>'}}
175+
ac/o2 {'reversed': {}, 'new': {'CYTBD_c0': '>', 'NADH16_c0': '>', 'O2t_c0': '>'}}
176+
"""
85177
atp_correction.determine_growth_media()
86-
print(atp_correction.selected_media)
178+
179+
assert len(atp_correction.selected_media) == 1 # selects glucose
180+
181+
atp_correction.apply_growth_media_gapfilling()
182+
87183
media_eval = atp_correction.evaluate_growth_media()
88-
print(media_eval)
184+
89185
atp_correction.expand_model_to_genome_scale()
90186
tests = atp_correction.build_tests()
91-
print(tests)
92-
assert True
187+
188+
assert tests
189+
assert len(tests) == 1
190+
assert tests[0]['threshold'] > 0
191+
assert tests[0]['objective'] == 'ATPM_c0'
192+
193+
194+
def test_ms_atp_correction_and_gap_fill1(get_model_with_infinite_atp_loop, template, template_genome_scale,
195+
media_glucose_aerobic, media_genome_scale_glucose_aerobic):
196+
from modelseedpy import MSGapfill
197+
198+
model = get_model_with_infinite_atp_loop(['GLCpts_c0', 'GLUSy_c0', 'GLUDy_c0'])
199+
model.reactions.ATPM_c0.lower_bound = 0
200+
model.reactions.ATPM_c0.upper_bound = 1000
201+
202+
atp_correction = MSATPCorrection(model, template, [media_glucose_aerobic], atp_hydrolysis_id='ATPM_c0')
203+
tests = atp_correction.run_atp_correction()
204+
205+
# expected tests = [{'media': MSMedia object, 'is_max_threshold': True, 'threshold': 21.0, 'objective': 'ATPM_c0'}]
206+
207+
assert tests
208+
assert len(tests) == 1
209+
assert tests[0]['threshold'] > 0
210+
assert tests[0]['objective'] == 'ATPM_c0'
211+
212+
gap_fill = MSGapfill(model, [template_genome_scale], [], tests, {}, [])
213+
result = gap_fill.run_gapfilling(media_genome_scale_glucose_aerobic, 'BIOMASS_Ecoli_core_w_GAM_c0', minimum_obj=0.1)
214+
215+
# either GLUSy_c0 or GLUDy_c0 should be gap filled for glutamate
216+
217+
assert result
218+
assert len(result['new']) == 1
219+
assert 'GLUSy_c0' in result['new'] or 'GLUDy_c0' in result['new']
220+
221+
model = gap_fill.integrate_gapfill_solution(result)
222+
223+
assert model

tests/core/test_msgapfill.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_ms_gap_fill1(template, get_model, media_glucose_aerobic):
7070
"""
7171
Test gap filling with glucose aerobic requesting minimal growth value
7272
"""
73-
model = get_model(["GLCpts_c0", "NADH16_c0", "GLCpts_c0", "O2t_c0"])
73+
model = get_model(["CYTBD_c0", "NADH16_c0", "GLCpts_c0", "O2t_c0"])
7474
gap_fill = MSGapfill(model, [template])
7575
result = gap_fill.run_gapfilling(media_glucose_aerobic, 'BIOMASS_Ecoli_core_w_GAM_c0', minimum_obj=0.1)
7676
assert result

tests/test_data/template_genome_scale_bigg.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)