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

Source Code for Module Biskit.Dock.ContactMaster

  1  ## Automatically adapted for numpy.oldnumeric Mar 26, 2007 by alter_code1.py 
  2   
  3  ## 
  4  ## Biskit, a toolkit for the manipulation of macromolecular structures 
  5  ## Copyright (C) 2004-2006 Raik Gruenberg & Johan Leckner 
  6  ## 
  7  ## This program is free software; you can redistribute it and/or 
  8  ## modify it under the terms of the GNU General Public License as 
  9  ## published by the Free Software Foundation; either version 2 of the 
 10  ## License, or any later version. 
 11  ## 
 12  ## This program is distributed in the hope that it will be useful, 
 13  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 15  ## General Public License for more details. 
 16  ## 
 17  ## You find a copy of the GNU General Public License in the file 
 18  ## license.txt along with this program; if not, write to the Free 
 19  ## Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 20  ## 
 21  ## 
 22  ## last $Author: graik $ 
 23  ## last $Date: 2007/03/26 18:40:42 $ 
 24  ## $Revision: 2.17 $ 
 25   
 26  """ 
 27  Distribute calculation of contact matrices for many complexes 
 28  over many nodes. 
 29  """ 
 30   
 31  from Biskit.PVM import TrackingJobMaster 
 32  from Biskit.ReduceCoordinates import ReduceCoordinates 
 33  from Biskit.hosts import cpus_all, nice_dic 
 34  import Biskit.tools as t 
 35  import Biskit.mathUtils as MU 
 36  import Biskit.settings as settings 
 37  from Biskit.PDBDope import PDBDope 
 38  from Biskit.PDBModel import PDBProfiles 
 39  from Biskit.Errors import BiskitError 
 40  from Biskit.LogFile import StdLog 
 41  from Biskit import EHandler 
 42   
 43  from Complex import Complex 
 44  from ComplexList import ComplexList 
 45  from ComplexEvolvingList import ComplexEvolvingList 
 46   
 47  import numpy.oldnumeric as N 
 48  import tempfile 
 49  import os.path 
 50   
 51  slave_path = t.projectRoot()+"/Biskit/Dock/ContactSlave.py" 
 52   
