Package Biskit :: Module SurfaceRacer
[hide private]
[frames] | no frames]

Source Code for Module Biskit.SurfaceRacer

  1  ## 
  2  ## Biskit, a toolkit for the manipulation of macromolecular structures 
  3  ## Copyright (C) 2004-2005 Raik Gruenberg & Johan Leckner 
  4  ## 
  5  ## This program is free software; you can redistribute it and/or 
  6  ## modify it under the terms of the GNU General Public License as 
  7  ## published by the Free Software Foundation; either version 2 of the 
  8  ## License, or any later version. 
  9  ## 
 10  ## This program is distributed in the hope that it will be useful, 
 11  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 13  ## General Public License for more details. 
 14  ## 
 15  ## You find a copy of the GNU General Public License in the file 
 16  ## license.txt along with this program; if not, write to the Free 
 17  ## Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 18  ## 
 19  ##  
 20  ## last $Author: graik $ 
 21  ## last $Date: 2006/10/04 10:50:35 $ 
 22  ## $Revision: 2.11 $ 
 23   
 24  """ 
 25  Calculates accessible, molecular surface areas and average curvature. 
 26  """ 
 27   
 28  import tempfile, re 
 29  import os.path 
 30  import string 
 31  import Numeric as N 
 32   
 33  from Biskit import Executor, TemplateError 
 34  import Biskit.settings as S 
 35  import Biskit.tools as T 
 36   
 37  import Biskit.surfaceRacerTools as SRT 
 38   
