00001 '''
00002 MSMS module
00003
00004 Author: Tobias Schmidt
00005
00006 This module is for calculating MSMS surfaces as well as surface areas
00007 (SESA, SASA) from OpenStructure using the external program MSMS.
00008
00009 How To Use This Module:
00010 1. Import it (e.g. as "from ost.bindings import msms")
00011 2. Use it (e.g. as "surfaces_list = msms.CalculateSurface(entity)"
00012 "(sesa,sasa) = msms.CalculateSurfaceArea(entity)")
00013
00014 Requirement:
00015 - MSMS installed
00016 '''
00017
00018 import tempfile
00019 import subprocess
00020 import os
00021
00022 from ost import io
00023 from ost import mol
00024 from ost import settings
00025 from ost import geom
00026
00027
00028 class MsmsProcessError(Exception):
00029 """
00030 Python 2.4 and older do not include the CalledProcessError exception. This
00031 class substitutes it.
00032 """
00033 def __init__(self, returncode,command):
00034 self.returncode = returncode
00035 self.command = command
00036 def __str__(self):
00037 return repr(self.returncode)
00038
00039
00040 def GetVersion(msms_exe=None, msms_env=None):
00041 """
00042 Get version of MSMS executable
00043 """
00044 msms_executable = _GetExecutable(msms_exe, msms_env)
00045 command = "%s" % (msms_executable)
00046 proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
00047 stdout_value, stderr_value = proc.communicate()
00048
00049 version = ""
00050 for l in stdout_value.splitlines():
00051 if l[0:4]=='MSMS':
00052 version = l.split(' ')[1]
00053 return version
00054 if version=="":
00055 LogWarning('Could not parse MSMS version string')
00056 return
00057
00058
00059 def _GetExecutable(msms_exe, msms_env):
00060 """
00061 Function to check if MSMS executable is present
00062
00063 :param msms_exe: Explicit path to msms executable
00064 :param msms_env: Environment variable pointing to msms executable
00065 :returns: Path to the executable
00066 :raises: :class:`~ost.FileNotFound` if executable is not found
00067 """
00068 return settings.Locate('msms', explicit_file_name=msms_exe,
00069 env_name=msms_env)
00070
00071
00072 def _SetupFiles(entity, selection):
00073 """
00074 Setup files for MSMS calculation in temporary directory
00075
00076 :param entity: The entity for which the surface is to be calculated
00077 :type entity: :class:`~ost.mol.EntityHandle` or :class:`~ost.mol.EntityHandle`
00078 :param selection: Calculate surface for subset of entity
00079 :type selection: :class:`str`
00080 :returns: tuple containing temporary directory and msms input file
00081 :raises: :class:`RuntimeError` if selection is not valid
00082 """
00083
00084 tmp_dir_name=tempfile.mkdtemp()
00085
00086
00087 entity_view=entity.Select(selection)
00088 if not entity_view.IsValid():
00089 raise RuntimeError, "Could not create view for selection (%s)"%(selection)
00090
00091
00092 tmp_file_name=os.path.join(tmp_dir_name,"entity")
00093 tmp_file_handle=open(tmp_file_name, 'w')
00094 for a in entity_view.GetAtomList():
00095 position=a.GetPos()
00096 tmp_file_handle.write('%8.3f %8.3f %8.3f %4.2f\n' % (position[0],
00097 position[1], position[2], a.radius))
00098 tmp_file_handle.close()
00099
00100 return (tmp_dir_name, tmp_file_name)
00101
00102
00103 def _ParseAreaFile(entity, selection, file, asa_prop, esa_prop):
00104 """
00105 Reads Area file (-af) and attach sasa and sesa per atom to an entitiy
00106
00107 :param entity: :class:`~ost.mol.EntityHandle` or :class:`~ost.mol.EntityView`
00108 for attaching sasa and sesa on atom level
00109 :param file: Filename of area file
00110 :param asa_prop: Name of the float property for SASA
00111 :param esa_prop: Name of the float property for SESA
00112 :raises: :class:`RuntimeError` if number of atoms in file != number of atoms in entity
00113 """
00114 view=entity.Select(selection)
00115 area_fh = open(file)
00116 area_lines = area_fh.readlines()
00117 area_fh.close()
00118
00119 area_lines = area_lines[1:]
00120 if view.GetAtomCount() != len(area_lines):
00121 raise RuntimeError, "Atom count (%d) unequeal to number of atoms in area file (%d)" % (view.GetAtomCount(), len(area_lines))
00122 for l in area_lines:
00123 atom_no, sesa, sasa = l.split()
00124 a = view.atoms[int(atom_no)]
00125 if asa_prop:
00126 a.SetFloatProp(asa_prop, float(sasa))
00127 if esa_prop:
00128 a.SetFloatProp(esa_prop, float(sesa))
00129
00130
00131
00132 def _CleanupFiles(dir_name):
00133 """
00134 Function which recursively deletes a directory and all the files contained
00135 in it. *Warning*: This method removes also non-empty directories without
00136 asking, so be careful!
00137 """
00138 import shutil
00139 shutil.rmtree(dir_name)
00140
00141 def _RunMSMS(command):
00142 """
00143 Run the MSMS surface calculation
00144
00145 This functions starts the external MSMS executable and returns the stdout of
00146 MSMS.
00147
00148 :param command: Command to execute
00149 :returns: stdout of MSMS
00150 :raises: :class:`CalledProcessError` for non-zero return value
00151 """
00152 proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
00153 stdout_value, stderr_value = proc.communicate()
00154
00155
00156 if proc.returncode!=0:
00157 print "WARNING: msms error\n", stdout_value
00158 raise MsmsProcessError(proc.returncode, command)
00159
00160 return stdout_value
00161
00162
00163
00164 def CalculateSurfaceArea(entity, density=1.0, radius=1.5, all_surf=False,
00165 no_hydrogens=False, no_hetatoms=False, no_waters=False,
00166 selection='',
00167 msms_exe=None, msms_env=None, keep_files=False,
00168 attach_asa=None, attach_esa=None):
00169 """
00170 Calculates analytical solvent excluded and solvent accessible surface
00171 area by using the external MSMS program.
00172
00173 This method calculates the molecular surface areas by invoking the external
00174 program MSMS. First, it is checked if the MSMS executable is present, then,
00175 the necessary files are prepared in a temporary directory and MSMS is
00176 executed. The last step is to remove the temporary directory.
00177
00178
00179 :param entity: OST entity to calculate surface
00180 :param density: Surface point density
00181 :param radius: Surface probe radius
00182 :param all_surf: Calculate surface area for all cavities (returns multiple
00183 surfaces areas as a list)
00184 :param no_hydrogens: Calculate surface only for hevy atoms
00185 :param selection: Calculate surface for subset of entity
00186 :param msms_exe: msms executable (full path to executable)
00187 :param msms_env: msms environment variable
00188 :param keep_files: Do not delete temporary files
00189 :param attach_asa: Attaches per atom SASA to specified FloatProp at atom level
00190 :param attach_esa: Attaches per atom SESA to specified FloatProp at atom level
00191 :returns: Tuple of lists for (SES, SAS)
00192 """
00193 import re
00194
00195
00196 msms_executable=_GetExecutable(msms_exe, msms_env)
00197
00198
00199 if no_hydrogens:
00200 if selection!='':
00201 selection+=" and "
00202 selection+="ele!=H"
00203
00204 if no_hetatoms:
00205 if selection!='':
00206 selection+=" and "
00207 selection+="ishetatm=False"
00208
00209 if no_waters:
00210 if selection!='':
00211 selection+=" and "
00212 selection+="rname!=HOH"
00213
00214
00215 (msms_data_dir, msms_data_file)=_SetupFiles(entity, selection)
00216
00217
00218 command="%s -if %s -of %s -density %s -probe_radius %s -surface ases" % \
00219 (msms_executable, msms_data_file, msms_data_file, density, radius)
00220 if all_surf:
00221 command+=" -all"
00222 if attach_asa != None or attach_esa != None:
00223 command+=" -af %s" % os.path.join(msms_data_dir, "asa_atom")
00224
00225 stdout_value=_RunMSMS(command)
00226
00227
00228 if attach_asa != None or attach_esa != None:
00229 _ParseAreaFile(entity, selection, os.path.join(msms_data_dir, "asa_atom.area"),
00230 attach_asa, attach_esa)
00231
00232
00233 msms_ases=[]
00234 msms_asas=[]
00235 data_paragraph=False
00236 for line in stdout_value.splitlines():
00237 if re.match('MSMS terminated normally', line):
00238 data_paragraph=False
00239 if data_paragraph:
00240 (ses_,sas_)=line.split()[5:7]
00241 msms_ases.append(float(ses_))
00242 msms_asas.append(float(sas_))
00243 if re.match(' Comp. probe_radius, reent, toric, contact SES SAS', line):
00244 data_paragraph=True
00245
00246
00247 if not keep_files:
00248 _CleanupFiles(msms_data_dir)
00249
00250 return (msms_ases, msms_asas)
00251
00252 def CalculateSurfaceVolume(entity, density=1.0, radius=1.5, all_surf=False,
00253 no_hydrogens=False, no_hetatoms=False, no_waters=False,
00254 selection='',
00255 msms_exe=None, msms_env=None, keep_files=False,
00256 attach_asa=None, attach_esa=None):
00257 """
00258 Calculates the volume of the solvent excluded surface by using the external MSMS program.
00259
00260 This method calculates the volume of the molecular surface by invoking the external
00261 program MSMS. First, it is checked if the MSMS executable is present, then,
00262 the necessary files are prepared in a temporary directory and MSMS is
00263 executed. The last step is to remove the temporary directory.
00264
00265
00266 :param entity: OST entity to calculate surface
00267 :param density: Surface point density
00268 :param radius: Surface probe radius
00269 :param all_surf: Calculate surface area for all cavities (returns multiple
00270 surfaces areas as a list)
00271 :param no_hydrogens: Calculate surface only for hevy atoms
00272 :param selection: Calculate surface for subset of entity
00273 :param msms_exe: msms executable (full path to executable)
00274 :param msms_env: msms environment variable
00275 :param keep_files: Do not delete temporary files
00276 :param attach_asa: Attaches per atom SASA to specified FloatProp at atom level
00277 :param attach_esa: Attaches per atom SESA to specified FloatProp at atom level
00278 :returns: Tuple of lists for (SES, SAS)
00279 """
00280 import re
00281
00282
00283 msms_executable=_GetExecutable(msms_exe, msms_env)
00284
00285
00286 if no_hydrogens:
00287 if selection!='':
00288 selection+=" and "
00289 selection+="ele!=H"
00290
00291 if no_hetatoms:
00292 if selection!='':
00293 selection+=" and "
00294 selection+="ishetatm=False"
00295
00296 if no_waters:
00297 if selection!='':
00298 selection+=" and "
00299 selection+="rname!=HOH"
00300
00301
00302 (msms_data_dir, msms_data_file)=_SetupFiles(entity, selection)
00303
00304
00305 command="%s -if %s -of %s -density %s -probe_radius %s " % \
00306 (msms_executable, msms_data_file, msms_data_file, density, radius)
00307 if all_surf:
00308 command+=" -all"
00309 if attach_asa != None or attach_esa != None:
00310 command+=" -af %s" % os.path.join(msms_data_dir, "asa_atom")
00311
00312 stdout_value=_RunMSMS(command)
00313
00314
00315 if attach_asa != None or attach_esa != None:
00316 _ParseAreaFile(entity, selection, os.path.join(msms_data_dir, "asa_atom.area"),
00317 attach_asa, attach_esa)
00318
00319
00320 ses_volume=0
00321 for line in stdout_value.splitlines():
00322 if re.match(' Total ses_volume:', line):
00323 ses_volume=float(line.split(':')[1])
00324
00325
00326 if not keep_files:
00327 _CleanupFiles(msms_data_dir)
00328
00329 return ses_volume
00330
00331
00332 def CalculateSurface(entity, density=1.0, radius=1.5, all_surf=False,
00333 no_hydrogens=False, no_hetatoms=False, no_waters=False,
00334 selection='',
00335 msms_exe=None, msms_env=None, keep_files=False,
00336 attach_asa=None, attach_esa=None):
00337
00338 """
00339 Calculates molecular surface by using the external MSMS program
00340
00341 This method calculates a molecular surface by invoking the external program
00342 MSMS. First, it is checked if the MSMS executable is present, then, the
00343 necessary files are prepared in a temporary directory and MSMS is executed.
00344 The last step is to remove the temporary directory.
00345
00346
00347 :param entity: Entity for which the surface is to be calculated
00348 :param density: Surface point density
00349 :param radius: Surface probe radius
00350 :param all_surf: Calculate surface for all cavities (returns multiple
00351 surfaces as a list)
00352 :param no_hydrogens: Calculate surface only for heavy atoms
00353 :param selection: Calculate surface for subset of entity
00354 :param msms_exe: msms executable (full path to executable)
00355 :param msms_env: msms environment variable
00356 :param keep_files: Do not delete temporary files
00357 :param attach_asa: Attaches per atom SASA to specified FloatProp at atom level
00358 :param attach_esa: Attaches per atom SESA to specified FloatProp at atom level
00359 :returns: list of :class:`~ost.mol.SurfaceHandle` objects
00360 """
00361 import os
00362 import re
00363
00364
00365 msms_executable=_GetExecutable(msms_exe, msms_env)
00366
00367
00368 if no_hydrogens:
00369 if selection!='':
00370 selection+=" and "
00371 selection+="ele!=H"
00372
00373 if no_hetatoms:
00374 if selection!='':
00375 selection+=" and "
00376 selection+="ishetatm=False"
00377
00378 if no_waters:
00379 if selection!='':
00380 selection+=" and "
00381 selection+="rname!=HOH"
00382
00383
00384 (msms_data_dir, msms_data_file)=_SetupFiles(entity, selection)
00385
00386
00387 command="%s -if %s -of %s -density %s -probe_radius %s" % (msms_executable,
00388 msms_data_file, msms_data_file, density, radius)
00389 if all_surf:
00390 command+=" -all"
00391 if attach_asa != None or attach_esa != None:
00392 command+=" -af %s" % os.path.join(msms_data_dir, "asa_atom")
00393
00394
00395 stdout_value=_RunMSMS(command)
00396
00397
00398 if attach_asa != None or attach_esa != None:
00399 _ParseAreaFile(entity, selection, os.path.join(msms_data_dir, "asa_atom.area"),
00400 attach_asa, attach_esa)
00401
00402
00403 num_surf=0
00404 for line in stdout_value.splitlines():
00405 if re.search('RS component [0-9]+ identified',line):
00406 num_surf=int(line.split()[2])
00407
00408
00409 entity_sel = entity.Select(selection)
00410 msms_surfaces=[]
00411 s = io.LoadSurface(msms_data_file, "msms")
00412 s.Attach(entity_sel, 3+radius)
00413 msms_surfaces.append(s)
00414 for n in range(1,num_surf+1):
00415 filename=msms_data_file+'_'+str(n)
00416 s = io.LoadSurface(filename, "msms")
00417 s.Attach(entity_sel, 3+radius)
00418 msms_surfaces.append(s)
00419
00420
00421 if not keep_files:
00422 _CleanupFiles(msms_data_dir)
00423
00424 return msms_surfaces
00425