@@ -148,7 +148,8 @@ def _f_reaction_rev(sid, prefix="R_"):
148
148
# -----------------------------------------------------------------------------
149
149
# Read SBML
150
150
# -----------------------------------------------------------------------------
151
- def read_sbml_model (filename , number = float , f_replace = F_REPLACE , ** kwargs ):
151
+ def read_sbml_model (filename , number = float , f_replace = F_REPLACE ,
152
+ set_missing_bounds = False , ** kwargs ):
152
153
"""Reads SBML model from given filename.
153
154
154
155
If the given filename ends with the suffix ''.gz'' (for example,
@@ -184,6 +185,8 @@ def read_sbml_model(filename, number=float, f_replace=F_REPLACE, **kwargs):
184
185
By default the following id changes are performed on import:
185
186
clip G_ from genes, clip M_ from species, clip R_ from reactions
186
187
If no replacements should be performed, set f_replace={}, None
188
+ set_missing_bounds : boolean flag to set missing bounds
189
+ Missing bounds are set to default bounds in configuration.
187
190
188
191
Returns
189
192
-------
@@ -198,8 +201,11 @@ def read_sbml_model(filename, number=float, f_replace=F_REPLACE, **kwargs):
198
201
"""
199
202
try :
200
203
doc = _get_doc_from_filename (filename )
201
- return _sbml_to_model (doc , number = number ,
202
- f_replace = f_replace , ** kwargs )
204
+ return _sbml_to_model (doc ,
205
+ number = number ,
206
+ f_replace = f_replace ,
207
+ set_missing_bounds = set_missing_bounds ,
208
+ ** kwargs )
203
209
except IOError as e :
204
210
raise e
205
211
@@ -255,7 +261,7 @@ def _get_doc_from_filename(filename):
255
261
return doc
256
262
257
263
258
- def _sbml_to_model (doc , number = float , f_replace = None , skip_annotations = False ,
264
+ def _sbml_to_model (doc , number = float , f_replace = None , set_missing_bounds = False ,
259
265
** kwargs ):
260
266
"""Creates cobra model from SBMLDocument.
261
267
@@ -265,6 +271,7 @@ def _sbml_to_model(doc, number=float, f_replace=None, skip_annotations=False,
265
271
number: data type of stoichiometry: {float, int}
266
272
In which data type should the stoichiometry be parsed.
267
273
f_replace : dict of replacement functions for id replacement
274
+ set_missing_bounds : flag to set missing bounds
268
275
269
276
Returns
270
277
-------
@@ -529,28 +536,32 @@ def process_association(ass):
529
536
p_lb = klaw .getParameter ("LOWER_BOUND" ) # noqa: E501 type: libsbml.LocalParameter
530
537
if p_lb :
531
538
cobra_reaction .lower_bound = p_lb .getValue ()
532
- else :
533
- raise CobraSBMLError ("Missing flux bounds on reaction: %s" ,
534
- reaction )
535
539
p_ub = klaw .getParameter ("UPPER_BOUND" ) # noqa: E501 type: libsbml.LocalParameter
536
540
if p_ub :
537
541
cobra_reaction .upper_bound = p_ub .getValue ()
538
- else :
539
- raise CobraSBMLError ("Missing flux bounds on reaction %s" ,
540
- reaction )
541
542
542
- LOGGER .warning ("Encoding LOWER_BOUND and UPPER_BOUND in "
543
- "KineticLaw is discouraged, "
544
- "use fbc:fluxBounds instead: %s" , reaction )
543
+ if p_ub is not None or p_lb is not None :
544
+ LOGGER .warning ("Encoding LOWER_BOUND and UPPER_BOUND in "
545
+ "KineticLaw is discouraged, "
546
+ "use fbc:fluxBounds instead: %s" , reaction )
545
547
546
548
if p_lb is None :
547
549
LOGGER .error ("Missing lower flux bound for reaction: "
548
550
"%s" , reaction )
549
551
missing_bounds = True
552
+ if set_missing_bounds :
553
+ cobra_reaction .lower_bound = config .lower_bound
554
+ LOGGER .warning ("Set lower flux bound to default for reaction: "
555
+ "%s" , reaction )
556
+
550
557
if p_ub is None :
551
558
LOGGER .error ("Missing upper flux bound for reaction: "
552
559
"%s" , reaction )
553
560
missing_bounds = True
561
+ if set_missing_bounds :
562
+ cobra_reaction .upper_bound = config .upper_bound
563
+ LOGGER .warning ("Set upper flux bound to default for reaction: "
564
+ "%s" , reaction )
554
565
555
566
# add reaction
556
567
reactions .append (cobra_reaction )
@@ -613,8 +624,6 @@ def process_association(ass):
613
624
cobra_reaction .gene_reaction_rule = gpr
614
625
615
626
cobra_model .add_reactions (reactions )
616
- if missing_bounds :
617
- raise CobraSBMLError ("Missing flux bounds on reactions." )
618
627
619
628
# Objective
620
629
obj_direction = "max"
@@ -647,18 +656,15 @@ def process_association(ass):
647
656
)
648
657
except ValueError as e :
649
658
LOGGER .warning (str (e ))
650
- set_objective (cobra_model , coefficients )
651
- cobra_model .solver .objective .direction = obj_direction
652
659
else :
653
660
# some legacy models encode objective coefficients in kinetic laws
654
- for cobra_reaction in model .getListOfReactions (): # noqa: E501 type: libsbml.Reaction
655
- if cobra_reaction .isSetKineticLaw ():
656
-
661
+ for reaction in model .getListOfReactions (): # noqa: E501 type: libsbml.Reaction
662
+ if reaction .isSetKineticLaw ():
657
663
klaw = reaction .getKineticLaw () # type: libsbml.KineticLaw
658
664
p_oc = klaw .getParameter (
659
665
"OBJECTIVE_COEFFICIENT" ) # noqa: E501 type: libsbml.LocalParameter
660
666
if p_oc :
661
- rid = cobra_reaction .getId ()
667
+ rid = reaction .getId ()
662
668
if f_replace and F_REACTION in f_replace :
663
669
rid = f_replace [F_REACTION ](rid )
664
670
try :
@@ -762,6 +768,11 @@ def process_association(ass):
762
768
763
769
cobra_model .add_groups (groups )
764
770
771
+ # run the complete parsing to get all warnings and errors before
772
+ # raising errors
773
+ if missing_bounds and not set_missing_bounds :
774
+ raise CobraSBMLError ("Missing flux bounds on reactions." )
775
+
765
776
return cobra_model
766
777
767
778
@@ -847,6 +858,8 @@ def _model_to_sbml(cobra_model, f_replace=None, units=True):
847
858
if cobra_model .name is not None :
848
859
model .setName (cobra_model .name )
849
860
861
+ _sbase_annotations (model , cobra_model .annotation )
862
+
850
863
# Meta information (ModelHistory)
851
864
if hasattr (cobra_model , "_sbml" ):
852
865
meta = cobra_model ._sbml
@@ -1011,16 +1024,19 @@ def _model_to_sbml(cobra_model, f_replace=None, units=True):
1011
1024
# GPR
1012
1025
gpr = cobra_reaction .gene_reaction_rule
1013
1026
if gpr is not None and len (gpr ) > 0 :
1014
- gpa = r_fbc . createGeneProductAssociation () # noqa: E501 type: libsbml.GeneProductAssociation
1015
- # replace ids
1027
+
1028
+ # replace ids in string
1016
1029
if f_replace and F_GENE_REV in f_replace :
1030
+ gpr = gpr .replace ('(' , '( ' )
1031
+ gpr = gpr .replace (')' , ' )' )
1017
1032
tokens = gpr .split (' ' )
1018
1033
for k in range (len (tokens )):
1019
- if tokens [k ] not in ['and' , 'or' , '(' , ')' ]:
1034
+ if tokens [k ] not in [' ' , ' and' , 'or' , '(' , ')' ]:
1020
1035
tokens [k ] = f_replace [F_GENE_REV ](tokens [k ])
1021
- gpr = " " .join (tokens )
1036
+ gpr_new = " " .join (tokens )
1022
1037
1023
- gpa .setAssociation (gpr )
1038
+ gpa = r_fbc .createGeneProductAssociation () # noqa: E501 type: libsbml.GeneProductAssociation
1039
+ gpa .setAssociation (gpr_new )
1024
1040
1025
1041
# objective coefficients
1026
1042
if reaction_coefficients .get (cobra_reaction , 0 ) != 0 :
@@ -1238,8 +1254,8 @@ def _sbase_notes_dict(sbase, notes):
1238
1254
1239
1255
In the current stage the new annotation format is not completely supported yet.
1240
1256
"""
1241
- URL_IDENTIFIERS_PATTERN = r"^http[s]{0,1}://identifiers.org/(.+)/(.+)"
1242
- URL_IDENTIFIERS_PREFIX = r "https://identifiers.org"
1257
+ URL_IDENTIFIERS_PATTERN = re . compile ( r"^http[s]{0,1}://identifiers.org/(.+? )/(.+)" ) # noqa: E501
1258
+ URL_IDENTIFIERS_PREFIX = "https://identifiers.org"
1243
1259
QUALIFIER_TYPES = {
1244
1260
"is" : libsbml .BQB_IS ,
1245
1261
"hasPart" : libsbml .BQB_HAS_PART ,
@@ -1278,8 +1294,8 @@ def _parse_annotations(sbase):
1278
1294
-------
1279
1295
dict (annotation dictionary)
1280
1296
1281
- FIXME: annotation format must be updated
1282
- ( https://github.com/opencobra/cobrapy/issues/684)
1297
+ FIXME: annotation format must be updated (this is a big collection of
1298
+ fixes) - see: https://github.com/opencobra/cobrapy/issues/684)
1283
1299
"""
1284
1300
annotation = {}
1285
1301
@@ -1295,20 +1311,22 @@ def _parse_annotations(sbase):
1295
1311
1296
1312
for cvterm in cvterms : # type: libsbml.CVTerm
1297
1313
for k in range (cvterm .getNumResources ()):
1298
- uri = cvterm .getResourceURI (k )
1299
-
1300
1314
# FIXME: read and store the qualifier
1301
- tokens = uri .split ('/' )
1302
- if len (tokens ) != 5 or not tokens [2 ] == "identifiers.org" :
1315
+
1316
+ uri = cvterm .getResourceURI (k )
1317
+ match = URL_IDENTIFIERS_PATTERN .match (uri )
1318
+ if not match :
1303
1319
LOGGER .warning ("%s does not conform to "
1304
1320
"http(s)://identifiers.org/collection/id" , uri )
1305
1321
continue
1306
1322
1307
- provider , identifier = tokens [ 3 ], tokens [ 4 ]
1323
+ provider , identifier = match . group ( 1 ), match . group ( 2 )
1308
1324
if provider in annotation :
1309
1325
if isinstance (annotation [provider ], string_types ):
1310
1326
annotation [provider ] = [annotation [provider ]]
1311
- annotation [provider ].append (identifier )
1327
+ # FIXME: use a list
1328
+ if identifier not in annotation [provider ]:
1329
+ annotation [provider ].append (identifier )
1312
1330
else :
1313
1331
# FIXME: always in list
1314
1332
annotation [provider ] = identifier
@@ -1335,7 +1353,11 @@ def _sbase_annotations(sbase, annotation):
1335
1353
1336
1354
# standardize annotations
1337
1355
annotation_data = deepcopy (annotation )
1356
+
1338
1357
for key , value in annotation_data .items ():
1358
+ # handling of non-string annotations (e.g. integers)
1359
+ if isinstance (value , (float , int )):
1360
+ value = str (value )
1339
1361
if isinstance (value , string_types ):
1340
1362
annotation_data [key ] = [("is" , value )]
1341
1363
0 commit comments