OpenStructure
msms.py
Go to the documentation of this file.
1 '''
2 MSMS module
3 
4 Author: Tobias Schmidt
5 
6 This module is for calculating MSMS surfaces as well as surface areas
7 (SESA, SASA) from OpenStructure using the external program MSMS.
8 
9 How To Use This Module:
10  1. Import it (e.g. as "from ost.bindings import msms")
11  2. Use it (e.g. as "surfaces_list = msms.CalculateSurface(entity)"
12  "(sesa,sasa) = msms.CalculateSurfaceArea(entity)")
13 
14 Requirement:
15  - MSMS installed
16 '''
17 
18 import tempfile
19 import subprocess
20 import os
21 
22 from ost import io
23 from ost import mol
24 from ost import settings
25 from ost import geom
26 
27 
28 
29 ## \brief custom exception that substitutes CalledProcessError
30 #
31 # Python 2.4 does not include the CalledProcessError exception.
32 # This one substitutes it
33 class MsmsProcessError(Exception):
34  def __init__(self, returncode,command):
35  self.returncode = returncode
36  self.command = command
37  def __str__(self):
38  return repr(self.returncode)
39 
40 
41 def GetVersion(msms_exe=None, msms_env=None):
42  msms_executable = _GetExecutable(msms_exe, msms_env)
43  command = "%s" % (msms_executable)
44  proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
45  stdout_value, stderr_value = proc.communicate()
46 
47  version = ""
48  for l in stdout_value.splitlines():
49  if l[0:4]=='MSMS':
50  version = l.split(' ')[1]
51  return version
52  if version=="":
53  LogWarning('Could not parse MSMS version string')
54  return
55 
56 ## \brief Method to check if MSMS executable is present
57 #
58 # \param msms_exe Explicit path to msms executable
59 # \param msms_env Environment variable pointing to msms executable
60 # \return Path to the executable
61 # \exception FileNotFound if executable is not found
62 def _GetExecutable(msms_exe, msms_env):
63  return settings.Locate('msms', explicit_file_name=msms_exe,
64  env_name=msms_env)
65 
66 ## \brief Setup files for MSMS calculation in temporary directory
67 #
68 # \param entity EntityHandle or EntityView to calculate surface
69 # \param selection Calculate surface for subset of entity
70 # \return Touple containing temporary directory and msms input file
71 # \exception RuntimeError if selection is not valid
72 def _SetupFiles(entity, selection):
73  # create temporary directory
74  tmp_dir_name=tempfile.mkdtemp()
75 
76  # select only heavy atoms if no_hydrogens is true
77  entity_view=entity.Select(selection)
78  if not entity_view.IsValid():
79  raise RuntimeError, "Could not create view for selection (%s)"%(selection)
80 
81  # write entity to tmp file
82  tmp_file_name=os.path.join(tmp_dir_name,"entity")
83  tmp_file_handle=open(tmp_file_name, 'w')
84  for a in entity_view.GetAtomList():
85  position=a.GetPos()
86  tmp_file_handle.write('%8.3f %8.3f %8.3f %4.2f\n' % (position[0],
87  position[1], position[2], a.radius))
88  tmp_file_handle.close()
89 
90  return (tmp_dir_name, tmp_file_name)
91 
92 ## \brief Reads Area file (-af) and attach sasa and sesa per atom to an entitiy
93 #
94 # \param entity EntityHandle or EntityView for attaching sasa and sesa on atom level
95 # \param file Filename of area file
96 # \param asa_prop Name of the float property for SASA
97 # \param esa_prop Name of the float property for SESA
98 # \exception RuntimeError if number of atoms in file != number of atoms in entity
99 def _ParseAreaFile(entity,file, asa_prop, esa_prop):
100  area_fh = open(file)
101  area_lines = area_fh.readlines()
102  area_fh.close()
103  # shift first line
104  area_lines = area_lines[1:]
105  if entity.GetAtomCount() != len(area_lines):
106  raise RuntimeError, "Atom count (%d) unequeal to number of atoms in area file (%d)" % (entity.GetAtomCount(), len(area_lines))
107  for l in area_lines:
108  atom_no, sesa, sasa = l.split()
109  a = entity.atoms[int(atom_no)]
110  if asa_prop:
111  a.SetFloatProp(asa_prop, float(sasa))
112  if esa_prop:
113  a.SetFloatProp(esa_prop, float(sesa))
114 
115 
116 ## \brief Method which recursively deletes a directory
117 #
118 # \warning This method removes also non-empty directories without asking, so
119 # be careful!
120 def __CleanupFiles(dir_name):
121  import shutil
122  shutil.rmtree(dir_name)
123 
124 ## \brief Method to run the MSMS surface calculation
125 #
126 # This method starts the external MSMS executable and returns the stdout of MSMS
127 #
128 # \param command Command to execute
129 # \return stdout of MSMS
130 # \exception CalledProcessError for non-zero return value
131 def _RunMSMS(command):
132  proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
133  stdout_value, stderr_value = proc.communicate()
134 
135  #check for successful completion of msms
136  if proc.returncode!=0:
137  print "WARNING: msms error\n", stdout_value
138  raise MsmsProcessError(proc.returncode, command)
139 
140  return stdout_value
141 
142 
143 ## \brief Calculates analytical solvent excluded and solvent accessible surface
144 # area by using the external MSMS program
145 #
146 # This method calculates the molecular surface areas by invoking the external
147 # program MSMS. First, it is checked if the MSMS executable is present, then,
148 # the necessary files are prepared in a temporary directory and MSMS is
149 # executed. The last step is to remove the temporary directory.
150 #
151 #
152 # \param entity OST entity to calculate surface
153 # \param density Surface point density
154 # \param radius Surface probe radius
155 # \param all_surf Calculate surface area for all cavities (returns multiple
156 # surfaces areas as a list)
157 # \param no_hydrogens Calculate surface only for hevy atoms
158 # \param selection Calculate surface for subset of entity
159 # \param msms_exe msms executable (full path to executable)
160 # \param msms_env msms environment variable
161 # \param keep_files Do not delete temporary files
162 # \param attach_asa Attaches per atom SASA to specified FloatProp at atom level
163 # \param attach_esa Attaches per atom SESA to specified FloatProp at atom level
164 # \return Touplet of lists for (SES, SAS)
165 def CalculateSurfaceArea(entity, density=1.0, radius=1.5, all_surf=False,
166  no_hydrogens=False, no_hetatoms=False, no_waters=False,
167  selection='',
168  msms_exe=None, msms_env=None, keep_files=False,
169  attach_asa=None, attach_esa=None):
170  import re
171 
172  # check if msms executable is specified
173  msms_executable=_GetExecutable(msms_exe, msms_env)
174 
175  # parse selection
176  if no_hydrogens:
177  if selection!='':
178  selection+=" and "
179  selection+="ele!=H"
180 
181  if no_hetatoms:
182  if selection!='':
183  selection+=" and "
184  selection+="ishetatm=False"
185 
186  if no_waters:
187  if selection!='':
188  selection+=" and "
189  selection+="rname!=HOH"
190 
191  # setup files for msms
192  (msms_data_dir, msms_data_file)=_SetupFiles(entity, selection)
193 
194  # set command line
195  command="%s -if %s -of %s -density %s -probe_radius %s -surface ases" % \
196  (msms_executable, msms_data_file, msms_data_file, density, radius)
197  if all_surf:
198  command+=" -all"
199  if attach_asa != None:
200  command+=" -af %s" % os.path.join(msms_data_dir, "asa_atom")
201  # run msms
202  stdout_value=_RunMSMS(command)
203 
204  # add sesa and asa to entity if attach_asa is specified
205  if attach_asa != None:
206  _ParseAreaFile(entity, os.path.join(msms_data_dir, "asa_atom.area"),
207  attach_asa, attach_esa)
208 
209  # parse MSMS output
210  msms_ases=[]
211  msms_asas=[]
212  data_paragraph=False
213  for line in stdout_value.splitlines():
214  if re.match('MSMS terminated normally', line):
215  data_paragraph=False
216  if data_paragraph:
217  (ses_,sas_)=line.split()[5:7]
218  msms_ases.append(float(ses_))
219  msms_asas.append(float(sas_))
220  if re.match(' Comp. probe_radius, reent, toric, contact SES SAS', line):
221  data_paragraph=True
222 
223  # clean up
224  if not keep_files:
225  __CleanupFiles(msms_data_dir)
226 
227  return (msms_ases, msms_asas)
228 
229 
230 ## \brief Calculates molecular surface by using the external MSMS program
231 #
232 # This method calculates a molecular surface by invoking the external program
233 # MSMS. First, it is checked if the MSMS executable is present, then, the
234 # necessary files are prepared in a temporary directory and MSMS is executed.
235 # The last step is to remove the temporary directory.
236 #
237 #
238 # \param entity OST entity to calculate surface
239 # \param density Surface point density
240 # \param radius Surface probe radius
241 # \param all_surf Calculate surface for all cavities (returns multiple
242 # surfaces as a list)
243 # \param no_hydrogens Calculate surface only for hevy atoms
244 # \param selection Calculate surface for subset of entity
245 # \param msms_exe msms executable (full path to executable)
246 # \param msms_env msms environment variable
247 # \param keep_files Do not delete temporary files
248 # \return list of OST SurfaceHandle objects
249 def CalculateSurface(entity, density=1.0, radius=1.5, all_surf=False,
250  no_hydrogens=False, no_hetatoms=False, no_waters=False,
251  selection='',
252  msms_exe=None, msms_env=None, keep_files=False):
253 
254  import os
255  import re
256 
257  # check if msms executable is specified
258  msms_executable=_GetExecutable(msms_exe, msms_env)
259 
260  # parse selection
261  if no_hydrogens:
262  if selection!='':
263  selection+=" and "
264  selection+="ele!=H"
265 
266  if no_hetatoms:
267  if selection!='':
268  selection+=" and "
269  selection+="ishetatm=False"
270 
271  if no_waters:
272  if selection!='':
273  selection+=" and "
274  selection+="rname!=HOH"
275 
276  # setup files for msms
277  (msms_data_dir, msms_data_file)=_SetupFiles(entity, selection)
278 
279  # set command line
280  command="%s -if %s -of %s -density %s -probe_radius %s" % (msms_executable,
281  msms_data_file, msms_data_file, density, radius)
282  if all_surf:
283  command+=" -all"
284 
285  # run msms
286  stdout_value=_RunMSMS(command)
287 
288  # parse msms output
289  num_surf=0
290  for line in stdout_value.splitlines():
291  if re.search('RS component [0-9]+ identified',line):
292  num_surf=int(line.split()[2])
293 
294  # get surfaces
295  msms_surfaces=[]
296  msms_surfaces.append(io.LoadSurface(msms_data_file, "msms"))
297  for n in range(1,num_surf+1):
298  filename=msms_data_file+'_'+str(n)
299  msms_surfaces.append(io.LoadSurface(filename, "msms"))
300 
301  # clean up
302  if not keep_files:
303  __CleanupFiles(msms_data_dir)
304 
305  return msms_surfaces
306