1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
40 pass
41
42
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
137 self.f_pdb = None
138 self.f_pdb_name = None
139 self.f_out_name = None
140
141
142 self.probe = probe
143 self.vdw_set = vdw_set
144 self.mode = mode
145
146
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
155 self.i_failed = 0
156
157
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
172
173 self.f_out_name = self.f_pdb[:-3]+'txt'
174
175 self.__prepareModel( self.model, self.f_pdb )
176
177
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
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
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
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
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 = []
262 ms = []
263 as = []
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
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
296 result['curvature'] = \
297 self.__checkProfileIntegrity( result['curvature'], 1.0, -1.0 )
298
299 return result
300
301
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
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
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
365 """
366 Overrides Executor method
367 """
368 Executor.finish( self )
369
370 self.result = self.parse_result( self.output )
371
372
373
374
375
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
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
401
402
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
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