53 -class ContactMaster(TrackingJobMaster):
54 """ 55 Distribute calculations done on all complexes of a ComplexList. 56 """ 57
58 - def __init__(self, complexLst, chunks=5, hosts=cpus_all, refComplex=None, 59 updateOnly=0, niceness = nice_dic, force = [], 60 outFile = 'complexes_cont.cl', com_version=-1, 61 show_output = 0, add_hosts=0, verbose=1, log=StdLog() ):
62 """ 63 @param complexLst: input list 64 @type complexLst: ComplexList 65 @param chunks: number of complexes to hand over to each slave 66 at each call 67 @type chunks: int 68 @param hosts: list of hostnames 69 @type hosts: [str] 70 @param niceness: dictionary mapping hosts to nice values:: 71 {'hostname':nice_value, ..., 'default': nice_value} 72 nice_value: int or str 73 @type niceness: dict 74 @param outFile: file name for output list of complexes with 75 calculated contacts (default: 'complexes_cont.cl') 76 @type outFile: str 77 @param com_version: contact the given version of a ComplexEvolving, 78 only valid if input is ComplexEvolvingList 79 (default: -1) 80 @type com_version: int 81 @param show_output: show x-window for each slave or not (default: 0) 82 @type show_output: 1|0 83 @param add_hosts: add hosts to PVM (can take some time) (default: 0) 84 @type add_hosts: 1|0 85 @param force: force update of given info keys 86 @type force: [str] 87 @param verbose: print progress infos (default: 1) 88 @type verbose: 0|1 89 90 @raise BiskitError: if attempting to extract version from list 91 that is not of type ComplexEvolvingList. 92 """ 93 self.outFile = outFile 94 95 self.verbose = verbose 96 self.log = log 97 98 ## set name for error log file 99 self.ferror = os.path.dirname( 100 t.absfile(self.outFile)) + '/contacting_errors.log' 101 102 ## reference for fraction of native contacts 103 self.c_ref_res = None 104 self.c_ref_atom_4_5 = self.c_ref_atom_7_5 = self.c_ref_atom_10 = None 105 106 ## masks to apply before comparing to reference atom matrix (casting) 107 self.mask_rec, self.mask_lig = None, None 108 109 ## for interface rms calculation 110 self.ref_interface_model = None 111 self.mask_rec_interface = self.mask_lig_interface = None 112 113 ## update None dictonary entries 114 self.updateOnly = updateOnly 115 116 ## force update of given info keys 117 self.force = force 118 119 ## extract given version of each Complex from ComplexEvolvingList 120 self.complexLst_original = None 121 self.com_version = None 122 123 if isinstance( complexLst, ComplexEvolvingList ): 124 complexLst = self.__extractVersion( complexLst, com_version) 125 else: 126 if com_version != -1: raise BiskitError( 127 'Input list must be ComplexEvolvingList to extract version.') 128 129 ## make sure models have a surfaceMask 130 self.__addSurfaceMasks( complexLst ) 131 132 ## store class type so that same class can be returned 133 self.list_class = complexLst.__class__ 134 135 complexDic, self.remainLst = self.__complexDic( complexLst ) 136 137 if verbose: 138 self.log.write( '%i complexes total in list\n'%len(complexLst) ) 139 self.log.write( '\tof which %i need updating,\n'%len(complexDic) ) 140 self.log.write( '\tand %i are complete\n'%len(self.remainLst) ) 141 142 self.__check_models( complexLst ) 143 144 ## coarse models for reduced atom contacts 145 self.reduced_recs, self.reduced_ligs = {}, {} 146 self.reduced_refCom = None 147 self.c_ref_ratom_10 = None 148 149 if not self.force or 'fnarc_10' in self.force or \ 150 'c_ratom_10' in self.force: 151 152 self.__create_reduced_models( complexLst, refCom=refComplex ) 153 if refComplex: 154 self.__reduced_refContacts() 155 156 ## reference complex data 157 self.c_ref_res_4_5 = self.c_ref_atom_4_5 = None 158 self.c_ref_atom_10 = self.mask_rec = self.mask_lig = None 159 self.mask_interface = self.mask_interface_bb = None 160 self.ref_interface_model = self.ref_interface_model_bb = None 161 162 ## get reference residue and atom contact matrices 163 if refComplex: 164 self.__refMasks( refComplex, complexLst[0] ) 165 166 self.finished = 0 167 168 TrackingJobMaster.__init__( self, complexDic, chunks, 169 hosts, niceness, slave_path, show_output=show_output, 170 add_hosts=add_hosts, verbose=verbose ) 171 172 if verbose: print "JobMaster initialized."
173 174
175 - def __check_models( self, cl ):
176 """ 177 Perform some checks for models assocciated with ComplexList. 178 """ 179 ## stray models are not in the ComplexLists.models because the don't 180 ## have a identical pickled source 181 if cl.strayModels() != ({},{}): 182 print """Warning: some receptor or ligand models have in-memory 183 coordinates or profiles. This will slow down the PVM traffic 184 and might have other bad consequences... 185 Make sure that all models referenced in the ComplexList are 186 PDBModel.isChangedFromDisc() == (0,0)! 187 If that's the case but you still get this message, the model 188 registry of the list may be messed up. Re-create the ComplexList 189 with: 190 cl = ComplexList( cl.toList() ) 191 """
192 193 ## For some reason profiles are sometimes marked as "changed" and 194 ## their content saved in the model even though the identical profile 195 ## exists in the source model. This can blow up pvm traffic. 196 ## BUT:!! 197 ## If the profiles are used by the ContactSlave you should do the 198 ## opposite, so that the slave doesn't need to unpickle the source 199 ## model 200 ## self.log.write("synchronizing models with source ...") 201 ## for m in cl.recModels() + cl.ligModels(): 202 ## m.update() 203 ## m.slim() 204 ## self.log.write("done\n") 205 206
207 - def __extractVersion( self, cel, com_version=-1 ):
208 """ 209 Get a ComplexList by extracting a specified version (generation) of 210 a ComplexEvolvingList. 211 212 @param cel: ComplexEvolvingList 213 @type cel: ComplexEvolvingList 214 @param com_version: version of ComplexEvolvingList to get 215 (default: -1, last version) 216 @type com_version: int 217 218 @return: ComplexList 219 @rtype: ComplexList 220 """ 221 self.complexLst_original = cel 222 self.com_version = com_version 223 224 ## mark complexes to detect mix-ups later on 225 for i in range( len( cel ) ): 226 cel[i][ com_version ]['$temp$'] = i 227 228 return cel.toComplexList( version=com_version )
229 230
231 - def __addSurfaceMasks( self, cl ):
232 """ 233 Add surface area to rec and lig model if not already there. 234 235 @param cl: ComplexList 236 @type cl: ComplexList 237 """ 238 for m in cl.recModels(): 239 if not m.profile('surfMask', 0) != 0: 240 d = PDBDope( m ) 241 d.addSurfaceMask() 242 243 for m in cl.ligModels(): 244 if not m.profile('surfMask', 0) != 0: 245 d = PDBDope( m ) 246 d.addSurfaceMask()
247 248
249 - def __addSurface( self, m ):
250 """ 251 Add surface area to model. 252 253 @param m: model 254 @type m: PDBModel 255 """ 256 if m.profile('relAS', 0) is not 0: 257 return 258 259 d = PDBDope( m ) 260 d.addSurfaceRacer()
261 262
263 - def __refMasks( self, refCom, normalCom ):
264 """ 265 Create residue and atom contact masks for reference complex. 266 267 @param refCom: reference complex 268 @type refCom: Complex 269 @param normalCom: complex with different atom/residue content than 270 the reference complex to be used for determining 271 the sections that are identical in both complexes 272 @type normalCom: Complex 273 """ 274 if self.verbose: self.log.write( 'Analyzing reference complex ... ') 275 276 NC = normalCom; RC = refCom 277 278 ## indices to apply for casting, cast reference complex, del. Hydrogens 279 i_rec_ref, i_lig_ref, i_rec, i_lig = RC.compareAtoms( NC ) 280 h_rec = N.nonzero( RC.rec_model.maskH() ) 281 h_lig = N.nonzero( RC.lig_model.maskH() ) 282 i_rec_ref = [ i for i in i_rec_ref if i not in h_rec ] 283 i_lig_ref = [ i for i in i_lig_ref if i not in h_lig ] 284 285 RC = RC.take( i_rec_ref, i_lig_ref ) 286 287 ## convert casting indices for normalCom to mask 288 m_rec = N.zeros( len( NC.rec_model ), N.Int ) 289 N.put( m_rec, i_rec, 1 ) 290 m_lig = N.zeros( len( NC.lig_model ), N.Int ) 291 N.put( m_lig, i_lig, 1 ) 292 293 self.mask_rec = m_rec * NC.rec_model.maskHeavy() 294 self.mask_lig = m_lig * NC.lig_model.maskHeavy() 295 296 ## reference residue contacts 297 cont_4_5 = RC.resContacts( cutoff=4.5, refComplex = NC, cache_pw=1 ) 298 self.c_ref_res_4_5 = MU.packBinaryMatrix( cont_4_5 ) 299 300 cont_10 = RC.resContacts( cutoff=10., refComplex= NC, cache_pw=1 ) 301 302 ## reference atom contacts 303 m = RC.atomContacts( 4.5, cache=1, map_back=0) 304 self.c_ref_atom_4_5 = MU.packBinaryMatrix( m ) 305 306 m = RC.atomContacts( 10., cache=1, map_back=0) 307 self.c_ref_atom_10 = MU.packBinaryMatrix( m ) 308 309 ## reference structure of contacting residues all atoms, cutoff 4.5 310 x, y = self.__ref_interface( RC, NC, cont_4_5, 311 self.mask_rec, self.mask_lig ) 312 self.mask_interface = MU.packBinaryMatrix( x ) 313 self.ref_interface_model = y 314 315 ## reference structure of contacting residues backbone only, cutoff 10 316 x, y = self.__ref_interface( RC, NC, cont_10, 317 self.mask_rec, self.mask_lig, bb=1 ) 318 self.mask_interface_bb = MU.packBinaryMatrix( x ) 319 self.ref_interface_model_bb = y 320 321 ## 322 self.mask_rec = MU.packBinaryMatrix( self.mask_rec ) 323 self.mask_lig = MU.packBinaryMatrix( self.mask_lig ) 324 325 if self.verbose: self.log.write('done\n')
326 327
328 - def __ref_interface( self, RC, NC, res_contacts, mask_rec, mask_lig, bb=0):
329 """ 330 @param RC: reference complex, casted to normal complex 331 @type RC: Complex 332 @param NC: normal complex, not casted to RC 333 @type NC: Complex 334 @param res_contacts: residue contact matrix for defining 335 interface residues 336 @type res_contacts: matrix 337 @param mask_rec: atom mask to cast NC.rec() to RC.rec() 338 @type mask_rec: [1|0] 339 @param mask_lig: atom mask to cast NC.lig() to RC.lig() 340 @type mask_lig: [1|0] 341 @param bb: apply a backbone mask (default: 0) 342 @type bb: 1|0 343 344 @return: atom mask for getting interface from NC.model(), 345 reference interface 346 @rtype: [int], PDBModel 347 """ 348 try: 349 ## calculate atom mask that extracts all interface residues from NC 350 if_rec = NC.rec_model.res2atomMask( N.sum( res_contacts, 1) ) 351 if_rec = if_rec * NC.rec_model.maskHeavy() * mask_rec 352 if bb: 353 if_rec = if_rec * NC.rec_model.maskBB() 354 355 if_lig = NC.lig_model.res2atomMask( N.sum( res_contacts, 0) ) 356 if_lig = if_lig * NC.lig_model.maskHeavy() * mask_lig 357 if bb: 358 if_lig = if_lig * NC.lig_model.maskBB() 359 360 mask_interface = N.concatenate( (if_rec, if_lig) ) 361 362 ## extract all interface residues of ref complex RC into a PDBModel 363 if_rec = N.compress( mask_rec, if_rec ) ## reduce to mask for RC 364 if_lig = N.compress( mask_lig, if_lig ) 365 mask_interface_ref = N.concatenate( (if_rec, if_lig) ) 366 367 ref_interface_model = RC.model().compress(mask_interface_ref) 368 ref_interface_model.rProfiles = PDBProfiles() 369 ref_interface_model.aProfiles = PDBProfiles() 370 ref_interface_model.xyz = ref_interface_model.xyz.tolist() 371 372 return mask_interface, ref_interface_model 373 374 except ValueError, why: 375 EHandler.error("ValueError (rescontacts: " + str(res_contacts)+")")
376 377
378 - def __reduced_refContacts( self ):
379 """ 380 calculate contact mask of atom-reduced reference complex. 381 """ 382 if self.verbose: self.log.write("get reduced reference contacts...") 383 384 c = self.reduced_refCom.atomContacts( 10, cache=1, map_back=0) 385 self.c_ref_ratom_10 = MU.packBinaryMatrix( c ) 386 387 if self.verbose: self.log.write(' done\n')
388 389
390 - def __toBeUpdated( self, com ):
391 """ 392 Check if complex is to be updated. 393 394 @param com: Complex 395 @type com: Complex 396 397 @return: 1, if complex needs to be contacted 398 @rtype: 1|0 399 """ 400 if not self.updateOnly: 401 return 1 402 403 for ikey in com.keys(): 404 if com[ikey] == None: 405 406 return 1 407 408 return 0
409 410
411 - def __complexDic( self, cl ):
412 """ 413 Collect info about complexes in ComplexList that needs to be updated. 414 415 @param cl: ComplexList 416 @type cl: ComplexList 417 418 @return: dictionary mapping solution number to Complex, 419 remaining complexes as a ComplexList 420 @rtype: {int:Complex}, ComplexList 421 """ 422 update = {} 423 remain = ComplexList() 424 425 if self.verbose: self.log.write('setting up task list ') 426 for i in range( len( cl ) ): 427 428 c = cl[i] 429 430 if self.__toBeUpdated( c ): 431 update[i] = c 432 else: 433 remain += [c] 434 435 if self.verbose: self.log.write(' done\n') 436 437 return update, remain
438 439
440 - def __create_reduced_models( self, cl, refCom=None ):
441 """ 442 Create rec and lig models with pooled atoms (ca. every 3 atoms) but 443 keep only those united atoms where the singular atoms have an average 444 rel surf acc area > 30%. Do the same to the reference complex rec and 445 lig. The result are PDBModels with actually less atoms than residues 446 but whose contact matrix (and fnac) still resembles the one calculated 447 from all atoms. 448 449 @param cl: ComplexList 450 @type cl: ComplexList 451 @param refCom: reference complex 452 @type refCom: Complex 453 """ 454 if self.verbose: 455 self.log.write('preparing/saving coarse models of receptors&ligands...') 456 457 rec_models = cl.recModels() 458 lig_models = cl.ligModels() 459 460 ## cast with reference complex, if available 461 if refCom: 462 ref_rec = refCom.rec_model 463 ref_lig = refCom.lig_model 464 465 i_rec, i_ref = rec_models[0].compareAtoms( ref_rec ) 466 ref_rec = ref_rec.take( i_ref ) 467 468 i_lig, i_ref = lig_models[0].compareAtoms( ref_lig ) 469 ref_lig = ref_lig.take( i_ref ) 470 471 rec_models = [ m.take( i_rec ) for m in rec_models ] 472 lig_models = [ m.take( i_lig ) for m in lig_models ] 473 474 ## add surface profile so that reducer will create averaged profile 475 self.__addSurface( rec_models[0] ) 476 self.__addSurface( lig_models[0] ) 477 478 ## reduce all ligands and receptors 479 r_reducer = ReduceCoordinates( rec_models[0] ) 480 l_reducer = ReduceCoordinates( lig_models[0] ) 481 482 r,l = {}, {} 483 for m in rec_models: 484 r[ m.source ] = r_reducer.reduceToModel( m.getXyz() ) 485 486 for m in lig_models: 487 l[ m.source ] = l_reducer.reduceToModel( m.getXyz() ) 488 489 ## reduce refComplex 490 if refCom: 491 ref_rec = r_reducer.reduceToModel( ref_rec.getXyz() ) 492 ref_lig = l_reducer.reduceToModel( ref_lig.getXyz() ) 493 self.reduced_refCom = Complex( ref_rec, ref_lig, 494 refCom.ligandMatrix ) 495 496 ## keep only (average) surface atoms 497 ## play around with cutoff: lower- more atoms, better correl. to fnac 498 i_r_surf = N.nonzero( r[ rec_models[0].source ].\ 499 profile2mask( 'relAS', 30 ) ) 500 i_l_surf = N.nonzero( l[ lig_models[0].source ].\ 501 profile2mask( 'relAS', 30 ) ) 502 503 for m in r.values(): m.keep( i_r_surf ) 504 for m in l.values(): m.keep( i_l_surf ) 505 506 if refCom: 507 ref_rec.keep( i_r_surf ) 508 ref_lig.keep( i_l_surf ) 509 510 ## save changed models 511 self.reduced_recs, self.reduced_ligs = r,l 512 513 for m in l.values() + r.values(): 514 f = tempfile.mktemp( '_reduced.model', dir=settings.tempDirShared ) 515 m.saveAs(f) 516 517 if self.verbose: self.log.write(' done\n')
518 519
520 - def getInitParameters(self, slave_tid):
521 """ 522 hand over parameters to slave once. 523 524 @param slave_tid: slave task id 525 @type slave_tid: int 526 527 @return: dictionaty with slave parameters 528 @rtype: dict 529 """ 530 r = {'ferror': self.ferror, 'force' : self.force, 531 'c_ref_res_4_5' : self.c_ref_res_4_5, 532 'c_ref_atom_4_5': self.c_ref_atom_4_5, 533 'c_ref_atom_10' : self.c_ref_atom_10, 534 'ref_interface_model' : self.ref_interface_model, 535 'ref_interface_model_bb': self.ref_interface_model_bb, 536 'mask_rec' : self.mask_rec, 537 'mask_lig' : self.mask_lig, 538 'mask_interface' : self.mask_interface, 539 'mask_interface_bb': self.mask_interface_bb, 540 'reduced_recs' : self.reduced_recs, 541 'reduced_ligs' : self.reduced_ligs, 542 'c_ref_ratom_10' : self.c_ref_ratom_10, 543 'log':self.log.fname, 544 'verbose':self.verbose 545 } 546 547 return r
548 549
550 - def isFinished( self ):
551 return self.finished
552 553
554 - def cleanup( self ):
555 """ 556 Remove temporary files. 557 Overrides TrackingJobMaster method 558 """ 559 if self.verbose: print "Deleting temporary coarse rec/lig models..." 560 561 for m in self.reduced_recs.values() + self.reduced_ligs.values(): 562 if not t.tryRemove( str( m.source ) ): 563 print "Cannot remove ", str( m.source )
564 565
566 - def getResult( self, **arg ):
567 """ 568 Return result ComplexList, if it is available. 569 570 @return: resulting ComplexList 571 @rtype: ComplexList 572 """ 573 return self.complexLst
574 575
576 - def done(self ):
577 """ 578 Collect the last complexes and write result ComplexList to file. 579 580 @raise BiskitError: if Complex version mixup 581 """ 582 if self.verbose: print "Assembling new ComplexList...", 583 self.complexLst = self.list_class( self.result.values() ) 584 self.result.clear() 585 586 self.complexLst += self.remainLst 587 588 ## update complexes in ComplexEvolvingList 589 if self.complexLst_original is not None: 590 591 if self.verbose: 592 print "Copying values into version %i ..." % self.com_version, 593 594 for cev, c in zip( self.complexLst_original, self.complexLst ): 595 596 check_1 = c['$temp$'] 597 check_2 = cev[ self.com_version ]['$temp$'] 598 del c['$temp$'] 599 del cev[self.com_version]['$temp$'] 600 if not check_1 == check_2: 601 raise BiskitError('Complex version mixup: %i != %i' \ 602 % (check_1, check_2) ) 603 604 cev[ self.com_version ].info.update( c.info ) 605 606 self.complexLst = self.complexLst_original 607 608 if self.verbose: print "\nSaving result to %s..." % self.outFile 609 t.Dump( self.complexLst, self.outFile ) 610 611 self.finished = 1
612 613 614 ############# 615 ## TESTING 616 ############# 617 import Biskit.test as BT 618
619 -class Test(BT.BiskitTest):
620 """Test case 621 622 Requires PVM installed and running and at least one (non-master) node. 623 """ 624 625 TAGS = [ BT.PVM ] 626
627 - def prepare(self):
628 self.cl_out = tempfile.mktemp('_test.cl')
629 630
631 - def cleanUp(self):
632 t.tryRemove( self.master.outFile ) 633 t.tryRemove( self.master.ferror )
634 635
636 - def test_ContactMaster(self):
637 """Dock.ContactMaster test""" 638 niceness = {'default': 0} 639 self.hosts = cpus_all[:4] 640 641 lst = t.Load( t.testRoot() + "/dock/hex/complexes.cl") 642 lst = lst[:9] 643 644 refcom = t.Load( t.testRoot() + "/com/ref.complex") 645 646 self.master = ContactMaster( lst, chunks = 3, hosts = self.hosts, 647 niceness = niceness, 648 show_output = self.local, 649 verbose = self.local, 650 refComplex = refcom, 651 outFile = self.cl_out ) 652 653 self.assert_( len(self.hosts) > 0, 654 'master needs at least one pvm node for calculations' ) 655 656 if len(self.hosts) > 0: 657 ## wait for calculation to finish, then load contacted list 658 self.cl_cont = self.master.calculateResult() 659 660 if self.local: 661 print 'Any error are written to: %s'%master.ferror 662 ## plot atom and residue contacts vs. rmsd 663 self.p = self.cl_cont.plot( 'rms', 'fnac_10','fnarc_10' ) 664 self.p.show() 665 666 self.assertAlmostEqual(N.sum(self.cl_cont.valuesOf('fnac_10')), 667 0.50811038550663579, 5 )
668 669 670 if __name__ == '__main__': 671 672 BT.localTest() 673