gsrap 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl
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.
- gsrap/.ipynb_checkpoints/__init__-checkpoint.py +22 -6
- gsrap/__init__.py +22 -6
- gsrap/commons/.ipynb_checkpoints/__init__-checkpoint.py +1 -0
- gsrap/commons/.ipynb_checkpoints/excelhub-checkpoint.py +49 -1
- gsrap/commons/.ipynb_checkpoints/medium-checkpoint.py +39 -4
- gsrap/commons/.ipynb_checkpoints/memoteutils-checkpoint.py +132 -0
- gsrap/commons/__init__.py +1 -0
- gsrap/commons/excelhub.py +49 -1
- gsrap/commons/medium.py +39 -4
- gsrap/commons/memoteutils.py +132 -0
- gsrap/mkmodel/.ipynb_checkpoints/mkmodel-checkpoint.py +28 -8
- gsrap/mkmodel/mkmodel.py +28 -8
- gsrap/parsedb/.ipynb_checkpoints/cycles-checkpoint.py +128 -0
- gsrap/parsedb/.ipynb_checkpoints/introduce-checkpoint.py +1 -9
- gsrap/parsedb/.ipynb_checkpoints/manual-checkpoint.py +27 -0
- gsrap/parsedb/.ipynb_checkpoints/parsedb-checkpoint.py +48 -25
- gsrap/parsedb/.ipynb_checkpoints/repeating-checkpoint.py +9 -0
- gsrap/parsedb/cycles.py +128 -0
- gsrap/parsedb/introduce.py +1 -9
- gsrap/parsedb/manual.py +27 -0
- gsrap/parsedb/parsedb.py +48 -25
- gsrap/parsedb/repeating.py +9 -0
- {gsrap-0.8.0.dist-info → gsrap-0.8.2.dist-info}/METADATA +1 -1
- {gsrap-0.8.0.dist-info → gsrap-0.8.2.dist-info}/RECORD +27 -23
- {gsrap-0.8.0.dist-info → gsrap-0.8.2.dist-info}/LICENSE.txt +0 -0
- {gsrap-0.8.0.dist-info → gsrap-0.8.2.dist-info}/WHEEL +0 -0
- {gsrap-0.8.0.dist-info → gsrap-0.8.2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import contextlib
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import memote
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_memote_results_dict(logger, model):
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger.info(f"Running selected modules of MEMOTE v{importlib.metadata.metadata('memote')['Version']}...")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# launch memote (only relevant modules)
|
|
19
|
+
with open(os.devnull, 'w') as devnull:
|
|
20
|
+
with contextlib.redirect_stdout(devnull), contextlib.redirect_stderr(devnull):
|
|
21
|
+
try: memote_report = memote.suite.api.test_model(model, exclusive=[
|
|
22
|
+
'test_annotation',
|
|
23
|
+
'test_sbo',
|
|
24
|
+
'test_stoichiometric_consistency',
|
|
25
|
+
'test_reaction_mass_balance',
|
|
26
|
+
'test_reaction_charge_balance',
|
|
27
|
+
'test_find_disconnected',
|
|
28
|
+
'test_find_reactions_unbounded_flux_default_condition'], results=True)
|
|
29
|
+
except ValueError: memote_report = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# parse memote's results
|
|
33
|
+
results_dict = {}
|
|
34
|
+
results_dict['version'] = importlib.metadata.version("memote")
|
|
35
|
+
test_results = dict(memote_report[1])['tests']
|
|
36
|
+
sections = {
|
|
37
|
+
'consistency': [
|
|
38
|
+
('test_stoichiometric_consistency', 3),
|
|
39
|
+
('test_reaction_mass_balance', 1),
|
|
40
|
+
('test_reaction_charge_balance', 1),
|
|
41
|
+
('test_find_disconnected', 1),
|
|
42
|
+
('test_find_reactions_unbounded_flux_default_condition', 1)
|
|
43
|
+
],
|
|
44
|
+
'annotation_M': [
|
|
45
|
+
('test_metabolite_annotation_presence', 1),
|
|
46
|
+
('test_metabolite_annotation_overview', 1),
|
|
47
|
+
('test_metabolite_annotation_wrong_ids', 1),
|
|
48
|
+
('test_metabolite_id_namespace_consistency', 1),
|
|
49
|
+
],
|
|
50
|
+
'annotation_R': [
|
|
51
|
+
('test_reaction_annotation_presence', 1),
|
|
52
|
+
('test_reaction_annotation_overview', 1),
|
|
53
|
+
('test_reaction_annotation_wrong_ids', 1),
|
|
54
|
+
('test_reaction_id_namespace_consistency', 1),
|
|
55
|
+
],
|
|
56
|
+
'annotation_G': [
|
|
57
|
+
('test_gene_product_annotation_presence', 1),
|
|
58
|
+
('test_gene_product_annotation_overview', 1),
|
|
59
|
+
('test_gene_product_annotation_wrong_ids', 1),
|
|
60
|
+
],
|
|
61
|
+
'annotation_SBO': [
|
|
62
|
+
('test_metabolite_sbo_presence', 1),
|
|
63
|
+
('test_metabolite_specific_sbo_presence', 1),
|
|
64
|
+
('test_reaction_sbo_presence', 1),
|
|
65
|
+
('test_metabolic_reaction_specific_sbo_presence', 1),
|
|
66
|
+
('test_transport_reaction_specific_sbo_presence', 1),
|
|
67
|
+
('test_exchange_specific_sbo_presence', 1),
|
|
68
|
+
('test_demand_specific_sbo_presence', 1),
|
|
69
|
+
('test_sink_specific_sbo_presence', 1),
|
|
70
|
+
('test_gene_sbo_presence', 1),
|
|
71
|
+
('test_gene_specific_sbo_presence', 1),
|
|
72
|
+
('test_biomass_specific_sbo_presence', 1),
|
|
73
|
+
],
|
|
74
|
+
}
|
|
75
|
+
section_multipliers = {
|
|
76
|
+
'consistency': 3,
|
|
77
|
+
'annotation_M': 1,
|
|
78
|
+
'annotation_R': 1,
|
|
79
|
+
'annotation_G': 1,
|
|
80
|
+
'annotation_SBO': 2,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
numerator_total = 0
|
|
85
|
+
denominator_total = 0
|
|
86
|
+
for section, metrics in sections.items():
|
|
87
|
+
numerator = 0
|
|
88
|
+
denominator = 0
|
|
89
|
+
results_dict[section] = {}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# iterate metrics of this section:
|
|
93
|
+
for metric, metric_multiplier in metrics:
|
|
94
|
+
metric_raw = test_results[metric]['metric']
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# no subcategories here:
|
|
98
|
+
if type(metric_raw) == float:
|
|
99
|
+
metric_percentage = ((1- metric_raw ) *100)
|
|
100
|
+
numerator = numerator + (metric_percentage * metric_multiplier)
|
|
101
|
+
denominator = denominator + metric_multiplier
|
|
102
|
+
results_dict[section][metric] = round(metric_percentage, 1)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# there are subcategories (like in the case of M/R/G/SBO annots)
|
|
106
|
+
else:
|
|
107
|
+
results_dict[section][metric] = {}
|
|
108
|
+
for key, value in metric_raw.items():
|
|
109
|
+
n_subcategories = len(metric_raw)
|
|
110
|
+
multiplier_corrected = metric_multiplier / n_subcategories
|
|
111
|
+
metric_percentage = ((1- value ) *100)
|
|
112
|
+
numerator = numerator + (metric_percentage * multiplier_corrected)
|
|
113
|
+
denominator = denominator + multiplier_corrected
|
|
114
|
+
results_dict[section][metric][key] = round(metric_percentage, 1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# compute the subtotal:
|
|
118
|
+
sub_total = numerator / denominator
|
|
119
|
+
results_dict[section]['sub_total'] = int(round(sub_total, 0))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# compute the total:
|
|
123
|
+
denominator_total = denominator_total + section_multipliers[section] *denominator
|
|
124
|
+
numerator_total = numerator_total + section_multipliers[section] *numerator
|
|
125
|
+
total = numerator_total / denominator_total
|
|
126
|
+
results_dict['total'] = int(round(total, 0))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
logger.info(f"Done! MEMOTE Total Score: {results_dict['total']}%.")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
return results_dict
|
|
@@ -41,8 +41,15 @@ from ..commons import log_unbalances
|
|
|
41
41
|
from ..commons import format_expansion
|
|
42
42
|
from ..commons import comparative_table
|
|
43
43
|
from ..commons import download_keggorg
|
|
44
|
+
from ..commons import initialize_model
|
|
45
|
+
from ..commons import get_memote_results_dict
|
|
46
|
+
|
|
44
47
|
|
|
45
48
|
from ..runsims.biosynth import biosynthesis_on_media
|
|
49
|
+
from ..runsims.simplegrowth import grow_on_media
|
|
50
|
+
|
|
51
|
+
from ..parsedb.cycles import verify_egc_all
|
|
52
|
+
|
|
46
53
|
|
|
47
54
|
|
|
48
55
|
|
|
@@ -100,7 +107,7 @@ def create_model_incore(params):
|
|
|
100
107
|
|
|
101
108
|
###### GAPFILLING
|
|
102
109
|
# force inclusion of reactions:
|
|
103
|
-
include_forced(logger, model, universe, args.
|
|
110
|
+
include_forced(logger, model, universe, args.include)
|
|
104
111
|
|
|
105
112
|
# remove missing conditional precursors + get the 'cond_col_dict' dict.
|
|
106
113
|
# 'cond_col_dict' is str-to-str: {'pheme_c': 'M00868: 1/8; M00121: 2/12;', 'hemeO_c': 'gr_HemeO: 0/1'}
|
|
@@ -111,15 +118,15 @@ def create_model_incore(params):
|
|
|
111
118
|
if response == 1: return 1
|
|
112
119
|
|
|
113
120
|
# gap-fill based on media:
|
|
114
|
-
df_B = gapfill_on_media(logger, model, universe, dbexp, args.
|
|
121
|
+
df_B = gapfill_on_media(logger, model, universe, dbexp, args.gapfill, cond_col_dict, args.excludeorp)
|
|
115
122
|
if type(df_B)==int: return 1
|
|
116
123
|
|
|
117
124
|
# force removal of reactions
|
|
118
|
-
setattr(args, '
|
|
119
|
-
remove_forced(logger, model, universe, args.
|
|
125
|
+
setattr(args, 'remove', '-') # experimental feature, not public. It's main purpose was to test gap-filling in biolog_on_media().
|
|
126
|
+
remove_forced(logger, model, universe, args.remove)
|
|
120
127
|
|
|
121
128
|
# perform Biolog(R) curation based on media
|
|
122
|
-
df_P = biolog_on_media(logger, model, universe, dbexp, args.
|
|
129
|
+
df_P = biolog_on_media(logger, model, universe, dbexp, args.gapfill, args.biolog, args.excludeorp, args.cnps)
|
|
123
130
|
if type(df_P)==int: return 1
|
|
124
131
|
|
|
125
132
|
|
|
@@ -139,9 +146,12 @@ def create_model_incore(params):
|
|
|
139
146
|
|
|
140
147
|
|
|
141
148
|
|
|
142
|
-
###### CHECKS
|
|
149
|
+
###### CHECKS 1
|
|
150
|
+
# check erroneous EGCs
|
|
151
|
+
verify_egc_all(logger, model, args.outdir)
|
|
152
|
+
|
|
143
153
|
# check blocked metabolites / dead-ends
|
|
144
|
-
df_S = biosynthesis_on_media(logger, model, dbexp, args.
|
|
154
|
+
df_S = biosynthesis_on_media(logger, model, dbexp, args.gapfill, args.biosynth)
|
|
145
155
|
if type(df_S)==int: return 1
|
|
146
156
|
|
|
147
157
|
|
|
@@ -149,6 +159,16 @@ def create_model_incore(params):
|
|
|
149
159
|
###### POLISHING 3
|
|
150
160
|
# reset growth environment befor saving the model
|
|
151
161
|
gempipe.reset_growth_env(model)
|
|
162
|
+
|
|
163
|
+
# initialize model
|
|
164
|
+
response = initialize_model(logger, model, dbexp, args.initialize, args.gapfill)
|
|
165
|
+
if response==1: return 1
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
###### CHECKS 2
|
|
170
|
+
# compute Memote metrics
|
|
171
|
+
memote_results_dict = get_memote_results_dict(logger, model)
|
|
152
172
|
|
|
153
173
|
|
|
154
174
|
|
|
@@ -159,7 +179,7 @@ def create_model_incore(params):
|
|
|
159
179
|
cobra.io.write_sbml_model(model, f'{args.outdir}/{model.id}.xml') # SBML # groups are saved only to SBML
|
|
160
180
|
logger.info(f"'{args.outdir}/{model.id}.xml' created!")
|
|
161
181
|
force_id_on_sbml(f'{args.outdir}/{model.id}.xml', model.id) # force introduction of the 'id=""' field
|
|
162
|
-
sheets_dict = write_excel_model(model, f'{args.outdir}/{model.id}.mkmodel.xlsx', args.nofigs, None, df_B, df_P, df_S)
|
|
182
|
+
sheets_dict = write_excel_model(model, f'{args.outdir}/{model.id}.mkmodel.xlsx', args.nofigs, memote_results_dict, None, df_B, df_P, df_S)
|
|
163
183
|
logger.info(f"'{args.outdir}/{model.id}.mkmodel.xlsx' created!")
|
|
164
184
|
|
|
165
185
|
|
gsrap/mkmodel/mkmodel.py
CHANGED
|
@@ -41,8 +41,15 @@ from ..commons import log_unbalances
|
|
|
41
41
|
from ..commons import format_expansion
|
|
42
42
|
from ..commons import comparative_table
|
|
43
43
|
from ..commons import download_keggorg
|
|
44
|
+
from ..commons import initialize_model
|
|
45
|
+
from ..commons import get_memote_results_dict
|
|
46
|
+
|
|
44
47
|
|
|
45
48
|
from ..runsims.biosynth import biosynthesis_on_media
|
|
49
|
+
from ..runsims.simplegrowth import grow_on_media
|
|
50
|
+
|
|
51
|
+
from ..parsedb.cycles import verify_egc_all
|
|
52
|
+
|
|
46
53
|
|
|
47
54
|
|
|
48
55
|
|
|
@@ -100,7 +107,7 @@ def create_model_incore(params):
|
|
|
100
107
|
|
|
101
108
|
###### GAPFILLING
|
|
102
109
|
# force inclusion of reactions:
|
|
103
|
-
include_forced(logger, model, universe, args.
|
|
110
|
+
include_forced(logger, model, universe, args.include)
|
|
104
111
|
|
|
105
112
|
# remove missing conditional precursors + get the 'cond_col_dict' dict.
|
|
106
113
|
# 'cond_col_dict' is str-to-str: {'pheme_c': 'M00868: 1/8; M00121: 2/12;', 'hemeO_c': 'gr_HemeO: 0/1'}
|
|
@@ -111,15 +118,15 @@ def create_model_incore(params):
|
|
|
111
118
|
if response == 1: return 1
|
|
112
119
|
|
|
113
120
|
# gap-fill based on media:
|
|
114
|
-
df_B = gapfill_on_media(logger, model, universe, dbexp, args.
|
|
121
|
+
df_B = gapfill_on_media(logger, model, universe, dbexp, args.gapfill, cond_col_dict, args.excludeorp)
|
|
115
122
|
if type(df_B)==int: return 1
|
|
116
123
|
|
|
117
124
|
# force removal of reactions
|
|
118
|
-
setattr(args, '
|
|
119
|
-
remove_forced(logger, model, universe, args.
|
|
125
|
+
setattr(args, 'remove', '-') # experimental feature, not public. It's main purpose was to test gap-filling in biolog_on_media().
|
|
126
|
+
remove_forced(logger, model, universe, args.remove)
|
|
120
127
|
|
|
121
128
|
# perform Biolog(R) curation based on media
|
|
122
|
-
df_P = biolog_on_media(logger, model, universe, dbexp, args.
|
|
129
|
+
df_P = biolog_on_media(logger, model, universe, dbexp, args.gapfill, args.biolog, args.excludeorp, args.cnps)
|
|
123
130
|
if type(df_P)==int: return 1
|
|
124
131
|
|
|
125
132
|
|
|
@@ -139,9 +146,12 @@ def create_model_incore(params):
|
|
|
139
146
|
|
|
140
147
|
|
|
141
148
|
|
|
142
|
-
###### CHECKS
|
|
149
|
+
###### CHECKS 1
|
|
150
|
+
# check erroneous EGCs
|
|
151
|
+
verify_egc_all(logger, model, args.outdir)
|
|
152
|
+
|
|
143
153
|
# check blocked metabolites / dead-ends
|
|
144
|
-
df_S = biosynthesis_on_media(logger, model, dbexp, args.
|
|
154
|
+
df_S = biosynthesis_on_media(logger, model, dbexp, args.gapfill, args.biosynth)
|
|
145
155
|
if type(df_S)==int: return 1
|
|
146
156
|
|
|
147
157
|
|
|
@@ -149,6 +159,16 @@ def create_model_incore(params):
|
|
|
149
159
|
###### POLISHING 3
|
|
150
160
|
# reset growth environment befor saving the model
|
|
151
161
|
gempipe.reset_growth_env(model)
|
|
162
|
+
|
|
163
|
+
# initialize model
|
|
164
|
+
response = initialize_model(logger, model, dbexp, args.initialize, args.gapfill)
|
|
165
|
+
if response==1: return 1
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
###### CHECKS 2
|
|
170
|
+
# compute Memote metrics
|
|
171
|
+
memote_results_dict = get_memote_results_dict(logger, model)
|
|
152
172
|
|
|
153
173
|
|
|
154
174
|
|
|
@@ -159,7 +179,7 @@ def create_model_incore(params):
|
|
|
159
179
|
cobra.io.write_sbml_model(model, f'{args.outdir}/{model.id}.xml') # SBML # groups are saved only to SBML
|
|
160
180
|
logger.info(f"'{args.outdir}/{model.id}.xml' created!")
|
|
161
181
|
force_id_on_sbml(f'{args.outdir}/{model.id}.xml', model.id) # force introduction of the 'id=""' field
|
|
162
|
-
sheets_dict = write_excel_model(model, f'{args.outdir}/{model.id}.mkmodel.xlsx', args.nofigs, None, df_B, df_P, df_S)
|
|
182
|
+
sheets_dict = write_excel_model(model, f'{args.outdir}/{model.id}.mkmodel.xlsx', args.nofigs, memote_results_dict, None, df_B, df_P, df_S)
|
|
163
183
|
logger.info(f"'{args.outdir}/{model.id}.mkmodel.xlsx' created!")
|
|
164
184
|
|
|
165
185
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import cobra
|
|
7
|
+
import gempipe
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from ..commons import fba_no_warnings
|
|
11
|
+
from ..commons import get_optthr
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def verify_egc(logger, model, mid, outdir):
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# changes as not permament:
|
|
19
|
+
found_egc = False
|
|
20
|
+
with model:
|
|
21
|
+
|
|
22
|
+
# close (0; 0) all the exchange reactions:
|
|
23
|
+
gempipe.close_boundaries(model)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# create a dissipation reaction:
|
|
27
|
+
dissip = cobra.Reaction(f'__dissip__{mid}')
|
|
28
|
+
model.add_reactions([dissip])
|
|
29
|
+
dissip = model.reactions.get_by_id(f'__dissip__{mid}')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# define the dissipation reaction:
|
|
33
|
+
modeled_mids = [m.id for m in model.metabolites]
|
|
34
|
+
if mid == 'atp':
|
|
35
|
+
dissip_string = 'atp_c + h2o_c --> adp_c + pi_c + h_c'
|
|
36
|
+
elif mid == 'ctp':
|
|
37
|
+
dissip_string = 'ctp_c + h2o_c --> cdp_c + pi_c + h_c'
|
|
38
|
+
elif mid == 'gtp':
|
|
39
|
+
dissip_string = 'gtp_c + h2o_c --> gdp_c + pi_c + h_c'
|
|
40
|
+
elif mid == 'utp':
|
|
41
|
+
dissip_string = 'utp_c + h2o_c --> udp_c + pi_c + h_c'
|
|
42
|
+
elif mid == 'itp':
|
|
43
|
+
dissip_string = 'itp_c + h2o_c --> idp_c + pi_c + h_c'
|
|
44
|
+
elif mid == 'nadh':
|
|
45
|
+
dissip_string = 'nadh_c --> nad_c + h_c'
|
|
46
|
+
elif mid == 'nadph':
|
|
47
|
+
dissip_string = 'nadph_c --> nadp_c + h_c'
|
|
48
|
+
elif mid == 'fadh2':
|
|
49
|
+
dissip_string = 'fadh2_c --> fad_c + 2.0 h_c'
|
|
50
|
+
elif mid == 'accoa':
|
|
51
|
+
dissip_string = 'accoa_c + h2o_c --> ac_c + coa_c + h_c'
|
|
52
|
+
elif mid == 'glu__L':
|
|
53
|
+
dissip_string = 'glu__L_c + h2o_c --> akg_c + nh4_c + 2.0 h_c'
|
|
54
|
+
elif mid == 'q8h2':
|
|
55
|
+
dissip_string = 'q8h2_c --> q8_c + 2.0 h_c'
|
|
56
|
+
dissip.build_reaction_from_string(dissip_string)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# set the objective and optimize:
|
|
60
|
+
model.objective = f'__dissip__{mid}'
|
|
61
|
+
res, obj_value, status = fba_no_warnings(model)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# apply the threshold:
|
|
65
|
+
obj_value = res.objective_value
|
|
66
|
+
status = res.status
|
|
67
|
+
if status == 'optimal' and obj_value >= get_optthr():
|
|
68
|
+
found_egc = True
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# get suspect !=0 fluxes
|
|
72
|
+
fluxes = res.fluxes
|
|
73
|
+
# get interesting fluxes (get_optthr() tries to take into account the approximation in glpk and cplex solvers)
|
|
74
|
+
fluxes_interesting = fluxes[(fluxes > get_optthr()) | (fluxes < -get_optthr())]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# create a model for escher, remove Rs not beloning to the cycle
|
|
78
|
+
model_copy = model.copy()
|
|
79
|
+
all_rids = [r.id for r in model_copy.reactions]
|
|
80
|
+
to_delete = set(all_rids) - set(fluxes_interesting.index)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# trick to avoid the WARNING "cobra/core/group.py:147: UserWarning: need to pass in a list"
|
|
84
|
+
# triggered when trying to remove reactions that are included in groups.
|
|
85
|
+
with warnings.catch_warnings(): # temporarily suppress warnings for this block
|
|
86
|
+
warnings.simplefilter("ignore") # ignore all warnings
|
|
87
|
+
cobra_logger = logging.getLogger("cobra.util.solver")
|
|
88
|
+
old_level = cobra_logger.level
|
|
89
|
+
cobra_logger.setLevel(logging.ERROR)
|
|
90
|
+
|
|
91
|
+
# triggering code
|
|
92
|
+
model_copy.remove_reactions(to_delete) # should work also with IDs
|
|
93
|
+
|
|
94
|
+
# restore original behaviour:
|
|
95
|
+
cobra_logger.setLevel(old_level)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# save JSON to direct import in Escher:
|
|
99
|
+
outfile = os.path.join(outdir, f'EGC_{mid}.json')
|
|
100
|
+
cobra.io.save_json_model(model_copy, outfile)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# log some messages
|
|
104
|
+
rid_labels = []
|
|
105
|
+
for rid, flux in fluxes_interesting.to_dict().items():
|
|
106
|
+
rid_label = "'" + rid + "'"
|
|
107
|
+
# mark reversible reactions composing the cycle:
|
|
108
|
+
r = model.reactions.get_by_id(rid)
|
|
109
|
+
if r.lower_bound < 0 and r.upper_bound > 0:
|
|
110
|
+
rid_label = rid_label + '(<=>)'
|
|
111
|
+
rid_labels.append(rid_label)
|
|
112
|
+
logger.warning(f"Found erroneous EGC (N={len(model_copy.reactions)}) for '{mid}' (f={obj_value}): [{', '.join(rid_labels)}]. EGC saved to '{outfile}' to be inspected with Escher-FBA.")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
return found_egc
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def verify_egc_all(logger, model, outdir='./', mids_to_check=['atp','ctp','gtp','utp','itp','nadh','nadph','fadh2','accoa','glu__L','q8h2']):
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
all_results = []
|
|
123
|
+
for mid in mids_to_check:
|
|
124
|
+
all_results.append(verify_egc(logger, model, mid, outdir))
|
|
125
|
+
if any(all_results)==False:
|
|
126
|
+
logger.info("Found 0 erroneous energy-generating cycles (EGCs).")
|
|
127
|
+
|
|
128
|
+
|
|
@@ -287,15 +287,7 @@ def introduce_transporters(logger, db, model, idcollection_dict, kegg_reaction_t
|
|
|
287
287
|
r = model.reactions.get_by_id(f'EX_{mid_e}')
|
|
288
288
|
r.name = f"Exchange for {model.metabolites.get_by_id(mid_e).name}"
|
|
289
289
|
r.build_reaction_from_string(f'{mid_e} --> ')
|
|
290
|
-
|
|
291
|
-
# basics:
|
|
292
|
-
'glc__D_e', 'nh4_e', 'pi_e', 'so4_e', 'h2o_e', 'h_e', 'o2_e', 'co2_e',
|
|
293
|
-
# metals:
|
|
294
|
-
'cu2_e', 'mobd_e', 'fe2_e', 'cobalt2_e',
|
|
295
|
-
]:
|
|
296
|
-
r.bounds = (-1000, 1000)
|
|
297
|
-
else:
|
|
298
|
-
r.bounds = (0, 1000)
|
|
290
|
+
r.bounds = (0, 1000)
|
|
299
291
|
|
|
300
292
|
# add SBO annotation
|
|
301
293
|
r.annotation['sbo'] = ['SBO:0000627'] # exchange reaction
|
|
@@ -19,6 +19,33 @@ def get_rids_with_mancheck_gpr():
|
|
|
19
19
|
return rids_mancheck_gpr
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def get_rids_with_mancheck_balancing():
|
|
23
|
+
rids_mancheck_bal = [ # same reactions involving ATP can be reversible
|
|
24
|
+
|
|
25
|
+
# SECTION "reversible both in KEGG and MetaCyc"
|
|
26
|
+
'PGK', 'SUCOAS', 'ADK1', 'GK1', 'NNATr', 'CYTK1', 'ACKr',
|
|
27
|
+
'DGK1', 'PPAKr', 'ATPSr', 'NDPK10',
|
|
28
|
+
|
|
29
|
+
### SECTION "reversible in KEGG but not in MetaCyc" ###
|
|
30
|
+
'CYTK2', # clearly reversible in KEGG but not in MetaCyc (RXN-7913)
|
|
31
|
+
'DADK', # clearly reversible in KEGG but not in MetaCyc (DEOXYADENYLATE-KINASE-RXN)
|
|
32
|
+
'UMPK', # clearly reversible in KEGG but not in MetaCyc (RXN-12002)
|
|
33
|
+
'NDPK1', # clearly reversible in KEGG but not in MetaCyc (GDPKIN-RXN)
|
|
34
|
+
'NDPK2', # clearly reversible in KEGG but not in MetaCyc (UDPKIN-RXN)
|
|
35
|
+
'NDPK3', # clearly reversible in KEGG but not in MetaCyc (CDPKIN-RXN)
|
|
36
|
+
'NDPK4', # clearly reversible in KEGG but not in MetaCyc (DTDPKIN-RXN)
|
|
37
|
+
'NDPK5', # clearly reversible in KEGG but not in MetaCyc (DGDPKIN-RXN)
|
|
38
|
+
'NDPK6', # clearly reversible in KEGG but not in MetaCyc (DUDPKIN-RXN)
|
|
39
|
+
'NDPK7', # clearly reversible in KEGG but not in MetaCyc (DCDPKIN-RXN)
|
|
40
|
+
'NDPK8', # clearly reversible in KEGG but not in MetaCyc (DADPKIN-RXN)
|
|
41
|
+
'NDPK9', # clearly reversible in KEGG but not in MetaCyc (RXN-14120)
|
|
42
|
+
|
|
43
|
+
### SECTION "missing reversibility info" ###
|
|
44
|
+
'LPHERA',
|
|
45
|
+
]
|
|
46
|
+
return rids_mancheck_bal
|
|
47
|
+
|
|
48
|
+
|
|
22
49
|
|
|
23
50
|
def get_manual_sinks():
|
|
24
51
|
|
|
@@ -18,6 +18,9 @@ from ..commons import adjust_biomass_precursors
|
|
|
18
18
|
from ..commons import count_undrawn_rids
|
|
19
19
|
from ..commons import format_expansion
|
|
20
20
|
from ..commons import download_keggorg
|
|
21
|
+
from ..commons import initialize_model
|
|
22
|
+
from ..commons import get_memote_results_dict
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
from .introduce import introduce_metabolites
|
|
23
26
|
from .introduce import introduce_reactions
|
|
@@ -35,6 +38,8 @@ from ..runsims.biosynth import biosynthesis_on_media
|
|
|
35
38
|
|
|
36
39
|
from ..mkmodel.polishing import remove_disconnected
|
|
37
40
|
|
|
41
|
+
from .cycles import verify_egc_all
|
|
42
|
+
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
|
|
@@ -173,41 +178,59 @@ def main(args, logger):
|
|
|
173
178
|
|
|
174
179
|
|
|
175
180
|
###### CHECKS 2
|
|
176
|
-
# check
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
# check erroneous EGCs
|
|
182
|
+
verify_egc_all(logger, universe, args.outdir)
|
|
183
|
+
|
|
179
184
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
if not args.justparse:
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
###### CHECKS 3
|
|
189
|
+
# check growth on minmal media
|
|
190
|
+
df_G = grow_on_media(logger, universe, dbexp, args.media, '-', True)
|
|
191
|
+
if type(df_G)==int: return 1
|
|
184
192
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
# check blocked biomass precursors
|
|
194
|
+
cond_col_dict = adjust_biomass_precursors(logger, universe, universe, 1.0)
|
|
195
|
+
df_E = precursors_on_media(logger, universe, universe, dbexp, args.media, cond_col_dict, args.precursors)
|
|
196
|
+
if type(df_E)==int: return 1
|
|
197
|
+
|
|
198
|
+
# check blocked metabolites / dead-ends
|
|
199
|
+
df_S = biosynthesis_on_media(logger, universe, dbexp, args.media, args.biosynth)
|
|
200
|
+
if type(df_S)==int: return 1
|
|
188
201
|
|
|
189
202
|
|
|
190
203
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
204
|
+
###### POLISHING 2
|
|
205
|
+
# reset growth environment befor saving the model
|
|
206
|
+
gempipe.reset_growth_env(universe)
|
|
207
|
+
|
|
208
|
+
# initialize model
|
|
209
|
+
response = initialize_model(logger, universe, dbexp, args.initialize, args.media)
|
|
210
|
+
if response==1: return 1
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
###### CHECKS 4
|
|
215
|
+
# compute Memote metrics
|
|
216
|
+
memote_results_dict = get_memote_results_dict(logger, universe)
|
|
217
|
+
|
|
195
218
|
|
|
196
219
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
# output the universe
|
|
221
|
+
logger.info("Writing universal model...")
|
|
222
|
+
cobra.io.save_json_model(universe, f'{args.outdir}/universe.json')
|
|
223
|
+
logger.info(f"'{args.outdir}/universe.json' created!")
|
|
224
|
+
cobra.io.write_sbml_model(universe, f'{args.outdir}/universe.xml') # groups are saved only to SBML
|
|
225
|
+
logger.info(f"'{args.outdir}/universe.xml' created!")
|
|
226
|
+
force_id_on_sbml(f'{args.outdir}/universe.xml', 'universe') # force introduction of the 'id=""' field
|
|
227
|
+
sheets_dict = write_excel_model(universe, f'{args.outdir}/universe.parsedb.xlsx', args.nofigs, memote_results_dict, df_E, None, None, df_S, df_C)
|
|
228
|
+
logger.info(f"'{args.outdir}/universe.parsedb.xlsx' created!")
|
|
206
229
|
|
|
207
230
|
|
|
208
231
|
|
|
209
|
-
###### CHECKS
|
|
210
|
-
# check if universal escher map
|
|
232
|
+
###### CHECKS 4
|
|
233
|
+
# check if universal escher map is updated:
|
|
211
234
|
count_undrawn_rids(logger, universe, lastmap)
|
|
212
235
|
|
|
213
236
|
|
|
@@ -4,6 +4,7 @@ import cobra
|
|
|
4
4
|
|
|
5
5
|
from .manual import get_deprecated_kos
|
|
6
6
|
from .manual import get_rids_with_mancheck_gpr
|
|
7
|
+
from .manual import get_rids_with_mancheck_balancing
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
|
|
@@ -138,6 +139,14 @@ def add_reaction(logger, model, rid, row, kr_ids, kegg_reaction_to_others, addty
|
|
|
138
139
|
return 1
|
|
139
140
|
|
|
140
141
|
|
|
142
|
+
# check if reversible and using ATP
|
|
143
|
+
if r.lower_bound < 0 and r.upper_bound > 0:
|
|
144
|
+
for m in r.metabolites:
|
|
145
|
+
if m.id.rsplit('_', 1)[0] == 'atp':
|
|
146
|
+
if rid not in get_rids_with_mancheck_balancing():
|
|
147
|
+
logger.warning(f"Reaction '{rid}' involves ATP and is reversible: are you sure?")
|
|
148
|
+
|
|
149
|
+
|
|
141
150
|
return 0
|
|
142
151
|
|
|
143
152
|
|