39 -class SurfaceRacer_Error( Exception ):
40 pass
41 42
43 -class SurfaceRacer( Executor ):
44 """ 45 Run SurfaceRacer 46 ================ 47 48 Runs surface_racer_3 with given PDBModel, and returns a dictionary with 49 accessible, molecular surface areas (MS and AS) and average curvature. 50 Hydrogens should not be present in the pdb-file during the calculation. 51 If a probe radius of 1.4 A and the Richards vdw radii set is used the 52 relative exposure is also calculated. 53 54 Options 55 ------- 56 probe - float, probe radii 57 vdw_set - int, Van del Waals radii set 58 1. Richards (1977) 59 2. Chothia (1976) 60 mode - int, calculation mode 61 1. Accessible surface area only 62 2. Accessible and molecular surface areas 63 3. Accessible, molecular surface areas and average 64 curvature 65 66 Example usage 67 ------------- 68 >>> x = SurfaceRacer( model, 1.4, verbose=1 ) 69 >>> result = x.run() 70 71 References 72 ---------- 73 - U{http://monte.biochem.wisc.edu/~tsodikov/surface.html} 74 - Tsodikov, O. V., Record, M. T. Jr. and Sergeev, Y. V. (2002). 75 A novel computer program for fast exact calculation of accessible 76 and molecular surface areas and average surface curvature. 77 J. Comput. Chem., 23, 600-609. 78 """ 79 80 inp = \ 81 """ 82 %(vdw_set)i 83 84 %(f_pdb_name)s 85 %(probe).2f 86 %(mode)i 87 88 89 """ 90
91 - def __init__( self, model, probe, vdw_set=1, mode=3, **kw ):
92 """ 93 SurfaceRacer creates three output files:: 94 result.txt - contains breakdown of surface areas and is writen 95 to the directory where the program resides. This 96 file is discharded here. 97 file *.txt - contains the accessible, molecular surface areas 98 and average curvature information paresd here. 99 The filename is that of the input pdb file but 100 with a .txt extension. 101 stdout - some general information about the calculation. 102 Redirected to /dev/null 103 104 @param model: model analyze 105 @type model: PDBModel 106 @param probe: probe radii, Angstrom 107 @type probe: float 108 @param vdw_set: Van del Waals radii set (default: 1):: 109 1 - Richards (1977) 110 2 - Chothia (1976) 111 @type vdw_set: 1|2 112 @param mode: calculation mode (default: 3):: 113 1- Accessible surface area only 114 2- Accessible and molecular surface areas 115 3- Accessible, molecular surface areas and 116 average curvature 117 @type mode: 1|2|3 118 119 @param kw: additional key=value parameters for Executor: 120 @type kw: key=value pairs 121 :: 122 debug - 0|1, keep all temporary files (default: 0) 123 verbose - 0|1, print progress messages to log (log != STDOUT) 124 node - str, host for calculation (None->local) NOT TESTED 125 (default: None) 126 nice - int, nice level (default: 0) 127 log - Biskit.LogFile, program log (None->STOUT) (default: None) 128 """ 129 130 Executor.__init__( self, 'surfaceracer', template=self.inp,\ 131 **kw ) 132 133 self.model = model.clone( deepcopy=1 ) 134 self.model = self.model.compress( self.model.maskHeavy() ) 135 136 ## will be filled in by self.prepare() after the temp folder is ready 137 self.f_pdb = None 138 self.f_pdb_name = None 139 self.f_out_name = None 140 141 ## parameters that can be changed 142 self.probe = probe 143 self.vdw_set = vdw_set 144 self.mode = mode 145 146 ## random data dictionaries 147 self.ranMS = SRT.ranMS 148 self.ranAS = SRT.ranAS 149 self.ranMS_Nter = SRT.ranMS_N 150 self.ranAS_Nter = SRT.ranAS_N 151 self.ranMS_Cter = SRT.ranMS_C 152 self.ranAS_Cter = SRT.ranAS_C 153 154 ## count failures 155 self.i_failed = 0
156 157
158 - def prepare( self ):
159 """ 160 Overrides Executor method. 161 - link surfaceracer binary to temporary folder 162 - put cleaned up pdb into same folder 163 - adapt working directory and output names to same folder 164 (changes self.cwd, f_pdb, f_pdb_names, f_out_name) 165 """ 166 self.cwd = self.__prepareFolder() 167 168 self.f_pdb = tempfile.mktemp( '_surfaceracer.pdb', self.cwd ) 169 self.f_pdb_name = os.path.split(self.f_pdb)[1] 170 171 ## The SurfRace output file has the same name as the input 172 ## pdb, but with a txt extension. 173 self.f_out_name = self.f_pdb[:-3]+'txt' 174 175 self.__prepareModel( self.model, self.f_pdb )
176 177
178 - def __prepareFolder( self ):
179 """ 180 The surfrace binary, radii files and the PDB have to be in the 181 current working directory. Otherwise we get a pretty silent 182 segmentation fault. 183 184 @return temp folder 185 @rtype str 186 """ 187 try: 188 folder = T.tempDir() 189 binlnk = os.path.join( folder, self.exe.name ) 190 191 if not os.path.exists( binlnk ): 192 os.symlink( self.exe.bin, binlnk ) 193 194 radii_txt = T.projectRoot() + '/external/surface_racer_3/radii.txt' 195 196 target = os.path.join(folder, 'radii.txt') 197 if not os.path.exists( target ): 198 os.symlink( radii_txt, target ) 199 200 self.exe.bin = binlnk 201 202 return folder + '/' 203 204 except OSError, error: 205 raise SurfaceRacer_Error, \ 206 'Error preparing temporary folder for SurfaceRacer\n'+\ 207 'Error: %r\n' % error +\ 208 'folder: %r\n' % folder +\ 209 'binary: %r\n' % self.exe.bin +\ 210 'binary link: %r' % binlnk
211 212 213
214 - def __prepareModel( self, model, f_pdb_out ):
215 """ 216 Prepare a model that SurfaceRacer likes. 217 - Surface curvature should only be calculated on heavy atoms. 218 - Delete Hydrogens, sequential numbering ... 219 220 @param model: model 221 @type model: PDBModel 222 @param f_pdb_out: name of pdb file to write 223 @type f_pdb_out: str 224 225 @raise SurfaceRacer_Error: if contains other than heavy atoms 226 """ 227 ## Hydrogens should not be present in calculation of curvature 228 model = model.compress( model.maskHeavy() ) 229 model.renumberResidues() 230 if sum(model.maskHeavy()) == model.lenAtoms(): 231 model.writePdb( f_pdb_out, wrap=1, left=0 ) 232 else: 233 raise SurfaceRacer_Error, \ 234 'The pdb file that was to be written as input for SurfaceRacer contains none heavy atoms.'
235 236
237 - def cleanup( self ):
238 """ 239 Tidy up the mess you created. 240 """ 241 Executor.cleanup( self ) 242 243 if not self.debug: 244 T.tryRemove( self.f_pdb ) 245 T.tryRemove( self.f_out_name )
246 247
248 - def parse_result( self, output ):
249 """ 250 Parse the SurfaceRacer output file which has the same nawe as the input 251 pdb, but with a txt extension. The output ends up un the same folder 252 as the input. In addition a file called result.txt is created in the 253 same directory as the binary. 254 255 @param output: full path to input pdb-file 256 @type output: str 257 258 @return: dictionary with curvature and surface data 259 @rtype: dict 260 """ 261 curv = [] ## average curvature 262 ms = [] ## molecular surface area 263 as = [] ## accessible surface area 264 265 try: 266 out_file = open( self.f_out_name ) 267 lines = out_file.readlines() 268 out_file.close() 269 except: 270 raise SurfaceRacer_Error,\ 271 'SurfaceRacer result file %s does not exist. You have probably encountered a very rare SurfaceRacer round off error that have caused the program to terminate. The simplest remedy to this problem is to increase the probe radii with a very small number, for example from %.3f to %.3f.'%(self.f_out_name, self.probe,self.probe+0.001 ) 272 273 if len(lines) == 0: 274 raise SurfaceRacer_Error,\ 275 'SurfaceRacer result file %s empty'%self.f_out_name 276 277 ## don't parse cavity information, find first occurance or 'CAVITY' 278 end = len(lines) 279 for i in range( len(lines)-1, 0, -1 ): 280 if lines[i][:6]=='CAVITY': 281 end = i 282 283 for i in range( end ): 284 curv += [ float( string.strip( lines[i][-11:-1] ) ) ] 285 ms += [ float( string.strip( lines[i][-17:-11] ) ) ] 286 as += [ float( string.strip( lines[i][-24:-17] ) ) ] 287 288 result = {'curvature':N.array(curv), 289 'MS':N.array(ms), 290 'AS':N.array(as), 291 'surfaceRacerInfo':{'probe_radius':self.probe, 292 'vdw_set':self.vdw_set} 293 } 294 295 ## check curvature profile integrity 296 result['curvature'] = \ 297 self.__checkProfileIntegrity( result['curvature'], 1.0, -1.0 ) 298 299 return result
300 301
302 - def __checkProfileIntegrity( self, profile, upperLimit=1.0, 303 lowerLimit=-1.0):
304 """ 305 In some cases SurfaceRacer generates incorrect curvature 306 values for some atoms. This function sets values outside 307 a given range to 0 308 309 @param profile: profile name 310 @type profile: str 311 @param upperLimit: upper limit for a valid value (default: 1.0) 312 @type upperLimit: float 313 @param lowerLimit: lower limit for a valid value (default: -1.0) 314 @type lowerLimit: float 315 316 @return: profile with inspected values 317 @rtype: [float] 318 """ 319 mask = N.greater( profile, upperLimit ) 320 mask += N.less( profile, lowerLimit ) 321 322 for i in N.nonzero(mask): 323 print 'WARNING! Profile value %.2f set to O\n'%profile[i] 324 profile[i] = 0 325 326 return profile
327 328
329 - def __relExposure( self, key='AS', clip=1 ):
330 """ 331 Calculate how exposed an atom is relative to the same 332 atom in a GLY-XXX-GLY tripeptide, an approximation of 333 the unfolded state. See L{Biskit.surfaceRacerTools.relExposure()} 334 335 @param key: caclulate relative Molecular Surface or 336 Acessible Surface (default: AS) 337 @type key: MS|AS 338 @param clip: clip MS ans AS values above 100% to 100% (default: 1) 339 @type clip: 1|0 340 341 @return: relAS - list of relative solvent accessible surfaces OR 342 relMS - list of relative molecular surface exposure 343 @rtype: [float] 344 """ 345 if not key=='MS' and not key=='AS': 346 raise SurfaceRacer_Error,\ 347 'Incorrect key for relative exposiure: %s '%key 348 349 self.result['rel'+key ] = SRT.relExposure( self.model, 350 self.result[key], 351 key )
352
353 - def isFailed( self ):
354 """ 355 Overrides Executor method 356 """ 357 if not os.path.exists(self.f_out_name): 358 T.flushPrint( '\nSurfaceRacer result file %s does not exist. You have probably encountered a very rare SurfaceRacer round off error that have caused the program to terminate. Will now try to recalculate the surface with a slightly increased surface probe radii: increasing radii from %.3f to %.3f.\n'%(self.f_out_name, self.probe,self.probe+0.001)) 359 return 1 360 361 return self.error is None
362 363
364 - def finish( self ):
365 """ 366 Overrides Executor method 367 """ 368 Executor.finish( self ) 369 370 self.result = self.parse_result( self.output ) 371 372 ## if probe radius other than 1.4 A the relative surface exposure 373 ## cannot be calculated, but allow this check to be a little flexible 374 ## if we ate forced to slightly increase the radii to excape round off 375 ## SurfaceRacer errors 376 if round(self.probe, 1) == 1.4 and self.vdw_set == 1: 377 self.__relExposure('MS') 378 self.__relExposure('AS') 379 else: 380 T.flushPrint("\nNo relative accessabilities calculated when using a prob radius other than 1.4 A or not using the Richards vdw radii set.")
381 382
383 - def failed( self ):
384 """ 385 Called if external program failed, Overrides Executor method. 386 387 In some very rare cases SurfaceRacer round off error cause the program 388 to terminate. The simplest remedy to this problem is to increase the 389 probe radii with a very small number and rerun the calculation. 390 """ 391 self.i_failed += 1 392 393 if self.i_failed < 2: 394 self.probe = self.probe + 0.001 395 self.run() 396 397 Executor.failed( self )
398 399 ############# 400 ## TESTING 401 ############# 402
403 -class Test:
404 """ 405 Test class 406 """ 407
408 - def run( self, local=0 ):
409 """ 410 run function test 411 412 @param local: transfer local variables to global and perform 413 other tasks only when run locally 414 @type local: 1|0 415 416 @return: sum of relMS, relAS and curvature for atoms 10 to 20 417 @rtype: float, float, float 418 """ 419 from Biskit import PDBModel 420 import Biskit.mathUtils as MA 421 422 if local: print 'Loading PDB...' 423 f = T.testRoot()+'/lig/1A19.pdb' 424 m = PDBModel(f) 425 m = m.compress( m.maskProtein() ) 426 427 if local: print 'Starting SurfaceRacer' 428 429 x = SurfaceRacer( m, 1.4, vdw_set=1, debug=0, verbose=0 ) 430 431 if local: 432 print 'Running (adding SurfaceRacer to local namespace as x)' 433 globals().update( locals() ) 434 435 r = x.run() 436 437 c= r['curvature'] 438 ms= r['MS'] 439 440 if local: 441 print "Curvature: weighted mean %.6f and standard deviation %.3f"\ 442 %(MA.wMean(c,ms), MA.wSD(c,ms)) 443 444 print 'Relative MS of atoms 10 to 20 atoms:', r['relMS'][10:20] 445 446 print 'Relative AS of atoms 10 to 20 atoms:', r['relAS'][10:20] 447 448 globals().update( locals() ) 449 450 451 return N.sum(r['relMS'][10:20]), N.sum(r['relAS'][10:20]), N.sum(r['curvature'][10:20])
452 453
454 - def expected_result( self ):
455 """ 456 Precalculated result to check for consistent performance. 457 458 @return: sum of relMS, relAS and curvature for atoms 10 to 20 459 @rtype: float, float, float 460 """ 461 462 return 570.47829086283639, 356.81939295543083, 0.80000000000000004
463 464 465 if __name__ == '__main__': 466 467 test = Test() 468 469 assert test.run( local=1 ) == test.expected_result() 470