1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 '''
19 Contains the main function and the modelMaGe class that start the program.
20
21 Due to multiple changes in the development process, the code has
22 become ugly and not well structured. Until I have fixed and refactored it,
23 I will give a short description of the dependencies and the order of a program
24 call:
25 modelmage.py:
26 main():
27 - parses the input and hands the arguments -r and -k to initializer
28 - initializer returns a normalized format of the arguments in the form of a
29 comma separated string and the macros which are used to name the generated models
30 - instantiates the ModelMage class and sets a master model
31 - calls the ModelMage.generate() method with the suitable arguments
32 - instantiates the RemoveHandler and KineticsHandler class to retrieve the correct
33 logical combinations of species and reactions
34 - instantiates Generator and calls generator.generate(), which returns the
35 newly created sbml documents
36 - writes the documents to sbml and copasi files
37 - calls update_tasks_cps()
38 - instantiates CPShandler and imports the tasks from the master model
39 into the newly created cps files
40 - calls estimateParameters and discriminate
41
42 '''
43
44
45 import libsbml as lSBML
46 import modelmage.generator as GEN
47 import modelmage.preparator as PRE
48 import modelmage.discriminator as DIS
49 import modelmage.unCellDesign as unCellDesign
50 import sys
51 from optparse import OptionParser
52 import os, glob, shutil
53 import modelmage.model as model
54 from modelmage.mmexceptions import libSBMLError, InputError
55
56 ResultsCopasi = 'ResultCopasiFiles'
57 ResultsSBML = 'ResultSBMLFiles'
58 Version = "1.0beta"
59
61 '''
62 Deletes the standard result folder. ./results
63 '''
64 result = os.path.join(os.path.abspath(os.path.curdir),output)
65 if os.path.exists(result):
66 for root, dirs, files in os.walk(result, topdown=False):
67 for name in files:
68 os.remove(os.path.join(root, name))
69 for name in dirs:
70 os.rmdir(os.path.join(root, name))
71 os.rmdir(result)
72
73 print '\n'
74 print "The directory %s has been removed."%result
75 print '\n'
76 sys.exit(0)
77 else:
78 print "could not find directory %s for deletion. Exiting ..." % result
79 sys.exit(1)
80
82 '''
83 build up artifical ReverseMacros and ReverseKineticMacros from
84 the command line given with the option '-k'
85 '''
86
87
88 ReverseMacrosKeys = []
89 ReverseMacrosValues = []
90 if remove:
91 for r in remove:
92
93 ReverseMacrosKeys.append(str(r))
94 ReverseMacrosValues.append(r[0])
95 ReverseMacros[str(r)] = r[0]
96 else:
97 ReverseMacrosValues.append('')
98 ReverseMacros[''] = ''
99
100 index = 0
101 if kinetics:
102 for keyKinetic, keyValue in kinetics.items():
103
104 ReverseKineticMacrosKey = keyKinetic + ":" + keyValue[0] + ":" + ReverseMacrosValues[index]
105 ReverseKineticMacros[ReverseKineticMacrosKey] = ReverseMacrosValues[index]
106 if index < len(ReverseMacros) - 1:
107 index += 1
108
109
110 return (ReverseMacros, ReverseKineticMacros)
111
113 '''
114 Main function of the program that reads the command line and invokes the according
115 commands.
116 '''
117
118 parser = OptionParser("usage: python %prog [options] [file]", version="%prog 1.0beta")
119 parser.add_option("-c", "--cleanresult", action="store_true", dest="cleanresult", help="delete the result folder")
120 parser.add_option("-d", "--discriminate", action="store_true", help="parses result files of the parameter estimation and displays a model ranking according to the AIC.")
121 parser.add_option("-i", "--init", dest="init", help="path to an init file")
122 parser.add_option("-k", "--kinetics", dest="kinetics", help="specifies reactions with alternative kinetics")
123 parser.add_option("-o", "--output", dest="output", help="name of the result directory, 'result' by default.")
124 parser.add_option("-p", "--parameterestimation", action="store_true", dest="parameterestimation", help="uses CopasiSE to do a parameter estimation as specified in {mastermodel}.cps")
125 parser.add_option("-r", "--remove", dest="remove", help="SBML ids separated by logical operators")
126 parser.add_option("-s", "--show", action="store_true", help="print species and reaction in the .cps file to the shell")
127 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="Verbose mode shows more information about removed nodes.")
128 parser.set_defaults(output="result", show=False, verbose=False, cleanresult=False)
129 (options, args) = parser.parse_args()
130
131 print '\nmodelMaGe', Version, '\n'
132
133 if options.output:
134 options.output = options.output.strip()
135
136 useMacros = False
137 if options.cleanresult:
138 cleanResult(options.output)
139
140 if not args and not options.discriminate:
141 parser.print_help()
142 sys.exit(0)
143
144 if args and not os.path.exists(args[0]):
145 parser.error("No valid model file. Please give a valid sbml or cps file!")
146
147 if options.parameterestimation:
148 options.parameterEstimation = True
149
150 MM = ModelMaGe(output=options.output, verbose=options.verbose)
151
152
153 if options.discriminate:
154 MM.discriminate()
155 print 'OK'
156 sys.exit(0)
157
158 MM.setMasterModel(filename=args[0])
159 RKmacros = [{}, {}]
160
161
162 if options.init:
163 options.init = str(options.init).strip()
164 initializer = PRE.Initializer(init = options.init, remove = options.remove, kinetics = options.kinetics)
165 initRemove, macros, initKinetics, kineticMacros, useMacros= initializer.getArgs()
166
167
168 remove = ''
169 for r in initRemove:
170 remove += ', ' + r
171 for macro in macros.keys():
172 if macro in remove:
173 remove = remove.replace(macro, macros[macro])
174 options.remove = remove.strip(',').strip()
175
176
177 kinetics = ''
178 for k in initKinetics:
179 kinetics += ', ' + k
180
181 for macro in kineticMacros.keys():
182 if macro != '':
183 if macro in kinetics:
184 kinetics = kinetics.replace(macro, kineticMacros[macro])
185
186 options.kinetics = kinetics.strip(',').strip()
187
188 RKmacros = [macros, kineticMacros]
189
190 if options.verbose:
191 print 'Remove components:\n %s\n' % remove
192
193 MM.generate(remove=options.remove, kinetics=options.kinetics, macros=RKmacros, useMacros=useMacros, show=options.show)
194
195 if options.parameterestimation:
196 MM.estimateParameters()
197 MM.discriminate()
198
199 print 'OK'
200 sys.exit(0)
201
202
204 """
205 This class is the main interface for all the other classes. It is instantiated
206 by calling the module from the command line and by the web interface. The methods
207 provided by this class represent the main functionalities of the whole program.
208 """
209 __masterModel = None
210 verbose = True
211
212 - def __init__(self, model=None, filename=None, verbose=False, output=''):
213 """
214 The class can be initialized with various preset parameters. Depending on the
215 calling class. This way it can be used by the web interface as well as well as
216 from command line.
217
218 @type model: Model
219 @param model: a model that can be passed directly to the class and is set as
220 the master model
221
222 @type filename: str
223 @param filename: the filename of the file that was passed as a model.
224
225 @type verbose: bool
226 @param verbose: switch that decides if the output on the commandline is verbose
227
228 @type output: str
229 @param output: path and name of the output files
230 """
231
232 if model:
233 self.setMasterModel(model)
234 self.filename = filename
235 self.output = output
236 self.verbose = verbose
237 self.master = ''
238
239
240 self.buildReverseMacro = False
241
242
243
244 self.functionsIdName = {}
245
246
247 self.currentResultsCopasi = os.path.join(self.output , ResultsCopasi)
248 self.currentResultsSBML = os.path.join(self.output , ResultsSBML)
249
250
252 """
253 This function sets the master model of the ModelMage instance. The model
254 can be given as an SBML file of a Model object.
255
256 @type SBMLmodel: Model
257 @param SBMLmodel: a model object that can be set as the master model
258
259 @type filename: str
260 @param filename: filename of the master model
261 """
262
263 if filename:
264 inputPath = os.path.abspath(filename)
265 self.filename = inputPath
266
267
268
269 if SBMLmodel == None:
270
271
272 input = filename
273 inputCPS = ''
274 if input[ - 4:] == ".cps":
275 self.master = input
276 inputCPS = input
277 input = input[: - 4] + ".xml"
278 os.popen('CopasiSE %s -e %s' % (inputCPS, input))
279
280 self.getMasterSBMLFunctions(input)
281 try:
282
283
284 rootModel = model.Model()
285 inFile = unCellDesign.unCellDesign(input)
286 if inFile.isCellDesign():
287 print 'Removing CellDesigner Tags...'
288 rootModel.readSBML(inFile.convert(), self.verbose)
289 else:
290 rootModel.readSBML(input, self.verbose)
291
292 except libSBMLError, e:
293 print "'%s' does not contain valid SBML. The following error occured:\n%s" % (input, e.type)
294 exit(0)
295 except Exception, e:
296 print e
297 exit(1)
298 self.__masterModel = rootModel
299 else:
300 self.__masterModel = model.Model()
301 self.__masterModel.readSBML(SBMLmodel)
302
304 '''
305 This function produces the dictionary
306 {self.functionsIdName:{function_id : function_name}}
307
308 @type mastermodel: str
309 @param mastermodel: filename of the master model
310 '''
311
312 mastermodel = os.path.abspath(mastermodel)
313 if os.path.exists(mastermodel):
314 pass
315 else:
316 print "%s does not exist. Exiting ...\n" % mastermodel
317 sys.exit(1)
318
319 inFile = unCellDesign.unCellDesign(mastermodel)
320
321 reader = lSBML.SBMLReader()
322 doc = reader.readSBMLFromString(inFile.convert())
323 modelNew = doc.getModel()
324
325 functions = modelNew.getListOfFunctionDefinitions()
326 for function in functions:
327
328
329
330
331
332 self.functionsIdName[function.getId()] = function.getName()
333
334
336 """
337 Returns the master model of the object.
338 """
339 return self.__masterModel
340
342 """
343 Returns the filename of the object.
344 """
345 return self.filename
346
348 """
349 Returns a list of species.
350
351 @rtype: list
352 @return: list of species
353 """
354 return self.__masterModel.getModelElement('Species', ['Name', 'Id'])
355
357 """
358 Returns a list of reactions.
359
360 @rtype: list
361 @return: list of reactions
362 """
363 return self.__masterModel.getModelElement('Reactions', ['Name', 'Id'])
364
366 """
367 transfer the experimental data in the right place, so that the
368 process of parameter estimation can be properly executed
369
370 @return: list of experimental files
371 @rtype: list
372 """
373 for file in expDataFiles:
374 file = str(file)
375 filename = os.path.split(file)[1]
376 datafilePath = os.path.split(file)[0]
377 filePath = os.path.split(self.filename)[0]
378 datafilePath = os.path.join(filePath,datafilePath)
379 datafilePath = os.path.normpath(datafilePath)
380 dataFile = os.path.join(datafilePath,filename)
381 target = os.path.join(self.currentResultsCopasi, filename)
382 if os.path.exists(dataFile):
383 shutil.copyfile(dataFile, target)
384 else:
385 print "Could not find data file for parameter estimation. Exiting ..."
386 exit(1)
387
388 - def generate(self, remove=None, kinetics=None, macros=None, useMacros=False, show=False):
389 """
390 Generates the candidate models from the master model.
391
392 @type remove: str
393 @param remove: logical combination of elements that shall be removed
394
395 @type kinetics: str
396 @param kinetics: kinetics that shall be exchanged
397
398 @type macros: map
399 @param macros: contains the keys of candidate model name and values of remove combinations
400
401 @type useMacros: bool
402 @param useMacros: flag that tells if the user specified custom names for the models
403
404 @type show: bool
405 @param show: for -s --show option
406 """
407 gen = GEN.Generator(self.__masterModel, self.functionsIdName, verbose=self.verbose)
408
409
410 REVERSEMACROS = [{}, {}]
411 ReverseMacros = {}
412 ReverseKineticMacros = {}
413
414 removeHandler = \
415 PRE.removeHandler(self.__masterModel.getIdNameMap(), self.__masterModel.getNameIdMap())
416 remove, ReverseMacros, macros = removeHandler.getTrueCombinations(remove, macros, useMacros)
417
418
419 kineticHandler = PRE.kineticHandler()
420 if kinetics:
421 kinetics, ReverseKineticMacros = \
422 kineticHandler.getKinetics(kinetics, self.__masterModel.getSbmlDocument(), macros)
423
424
425 REVERSEMACROS = [ReverseMacros, ReverseKineticMacros]
426 if kinetics:
427 try:
428 partOfModel = kineticHandler.checkPartOfModel(self.__masterModel.getSbmlDocument().getModel(), kinetics.keys())
429 except InputError, e:
430 partOfModel = 0
431 print e.message
432 print "Continue with Mass Action Kinetics..."
433 if partOfModel:
434 pass
435 else:
436 print """\nGiven reaction ids \"%s\" are not part of the model.
437 These ids will not be used as kinetic alternatives.""" % kineticHandler.getNonModelIds()
438 for id in kineticHandler.getNonModelIds():
439 kinetics.pop(id)
440 if remove:
441 newKineticsDocuments, nameKineticNameMap, modelFunctionsIdName, idNameReactions = \
442 gen.generate(remove, kinetics, REVERSEMACROS, self.verbose)
443 gen.printReactionsSpecies(remove)
444 else:
445 newKineticsDocuments, nameKineticNameMap, modelFunctionsIdName, idNameReactions = \
446 gen.generate([], kinetics, REVERSEMACROS, self.verbose)
447 gen.printReactionsSpecies()
448
449
450 if show:
451 print "\n\n"
452 sys.exit(0)
453
454
455 self.graphHistory = gen.getGraphHistory()
456 idNameMap = gen.getModel().getIdNameMap()
457
458
459 if not os.path.exists(self.output):
460 try:
461 os.mkdir(self.output)
462 except:
463 print "Could not create output directory %s. Exiting ..." % self.output
464 sys.exit(1)
465
466 if not os.path.exists(self.currentResultsSBML):
467 try:
468 os.mkdir(self.currentResultsSBML)
469 except:
470 print "Could not create SBML output directory. Exiting ..."
471 sys.exit(1)
472
473
474 createdSBML = []
475 filelist = []
476
477
478
479 usedName = {}
480 print "\n",
481 for key, newDocument in newKineticsDocuments.items():
482 for i, doc in enumerate(newDocument):
483
484 name = ReverseMacros[key]
485 sbmlFileName = ReverseMacros[key]
486 usedName[name] = 1
487 if i > 0:
488 newsbmlFileName = "%sK%s" % (sbmlFileName, i)
489 modelFunctionsIdName[newsbmlFileName] = modelFunctionsIdName[sbmlFileName]
490 sbmlFileName = newsbmlFileName
491
492
493 if sbmlFileName.rfind(':') != -1:
494 sbmlFileName = sbmlFileName.replace(':','_')
495 filelist.append(sbmlFileName)
496 doc.writeSBML(os.path.join(self.currentResultsSBML, sbmlFileName))
497 createdSBML.append(os.path.join(self.currentResultsSBML, sbmlFileName) + ".xml")
498 print "\tgenerating %s.xml" % (os.path.split(sbmlFileName)[-1])
499
500 print '\nSBML files generated.\n'
501
502
503
504 self.idNameReactions = idNameReactions
505 self.filelist = filelist
506
507
508 if not os.path.exists(self.currentResultsCopasi):
509 try:
510 os.mkdir(self.currentResultsCopasi)
511 except:
512 print "Could not create Copasi output directory. Exiting ..."
513 sys.exit(1)
514
515 createdCPS = []
516 if os.path.exists(self.currentResultsSBML):
517
518 if filelist:
519 for f in filelist:
520 sfile = os.path.join(self.currentResultsSBML, f) + '.xml'
521 cfile = os.path.join(self.currentResultsCopasi, f) + '.cps'
522 if os.path.exists(sfile):
523 im = "-i %s" % sfile
524 exp = "-s %s" % cfile
525 print "\tgenerating %s"%(os.path.split(cfile)[-1])
526 os.popen("CopasiSE %s %s" % (im, exp))
527 createdCPS.append(cfile)
528 else:
529 print "No such file or directory: %s" % sfile
530 print '\nCopasi files generated.\n'
531 else:
532 print 'No SBML files in directory %s. \
533 CopasiFile cannot be generated. Exiting ...\n' % self.currentResultsSBML
534 sys.exit(1)
535 else:
536 print 'Directory %s does not exist. \
537 CopasiFile cannot be generated. Exiting ...\n' % self.currentResultsSBML
538 sys.exit(1)
539
540 if self.verbose:
541 print 'Specifying tasks...'
542
543 if self.filename[ - 4:] == ".xml":
544 inputCPS = None
545 self.Update_tasks_cps(modelFunctionsIdName, nameKineticNameMap, ReverseMacros, idNameMap, inputCPS)
546
547 if self.filename[ - 4:] == ".cps":
548 inputCPS = self.filename
549 rootFile = GEN.CpsHandler(inputCPS, self.graphHistory)
550 expDataFiles = rootFile.getExperimentalDataFiles()
551 self.transferData(expDataFiles)
552
553 self.Update_tasks_cps(modelFunctionsIdName, nameKineticNameMap, ReverseMacros, idNameMap, inputCPS)
554
555
556
557
558 - def Update_tasks_cps(self, modelFunctionsIdName, nameKineticNameMap, ReverseMacros, idNameMap, inputCPS):
559 '''
560 Updates tasks in cps files, so they can be used for parameter estimation.
561
562 @rtype: None
563 @return: None
564 '''
565 newFileList = []
566 for file in self.filelist:
567 newFileList.append(os.path.split(file)[-1])
568
569 self.newFileList = newFileList
570
571 print "Updating tasks for generated Copasi candidate models ... \n"
572 for file in [x for x in glob.glob(os.path.join(self.currentResultsCopasi,"*.cps")) \
573 if os.path.split(x)[ - 1][: - 4] in self.newFileList]:
574
575 fileSym = os.path.split(file)[-1][:-4]
576 readyForAssignFunc = False
577
578
579 if (fileSym in modelFunctionsIdName.keys()):
580
581 functionReactionMap = self.getFunction_Id_Name_XML(fileSym, self.idNameReactions)
582 functionsIdName = modelFunctionsIdName[fileSym]
583
584 newKineticNameMap = {}
585
586 for key, value in nameKineticNameMap.items():
587 newKey = key.split(':')[0]
588 symbol = key.split(':')[1]
589 if symbol == fileSym:
590 newKineticNameMap[newKey] = value
591
592 readyForAssignFunc = True
593
594
595 else:
596
597 functionReactionMap = self.getFunction_Id_Name_XML(fileSym, self.idNameReactions)
598 functionsIdName = {}
599
600 if len(modelFunctionsIdName) > 1:
601 key = keypattern[0]
602 print modelFunctionsIdName
603 functionsIdName = modelFunctionsIdName[key]
604
605 else:
606 for key, value in modelFunctionsIdName.items():
607 for ke, va in value.items():
608 functionsIdName[ke] = va
609
610 newKineticNameMap = nameKineticNameMap
611 readyForAssignFunc = True
612
613 cpsFile = GEN.CpsHandler(file, self.graphHistory, idNameMap)
614 if readyForAssignFunc:
615 cpsFile.assignFunctionNames(functionReactionMap, functionsIdName, fileSym, newKineticNameMap)
616 try:
617 if self.verbose:
618 print "\nFile: %s:" % file
619 if inputCPS:
620 removedFitItems = cpsFile.importTask(inputCPS, 'parameterFitting')
621 if self.verbose:
622 for item in removedFitItems:
623 print "Removed fititem reaction: %s parameter: %s" % item
624
625 except IOError, e:
626 print "\nError:\nFile '{rootModel}.cps' not found, you need to create that \
627 file (with CopasiUI) and specify estimation parameters in this file."
628 exit(1)
629 try:
630 written = cpsFile.writeCPS(file)
631 if not written:
632 exit(1)
633 except Exception, e:
634 print '\nError:\n%s' % e
635 exit(1)
636
637
639 """
640 This function constructs a dictionary with the keys of reaction_id
641 and the values of corresponding function_id
642
643 @type modelName: str
644 @param modelName: filename of the master model
645
646 @type idNameReactions: dictionary
647 @param idNameReactions: {reaction_id : reaction_name}
648
649 @return: a map of { functionReactionMap:{reaction_id : function_id} }used by def assignFunctionNames() in generator.py
650 """
651
652 modelPath = os.path.join(os.path.abspath('.'),self.currentResultsSBML,modelName) + '.xml'
653
654 if os.path.exists(modelPath):
655 pass
656 else:
657 print "%s does not exist. Exiting ...\n" % modelPath
658 sys.exit(1)
659
660
661 inFile = unCellDesign.unCellDesign(modelPath)
662 reader = lSBML.SBMLReader()
663 doc = reader.readSBMLFromString(inFile.convert())
664 modelNew = doc.getModel()
665
666
667
668
669
670
671
672 functionReactionMap = {}
673 reactions = modelNew.getListOfReactions()
674
675 for reaction in reactions:
676 kine = reaction.getKineticLaw().getFormula()
677
678 if '*' in kine:
679 withoutCom = kine.split('*')[1]
680 fun = withoutCom.split('(')[0]
681 else:
682 fun = kine.split('(')[0]
683 vId = idNameReactions[reaction.getId()]
684
685
686 if vId == '':
687 pseName = str(reaction.getId())
688 functionReactionMap[pseName] = fun.strip(" ")
689 else:
690 functionReactionMap[vId] = fun.strip(" ")
691
692 return functionReactionMap
693
694
695
697 '''
698 This function produces the dictionary
699 {self.functionsIdName:{function_id : function_name}}
700
701 @type candidateName: str
702 @param candidateName: filename of the model
703 '''
704
705 path = '/result/ResultSBMLFiles/'
706 candidateName = os.path.split(candidateName)[ - 1]
707 modelPath = os.path.abspath('.') + path + candidateName + '.xml'
708
709
710 if os.path.exists(modelPath):
711 pass
712 else:
713 print "%s does not exist. Exiting ...\n"%modelPath
714 sys.exit(1)
715
716 inFile = unCellDesign.unCellDesign(modelPath)
717
718 reader = lSBML.SBMLReader()
719 doc = reader.readSBMLFromString(inFile.convert())
720 modelNew = doc.getModel()
721
722 functions = modelNew.getListOfFunctionDefinitions()
723 for function in functions:
724 kineticLaw = lSBML.KineticLaw(function.getMath())
725
726
727
728
729
730
731
733 """
734 This method starts a parameter estimation for all created output files.
735 """
736 for file in [x for x in glob.glob('%s/*.cps' % self.currentResultsCopasi)\
737 if os.path.split(x)[ - 1][: - 4] in self.newFileList]:
738 print 'Estimating parameters for %s ...' % file ,
739 sys.stdout.flush()
740 try:
741 if self.verbose:
742 os.popen('CopasiSE %s -s %s --verbose' % (file, file))
743 else:
744
745 os.popen('CopasiSE %s -s %s' % (file, file))
746
747 print 'Done.\n\tResults written to %s' % file.split('.')[0] + '_est.txt'
748 except Exception, e:
749 print '\nError:\nCopasi was not able to estimate.\nMessage: %s' % e.message
750 print "\n"
751
753 """
754 This method returns a ranking of the estimated models that is produced by
755 L{Discriminator}
756 """
757 if os.path.exists(self.currentResultsCopasi):
758 d = DIS.Discriminator(self.currentResultsCopasi)
759 else:
760 print "Directory %s does not exist. Cannot parse parameter estimation results. Exiting ..." % os.path.abspath(self.currentResultsCopasi)
761 sys.exit(1)
762
763 print d.getRanking()
764
765
766 if __name__ == '__main__':
767 main()
768