17
17
18
18
class MSATPCorrection :
19
19
20
- def __init__ (self , model , core_template , atp_medias ,compartment = "c0" ,
20
+ DEBUG = False
21
+
22
+ def __init__ (self , model , core_template , atp_medias , compartment = "c0" ,
21
23
max_gapfilling = None , gapfilling_delta = 0 , atp_hydrolysis_id = None ):
22
24
"""
23
25
@@ -60,56 +62,89 @@ def __init__(self, model, core_template, atp_medias,compartment="c0",
60
62
self .lp_filename = None
61
63
self .multiplier = 1.2
62
64
65
+ @staticmethod
66
+ def find_reaction_in_template (model_reaction , template , compartment ):
67
+ template_reaction = None # we save lookup result here
68
+ if model_reaction .id in template .reactions :
69
+ template_reaction = template .reactions .get_by_id (model_reaction .id )
70
+ else :
71
+ msid = FBAHelper .modelseed_id_from_cobra_reaction (model_reaction )
72
+ if msid is not None :
73
+ msid += "_" + compartment
74
+ if msid in template .reactions :
75
+ template_reaction = template .reactions .get_by_id (model_reaction .id [0 :- 1 ])
76
+ else :
77
+ # will leave this here for now
78
+ def split_id_from_index (s ):
79
+ """
80
+ Extracts the last digits of a string example: rxn12345, returns rxn 12345
81
+
82
+ @param s: any string
83
+ @return: string split into head (remaining) + tail (digits)
84
+ """
85
+ str_pos = len (s ) - 1
86
+ while str_pos >= 0 :
87
+ if not s [str_pos ].isdigit ():
88
+ break
89
+ str_pos -= 1
90
+
91
+ return s [:str_pos + 1 ], s [str_pos + 1 :]
92
+
93
+ rxn_id , index = split_id_from_index (model_reaction .id )
94
+ if rxn_id in template .reactions :
95
+ template_reaction = template .reactions .get_by_id (rxn_id )
96
+
97
+ return template_reaction
98
+
63
99
def disable_noncore_reactions (self ):
64
100
"""
65
- Disables all noncore reactions in the model
101
+ Disables all non core reactions in the model
66
102
:return:
67
103
"""
68
- #Must restore reactions before disabling to ensure bounds are not overwritten
104
+ # Must restore reactions before disabling to ensure bounds are not overwritten
69
105
if len (self .noncore_reactions ) > 0 :
70
- self .restore_noncore_reactions (noncore = True ,othercompartment = True )
71
- #Now clearing the existing noncore datastructures
106
+ self .restore_noncore_reactions (noncore = True , othercompartment = True )
107
+ # Now clearing the existing noncore data structures
72
108
self .original_bounds = {}
73
109
self .noncore_reactions = []
74
110
self .other_compartments = []
75
- #Iterating through reactions and disabling
111
+ # Iterating through reactions and disabling
76
112
for reaction in self .model .reactions :
77
113
if reaction .id == self .atp_hydrolysis .id :
78
114
continue
79
115
if FBAHelper .is_ex (reaction ):
80
116
continue
81
117
if FBAHelper .is_biomass (reaction ):
82
118
continue
83
- msid = FBAHelper .modelseed_id_from_cobra_reaction (reaction )
84
- if msid != None :
85
- msid += "_" + self .compartment [0 :1 ]
86
- if FBAHelper .rxn_compartment (reaction ) != self .compartment :
87
- logger .debug (reaction .id + " noncore" )
88
- self .original_bounds [reaction .id ] = (reaction .lower_bound , reaction .upper_bound )
89
- if reaction .lower_bound < 0 :
90
- self .other_compartments .append ([reaction , "<" ])
91
- if reaction .upper_bound > 0 :
92
- self .other_compartments .append ([reaction , ">" ])
93
- reaction .lower_bound = 0
94
- reaction .upper_bound = 0
95
- elif msid in self .coretemplate .reactions :
96
- self .original_bounds [reaction .id ] = (reaction .lower_bound , reaction .upper_bound )
97
- logger .debug (reaction .id + " core" )
98
- if reaction .lower_bound < 0 and self .coretemplate .reactions .get_by_id (reaction .id [0 :- 1 ]).lower_bound >= 0 :
99
- logger .debug (reaction .id + " core but reversible" )
119
+
120
+ self .original_bounds [reaction .id ] = (reaction .lower_bound , reaction .upper_bound )
121
+
122
+ # check if reaction is in core template
123
+ template_reaction = self .find_reaction_in_template (reaction , self .coretemplate , self .compartment [0 :1 ])
124
+
125
+ # update bounds to reaction
126
+ if template_reaction is not None :
127
+ logger .debug (f"{ reaction .id } core" )
128
+ if reaction .lower_bound < 0 and template_reaction .lower_bound >= 0 :
129
+ logger .debug (reaction .id + " core but reversible" )
100
130
self .noncore_reactions .append ([reaction , "<" ])
101
131
reaction .lower_bound = 0
102
- if reaction .upper_bound > 0 and self . coretemplate . reactions . get_by_id ( reaction . id [ 0 : - 1 ]) .upper_bound <= 0 :
103
- logger .debug (reaction .id + " core but reversible" )
132
+ if reaction .upper_bound > 0 and template_reaction .upper_bound <= 0 :
133
+ logger .debug (reaction .id + " core but reversible" )
104
134
self .noncore_reactions .append ([reaction , ">" ])
105
135
reaction .upper_bound = 0
106
136
else :
107
- logger .debug (reaction .id + " noncore" )
108
- self .original_bounds [reaction .id ] = (reaction .lower_bound , reaction .upper_bound )
109
- if reaction .lower_bound < 0 :
110
- self .noncore_reactions .append ([reaction , "<" ])
111
- if reaction .upper_bound > 0 :
112
- self .noncore_reactions .append ([reaction , ">" ])
137
+ logger .debug (f"{ reaction .id } non core" )
138
+ if FBAHelper .rxn_compartment (reaction ) != self .compartment :
139
+ if reaction .lower_bound < 0 :
140
+ self .other_compartments .append ([reaction , "<" ])
141
+ if reaction .upper_bound > 0 :
142
+ self .other_compartments .append ([reaction , ">" ])
143
+ else :
144
+ if reaction .lower_bound < 0 :
145
+ self .noncore_reactions .append ([reaction , "<" ])
146
+ if reaction .upper_bound > 0 :
147
+ self .noncore_reactions .append ([reaction , ">" ])
113
148
reaction .lower_bound = 0
114
149
reaction .upper_bound = 0
115
150
@@ -129,13 +164,14 @@ def evaluate_growth_media(self):
129
164
self .model .objective = self .atp_hydrolysis .id
130
165
#self.model.objective = self.model.problem.Objective(Zero,direction="max")
131
166
#self.atp_hydrolysis.update_variable_bounds()
132
- logger .debug (" ATP bounds:" + str ( self .atp_hydrolysis .lower_bound ) + ":" + str ( self .atp_hydrolysis .upper_bound ) )
167
+ logger .debug (f' ATP bounds: ( { self .atp_hydrolysis .lower_bound } , { self .atp_hydrolysis .upper_bound } )' )
133
168
#self.model.objective.set_linear_coefficients({self.atp_hydrolysis.forward_variable:1})
134
169
pkgmgr = MSPackageManager .get_pkg_mgr (self .model )
135
170
for media_tuple in self .atp_medias :
136
171
media = media_tuple [0 ]
137
172
logger .debug ('evaluate media %s' , media )
138
173
pkgmgr .getpkg ("KBaseMediaPkg" ).build_package (media )
174
+ logger .debug ('model.medium %s' , self .model .medium )
139
175
solution = self .model .optimize ()
140
176
logger .debug ('evaluate media %s - %f (%s)' , media .id , solution .objective_value , solution .status )
141
177
self .media_gapfill_stats [media ] = None
@@ -146,6 +182,11 @@ def evaluate_growth_media(self):
146
182
elif solution .objective_value >= media_tuple [1 ]:
147
183
self .media_gapfill_stats [media ] = {'reversed' : {}, 'new' : {}}
148
184
logger .debug ('gapfilling stats:' ,json .dumps (self .media_gapfill_stats [media ],indent = 2 ))
185
+
186
+ if MSATPCorrection .DEBUG :
187
+ with open ('debug.json' , 'w' ) as outfile :
188
+ json .dump (self .media_gapfill_stats [media ], outfile )
189
+
149
190
return output
150
191
151
192
def determine_growth_media (self ):
@@ -163,10 +204,15 @@ def determine_growth_media(self):
163
204
best_score = gfscore
164
205
if self .max_gapfilling is None :
165
206
self .max_gapfilling = best_score
207
+
208
+ logger .debug (f'max_gapfilling: { self .max_gapfilling } , best_score: { best_score } ' )
209
+
166
210
for media in self .media_gapfill_stats :
167
211
gfscore = 0
168
212
if self .media_gapfill_stats [media ]:
169
213
gfscore = len (self .media_gapfill_stats [media ]["new" ].keys ()) + 0.5 * len (self .media_gapfill_stats [media ]["reversed" ].keys ())
214
+
215
+ logger .debug (f'media gapfilling score: { media .id } : { gfscore } ' )
170
216
if gfscore <= self .max_gapfilling and gfscore <= (best_score + self .gapfilling_delta ):
171
217
self .selected_media .append (media )
172
218
@@ -211,9 +257,10 @@ def expand_model_to_genome_scale(self):
211
257
"""
212
258
self .filtered_noncore = []
213
259
tests = self .build_tests ()
214
- #Must restore noncore reactions and NOT other compartment reactions before running this function - it is not detrimental to run this twice
215
- self .restore_noncore_reactions (noncore = True ,othercompartment = False )
216
- # Extending model with noncore reactions while retaining ATP accuracy
260
+ # Must restore non core reactions and NOT other compartment reactions before running this function
261
+ # it is not detrimental to run this twice
262
+ self .restore_noncore_reactions (noncore = True , othercompartment = False )
263
+ # Extending model with non core reactions while retaining ATP accuracy
217
264
self .filtered_noncore = self .modelutl .reaction_expansion_test (self .noncore_reactions ,tests )
218
265
# Removing filtered reactions
219
266
for item in self .filtered_noncore :
@@ -225,8 +272,8 @@ def expand_model_to_genome_scale(self):
225
272
# reaction.update_variable_bounds()
226
273
if item [0 ].lower_bound == 0 and item [0 ].upper_bound == 0 :
227
274
self .model .remove_reactions ([item [0 ]])
228
- #Restoring other compartment reactions but not the core because this would undo reaction filtering
229
- self .restore_noncore_reactions (noncore = False ,othercompartment = True )
275
+ # Restoring other compartment reactions but not the core because this would undo reaction filtering
276
+ self .restore_noncore_reactions (noncore = False , othercompartment = True )
230
277
231
278
def restore_noncore_reactions (self ,noncore = True ,othercompartment = True ):
232
279
"""
@@ -247,8 +294,7 @@ def restore_noncore_reactions(self,noncore = True,othercompartment = True):
247
294
reaction .lower_bound = self .original_bounds [reaction .id ][0 ]
248
295
reaction .upper_bound = self .original_bounds [reaction .id ][1 ]
249
296
250
-
251
- def build_tests (self ,multiplier = None ):
297
+ def build_tests (self , multiplier = None ):
252
298
"""Build tests based on ATP media evaluations
253
299
254
300
Parameters
@@ -264,23 +310,28 @@ def build_tests(self,multiplier=None):
264
310
Raises
265
311
------
266
312
"""
267
- if multiplier == None :
313
+ if multiplier is None :
268
314
multiplier = self .multiplier
269
315
tests = []
270
316
self .model .objective = self .atp_hydrolysis .id
271
317
for media in self .selected_media :
272
318
self .modelutl .pkgmgr .getpkg ("KBaseMediaPkg" ).build_package (media )
273
- obj_value = model .slim_optimize ()
274
- logger .debug (media .name ," = " ,obj_value )
275
- tests .append ({"media" :media ,"is_max_threshold" : True ,"threshold" :multiplier * obj_value ,"objective" :self .atp_hydrolysis .id })
319
+ obj_value = self .model .slim_optimize ()
320
+ logger .debug (f'{ media .name } = { obj_value } ' )
321
+ tests .append ({
322
+ "media" : media ,
323
+ "is_max_threshold" : True ,
324
+ "threshold" : multiplier * obj_value ,
325
+ "objective" : self .atp_hydrolysis .id
326
+ })
276
327
return tests
277
328
278
329
def run_atp_correction (self ):
279
330
"""
280
331
Runs the entire ATP method
281
332
:return:
282
333
"""
283
- #Ensure all specified media work
334
+ # Ensure all specified media work
284
335
self .evaluate_growth_media ()
285
336
self .determine_growth_media ()
286
337
self .apply_growth_media_gapfilling ()
0 commit comments