Package Biskit :: Package Dock :: Module Docker
[hide private]
[frames] | no frames]

Source Code for Module Biskit.Dock.Docker

  1  ## Automatically adapted for numpy.oldnumeric Mar 26, 2007 by alter_code1.py 
  2   
  3  #!/usr/bin/env python 
  4  ## 
  5  ## Biskit, a toolkit for the manipulation of macromolecular structures 
  6  ## Copyright (C) 2004-2006 Raik Gruenberg & Johan Leckner 
  7  ## 
  8  ## This program is free software; you can redistribute it and/or 
  9  ## modify it under the terms of the GNU General Public License as 
 10  ## published by the Free Software Foundation; either version 2 of the 
 11  ## License, or any later version. 
 12  ## 
 13  ## This program is distributed in the hope that it will be useful, 
 14  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 16  ## General Public License for more details. 
 17  ## 
 18  ## You find a copy of the GNU General Public License in the file 
 19  ## license.txt along with this program; if not, write to the Free 
 20  ## Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 21  ## 
 22  ## 
 23  ## last $Author: graik $ 
 24  ## last $Date: 2007/04/06 10:16:09 $ 
 25  ## $Revision: 2.13 $ 
 26   
 27  """ 
 28  Prepare and run a HEX docking. 
 29  """ 
 30   
 31  from Biskit import PDBModel, PDBDope, molUtils 
 32  import Biskit.tools as t 
 33   
 34  from HexParser import HexParser 
 35  from ComplexList import ComplexList 
 36  import hexTools  
 37  import settings 
 38   
 39  import re 
 40  import os.path 
 41  from time import localtime, sleep 
 42  from threading import Thread, RLock, Condition 
 43  import numpy.oldnumeric as N 
 44   
45 -class DockerError( Exception ):
46 pass
47
48 -class Docker:
49 """ 50 Prepare and run a hex docking for one or several models 51 of a receptor and ligand pair. Collect docking results 52 into a growing ComplexList. 53 54 The docking runs are started in seperate Threads. External objects 55 can register a method with call_when_done() that is called whenever 56 a docking has finished. The waitForLastHex() method can be called 57 to wait until all currently running dockings are finished. 58 59 The models in the 2 given dictionaries might get different chainIds but 60 Docker doesn't save them. 61 62 @todo: implement that as JobMaster / JobSlave instead, so that also the 63 Hex-file parsing is distributed. 64 """ 65
66 - def __init__( self, recDic, ligDic, recPdb=None, ligPdb=None, 67 comPdb=None, out='hex_%s', soln=512, 68 recChainId=None, ligChainId=None, macDock=None, 69 bin=settings.hex_bin, verbose=1 ):
70 """ 71 @param recDic: receptor dictionary 72 @type recDic: dict 73 @param ligDic: ligand dictionary 74 @type ligDic: dict 75 @param recPdb: hex-formatted PDB with rec models 76 @type recPdb: str 77 @param ligPdb: hex-formatted PDB with lig models 78 @type ligPdb: str 79 @param comPdb: hex-formatted PDB with com models 80 @type comPdb: str 81 @param soln: number of solutions to keep from HEX (default 512) 82 @type soln: int 83 @param recChainId: force chain IDs only for HEX 84 @type recChainId: [str] 85 @param ligChainId: force chain IDs only for HEX 86 @type ligChainId: [str] 87 @param out: out folder name, will be formatted against date 88 @type out: str 89 @param macDock: force macro docking ON or OFF (default: size decides) 90 @type macDock: 1|0 91 @param bin: path to HEX binary 92 @type bin: str 93 @param verbose: verbosity level (default: 1) 94 @type verbose: 1|0 95 """ 96 self.lock = RLock() 97 self.lockMsg = Condition( self.lock ) 98 99 t.ensure( macDock, int, [None] ) 100 t.ensure( recDic, dict, ) 101 t.ensure( ligDic, dict, ) 102 103 self.verbose = verbose 104 105 self.recDic = recDic 106 self.ligDic = ligDic 107 108 ## force chain IDs 109 self.recChainId = recChainId 110 self.ligChainId = ligChainId 111 112 ## create folder if necessary 113 self.out = self.prepareOutFolder( out ) 114 115 self.comPdb = None 116 if comPdb: 117 self.comPdb = t.absfile( comPdb ) 118 119 ## remember which HEX PDB was created for which model 120 self.recHexPdbs = {} 121 self.ligHexPdbs = {} 122 123 ## remember which macrofile uses which models and which out file 124 self.runDic = {} 125 126 if recPdb: 127 ## HEX PDB provided externally 128 for k in self.recDic: 129 self.recHexPdbs[ k ] = t.absfile( recPdb ) 130 else: 131 ## HEX PDB has to be created 132 self.recHexPdbs = self.prepareHexPdbs( self.recDic, 133 self.recChainId, 'rec') 134 135 if ligPdb: 136 for k in self.ligDic: 137 self.ligHexPdbs[ k ] = t.absfile( recPdb ) 138 else: 139 self.ligHexPdbs = self.prepareHexPdbs( self.ligDic, 140 self.ligChainId, 'lig') 141 142 self.bin = bin 143 144 self.call_when_done = None 145 self.call_when_failed=None 146 147 ## remember whether macro dock was used 148 self.macroDock = macDock 149 150 self.running = [] 151 self.hexDone = 0 152 153 self.result = ComplexList() 154 155 self.soln = soln
156 157
158 - def prepareOutFolder( self, fout ):
159 """ 160 Setup an output folder 161 162 @param fout: outfile name 163 @type fout: str 164 165 @return: out path 166 @rtype: str 167 """ 168 ## date stamp in folder name 169 try: 170 datestr = "%02i%02i" % (localtime()[1], localtime()[2]) 171 fout = fout % ( datestr ) 172 if not os.path.exists( t.absfile( fout ) ): 173 os.mkdir( t.absfile( fout ) ) 174 175 ## user provided folder name -> formatting fails 176 except: 177 if not os.path.exists( t.absfile( fout ) ): 178 os.mkdir( t.absfile( fout ) ) 179 180 ## create folders for rec / lig HEX pdbs 181 if not os.path.exists( t.absfile( fout ) + '/rec' ): 182 os.mkdir( t.absfile( fout ) + '/rec' ) 183 184 if not os.path.exists( t.absfile( fout ) + '/lig' ): 185 os.mkdir( t.absfile( fout ) + '/lig' ) 186 187 return t.absfile( fout )
188 189
190 - def __setChainID( self, m, ids ):
191 """ 192 set chaiID for Hex pdb files 193 194 @param m: model 195 @type m: PDBModel 196 @param ids: chain id, len(ids) == m.lenChains 197 @type ids: [str] 198 199 @return: m is changed directly 200 @rtype: PDBModel 201 """ 202 if ids: 203 ids = t.toList( ids ) 204 cMap = m.chainMap() 205 206 for chain in range( m.lenChains() ): 207 idx = N.nonzero( cMap == chain ) 208 for i in idx: 209 m.aProfiles['chain_id'][i] = ids[chain]
210 211
212 - def prepareHexPdbs( self, modelDic, idList, subDir ):
213 """ 214 create HEX-formatted PDB for each model 215 216 @param modelDic: model dictionary to be prepared for HEX 217 @type modelDic: dict 218 @param idList: force chain IDs for HEX pdb files 219 @type idList: [str] 220 @param subDir: 'rec' or 'lig' 221 @type subDir: str 222 223 @return: model dictionary, { modelNumber : file_name_created, .. } 224 @rtype: dict 225 """ 226 result = {} 227 for k in modelDic: 228 229 m = modelDic[k].clone() 230 231 fhex = self.out + '/%s/%s_%03d_hex.pdb' % (subDir, m.pdbCode, k) 232 if os.path.exists( fhex ) and self.verbose: 233 print "using old ", os.path.split( fhex )[1] 234 235 else: 236 m.remove( m.maskH() ) 237 m = molUtils.sortAtomsOfModel(m) 238 self.__setChainID( m, idList ) 239 240 hexTools.createHexPdb_single( m, fhex ) 241 if self.verbose: print "created ", fhex 242 243 result[ k ] = fhex 244 245 return result
246 247
248 - def createHexInp( self, nRec, nLig ):
249 """ 250 Create a HEX macro file for docking a rec lig pair. 251 252 @param nRec: model number rec in model dictionaries 253 @type nRec: int 254 @param nLig: model number lig in model dictionaries 255 @type nLig: int 256 257 @return: macro file name, future out file name 258 @rtype: (str, str) 259 260 @raise DockerError: if macro dock option is different from previous 261 call 262 """ 263 ## fetch PDB file names for given model numbers 264 recPdb, ligPdb = self.recHexPdbs[nRec], self.ligHexPdbs[nLig] 265 266 ## fetch the according PDBModels 267 rec, lig = self.recDic[ nRec ], self.ligDic[ nLig ] 268 269 fout_base = self.out+'/%s_%i-%s_%i' % (rec.pdbCode, nRec, 270 lig.pdbCode, nLig) 271 272 if os.path.exists( t.absfile( fout_base + '_hex.mac' ) ) and self.verbose: 273 fmac = t.absfile( fout_base + '_hex.mac' ) 274 fout = t.absfile( fout_base + '_hex.out' ) 275 macro = self.macroDock 276 print "Dock setup: using old ", os.path.split( fmac )[1] 277 else: 278 silent=1 279 if self.verbose: silent=0 280 281 fmac, fout, macro = hexTools.createHexInp(recPdb,rec, ligPdb,lig, 282 self.comPdb, outFile=fout_base, 283 macDock=self.macroDock, sol=self.soln, 284 silent=silent) 285 286 if self.macroDock == None: 287 self.macroDock = macro 288 else: 289 if self.macroDock != macro: 290 raise DockerError('MacroDock setting changed to %i: %i : %i' %\ 291 ( macro, nRec, nLig) ) 292 293 ## remember which models and which out file are used 294 self.runDic[ fmac ] = ( nRec, nLig, fout ) 295 296 return fmac, fout
297 298
299 - def runHex( self, finp, log=0, ncpu=2, nice=0, host=os.uname()[1] ):
300 """ 301 @param finp: hex macro file 302 @type finp: str 303 @param log: write log file 304 @type log: 1|0 305 @param ncpu: number of cpus to use 306 @type ncpu: int 307 @param nice: nice value for HEX job (default: 0) 308 @type nice: int 309 @param host: host to run jon on 310 @type host: str 311 """ 312 flog = finp + '.log' 313 314 cmd = "%s -ncpu %i -nice %i -noexec < %s > %s" 315 cmd = cmd % (self.bin, ncpu, nice, finp, flog ) 316 317 cmd = "ssh %s %s" % (host, cmd) 318 319 runner = RunThread( cmd, self, finp=finp, host=host, 320 log=log, verbose=self.verbose ) 321 322 self.lock.acquire() 323 324 self.lastCmd = cmd 325 self.running += [runner] 326 327 runner.start() 328 329 self.lock.release()
330 331
332 - def countDifferentModels( self, lst ):
333 """ 334 """ 335 mrec = [] 336 mlig = [] 337 for c in lst: 338 if c.rec_model not in mrec: 339 mrec += [ c.rec_model ] 340 if c.lig_model not in mlig: 341 mlig += [ c.lig_model ] 342 343 return len( mrec ), len( mlig )
344 345
346 - def doneHex( self, runner ):
347 """ 348 Do something after hex has finished. Notify all threads waiting 349 on self.lockMsg 350 """ 351 self.lock.acquire() 352 353 ## append ComplexList 354 self.result += runner.result 355 356 if self.call_when_done: 357 self.call_when_done( runner ) 358 359 self.running.remove( runner ) 360 361 self.hexDone += 1 362 363 self.lockMsg.notifyAll() 364 self.lock.release()
365 366
367 - def failedHex( self, runner ):
368 """ 369 notify of failed hex run. 370 """ 371 self.lock.acquire() 372 373 self.running.remove( runner ) 374 375 if self.call_when_failed: 376 self.call_when_failed( runner ) 377 378 self.lockMsg.notifyAll() 379 self.lock.release()
380 381
382 - def waitForLastHex( self ):
383 """ 384 Return after the last hex thread has finished. 385 """ 386 self.lock.acquire() 387 388 while len( self.running ) > 0: 389 self.lockMsg.wait() 390 391 self.lock.release() 392 return
393 394
395 - def set_call_when_done( self, funct ):
396 """ 397 funct( RunThread ) 398 """ 399 self.lock.acquire() 400 self.call_when_done = funct 401 self.lock.release()
402
403 - def set_call_when_failed( self, funct ):
404 """ 405 funct( RunThread ) 406 """ 407 self.lock.acquire() 408 self.call_when_failed = funct 409 self.lock.release()
410 411
412 -class RunThread( Thread ):
413 """ 414 @todo: implement that as JobSlave instead 415 """ 416
417 - def __init__( self, cmd, owner, finp=None, log=0, 418 host=None, verbose=1, **kw ):
419 """ 420 @param cmd: command to execute 421 @type cmd: str 422 @param owner: Docker job to run to run 423 @type owner: Docker instance 424 @param finp: hex macro file (default: None) 425 @type finp: str 426 @param log: write log file (default: 0) 427 @type log: 1|0 428 @param host: host to run jon on (default: None, localhost) 429 @type host: str 430 @param kw: optional key/value pairs 431 @type kw: key=value 432 """ 433 Thread.__init__( self, name=(host or 'local'), **kw ) 434 self.cmd = cmd 435 self.owner = owner 436 self.finp = finp 437 self.log = log 438 self.host = host or os.uname()[1] 439 self.nRec, self.nLig, self.fout = self.owner.runDic[ self.finp ] 440 self.output = None 441 self.status = 0 442 self.result = None 443 self.verbose = verbose
444 445
446 - def run( self ):
447 """ 448 Run HEX job. 449 450 @raise DockerError: if HEX exists with error 451 """ 452 try: 453 if not os.path.exists( self.fout ): 454 455 if self.verbose: print "Executing: ", self.host, ' ', t.stripFilename(self.finp) 456 457 cmd_lst = self.cmd.split() 458 459 self.status = os.spawnvp(os.P_WAIT, cmd_lst[0], cmd_lst ) 460 461 waited = 0 462 while waited < 25 and not os.path.exists( self.fout ): 463 sleep( 5 ) 464 waited += 5 465 466 if self.status != 0: 467 raise DockerError, 'Hex returned exit status %i' % self.status 468 469 ## replace model numbers in HEX output file 470 self.__hackHexOut( self.nRec, self.nLig, self.fout ) 471 472 parser = HexParser(self.fout, self.owner.recDic, 473 self.owner.ligDic) 474 475 ## generate ComplexList from hex output 476 self.result = parser.parseHex() 477 478 self.done() 479 480 except: 481 self.failed()
482 483
484 - def done( self ):
485 """ 486 HEX job done. 487 """ 488 self.owner.doneHex( self )
489 490
491 - def failed( self ):
492 """ 493 If HEX job fails 494 """ 495 print "FAILED: ", self.host, ' ', t.stripFilename(self.finp) 496 print "\tJob details:" 497 print "\tCommand: ", self.cmd 498 print "\tinput: ", self.finp 499 print "\tHex log: ", self.log 500 print "\tHex out: ", self.fout 501 print 502 print "\t", t.lastError() 503 504 self.owner.failedHex( self )
505 506
507 - def __hackHexOut( self, nRec, nLig, fout ):
508 """ 509 Replace current model numbers in HEX out file by given nRec and nLig. 510 Only to be used with one-against-one dockings because all 511 ReceptorModel and LigandModel entries are replaced by nRec, nLig. 512 513 @param nRec: receptor number 514 @type nRec: int 515 @param nLig: ligand number 516 @type nLig: int 517 @param fout: name of existing out file, is overwritten 518 @type fout: str 519 """ 520 old_out = open( fout, 'r' ) 521 contents = old_out.readlines() 522 523 oldRec = 'ReceptorModel: \d+$' 524 oldLig = 'LigandModel: \d+$' 525 correctRec = 'ReceptorModel: %i' % nRec 526 correctLig = 'LigandModel: %i' % nLig 527 contents = [re.sub(oldRec, correctRec, i ) for i in contents] 528 contents = [re.sub(oldLig, correctLig, i ) for i in contents] 529 old_out.close() 530 531 new_out = open( fout, 'w') 532 new_out.writelines(contents ) 533 new_out.close()
534 535 536 537 ############# 538 ## TESTING 539 ############# 540 import Biskit.test as BT 541
542 -class TestCore(BT.BiskitTest):
543 """Base class for short and long Test case""" 544
545 - def prepare(self):
546 import tempfile 547 self.out_folder = tempfile.mktemp('_test_docker_%s')
548
549 - def cleanUp(self):
550 t.tryRemove( self.out_folder, tree=1 )
551
552 - def dry_or_wet_run( self, run=True ):
553 """ """ 554 import time, os 555 import os.path 556 557 ligDic = t.Load( t.testRoot() + '/multidock/lig/1A19_models.dic' ) 558 559 ## in the test root the directories "multidock/rec" and 560 ## "multidock/com" are symbolic links from "dock/rec" and 561 ## "dock/com". If this is a fleshly checked out project 562 ## they will not exist, so we will have to create them. 563 rec_dir = t.testRoot() + '/multidock/rec' 564 com_dir = t.testRoot() + '/multidock/com' 565 566 if not os.path.exists( rec_dir ): 567 ## remove old invalid links 568 if os.path.lexists( rec_dir ): 569 os.unlink( rec_dir ) 570 os.symlink( t.testRoot() + '/dock/rec', rec_dir ) 571 572 if not os.path.exists( com_dir ): 573 ## remove old invalid links 574 if os.path.lexists( com_dir ): 575 os.unlink( com_dir ) 576 os.symlink( t.testRoot() + '/dock/com', com_dir ) 577 578 recDic = t.Load( t.testRoot() + '/multidock/rec/1A2P_model.dic' ) 579 580 self.d = Docker( recDic, ligDic, out=self.out_folder, 581 verbose=self.local ) 582 583 # dock rec 1 vs. lig 2 on localhost 584 fmac1, fout = self.d.createHexInp( 1, 2 ) 585 if run: 586 self.d.runHex( fmac1, log=1, ncpu=2 ) 587 588 self.d.waitForLastHex() 589 590 ## dock receptor 1 vs. ligand one on remote host 591 # fmac2, fout2= d.createHexInp( 1, 1 ) 592 # d.runHex( fmac2, log=0, ncpu=2, host='remote_host_name' ) 593 594 if self.local: print "ALL jobs submitted."
595 596
597 -class TestLong(TestCore):
598 """Test case running a complete hex docking (ca. 30 min/CPU)""" 599 600 TAGS = [ BT.EXE, BT.LONG ] 601
602 - def test_DockerLong(self):
603 """Dock.Docker real test (20-30min/CPU)""" 604 self.dry_or_wet_run( run=True )
605
606 -class TestShort(TestCore):
607 608 TAGS = [ BT.EXE ] 609
610 - def test_DockerShort(self):
611 """Dock.Docker dry run test""" 612 self.dry_or_wet_run( run=False )
613 614 if __name__ == '__main__': 615 616 BT.localTest() 617