00001
00002
00003 """
00004 psize.py
00005 Get dimensions and other interesting information from a PQR file
00006
00007 Originally written by Dave Sept
00008 Additional APBS-specific features added by Nathan Baker
00009 Ported to Python/Psize class by Todd Dolinsky and subsequently hacked by
00010 Nathan Baker
00011
00012 Version: $Id: psize.py 1552 2010-02-10 17:46:27Z yhuang01 $
00013
00014 APBS -- Adaptive Poisson-Boltzmann Solver
00015
00016 Nathan A. Baker (baker@biochem.wustl.edu)
00017 Dept. Biochemistry and Molecular Biophysics
00018 Center for Computational Biology
00019 Washington University in St. Louis
00020
00021 Additional contributing authors listed in the code documentation.
00022
00023 Copyright (c) 2002-2010, Washington University in St. Louis.
00024 Portions Copyright (c) 2002-2010. Nathan A. Baker
00025 Portions Copyright (c) 1999-2002. The Regents of the University of California.
00026 Portions Copyright (c) 1995. Michael Holst
00027
00028 All rights reserved.
00029
00030 Redistribution and use in source and binary forms, with or without
00031 modification, are permitted provided that the following conditions are met:
00032
00033 * Redistributions of source code must retain the above copyright notice, this
00034 list of conditions and the following disclaimer.
00035
00036 * Redistributions in binary form must reproduce the above copyright notice,
00037 this list of conditions and the following disclaimer in the documentation
00038 and/or other materials provided with the distribution.
00039
00040 * Neither the name of Washington University in St. Louis nor the names of its
00041 contributors may be used to endorse or promote products derived from this
00042 software without specific prior written permission.
00043
00044 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00045 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00046 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00047 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
00048 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00049 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00050 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00051 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00052 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00053 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00054 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00055
00056 """
00057
00058 import string, sys, getopt
00059 from sys import stdout, stderr
00060 from math import log
00061
00062 class Psize:
00063 """Master class for parsing input files and suggesting settings"""
00064 def __init__(self):
00065 self.constants = {"cfac": 1.7, "fadd":20, "space": 0.50, "gmemfac": 200, "gmemceil": 400, "ofrac":0.1, "redfac": 0.25 }
00066 self.minlen = [None, None, None]
00067 self.maxlen = [None, None, None]
00068 self.q = 0.0
00069 self.gotatom = 0
00070 self.gothet = 0
00071 self.olen = [0.0, 0.0, 0.0]
00072 self.cen = [0.0, 0.0, 0.0]
00073 self.clen = [0.0, 0.0, 0.0]
00074 self.flen = [0.0, 0.0, 0.0]
00075 self.n = [0, 0, 0]
00076 self.np = [0.0, 0.0, 0.0]
00077 self.nsmall = [0,0,0]
00078 self.nfocus = 0
00079
00080 def parseString(self, structure):
00081 """ Parse the input structure as a string in PDB or PQR format """
00082 lines = string.split(structure, "\n")
00083 self.parseLines(lines)
00084
00085 def parseInput(self, filename):
00086 """ Parse input structure file in PDB or PQR format """
00087 file = open(filename, "r")
00088 self.parseLines(file.readlines())
00089
00090 def parseLines(self, lines):
00091 """ Parse the lines """
00092 for line in lines:
00093 if string.find(line,"ATOM") == 0:
00094 subline = string.replace(line[30:], "-", " -")
00095 words = string.split(subline)
00096 if len(words) < 4:
00097 continue
00098 self.gotatom = self.gotatom + 1
00099 self.q = self.q + float(words[3])
00100 rad = float(words[4])
00101 center = []
00102 for word in words[0:3]:
00103 center.append(float(word))
00104 for i in range(3):
00105 if self.minlen[i] == None or center[i]-rad < self.minlen[i]:
00106 self.minlen[i] = center[i]-rad
00107 if self.maxlen[i] == None or center[i]+rad > self.maxlen[i]:
00108 self.maxlen[i] = center[i]+rad
00109 elif string.find(line, "HETATM") == 0:
00110 self.gothet = self.gothet + 1
00111
00112 if self.gotatom == 0:
00113 subline = string.replace(line[30:], "-", " -")
00114 words = string.split(subline)
00115 if len(words) < 4:
00116 continue
00117 self.q = self.q + float(words[3])
00118 rad = float(words[4])
00119 center = []
00120 for word in words[0:3]:
00121 center.append(float(word))
00122 for i in range(3):
00123 if self.minlen[i] == None or center[i]-rad < self.minlen[i]:
00124 self.minlen[i] = center[i]-rad
00125 if self.maxlen[i] == None or center[i]+rad > self.maxlen[i]:
00126 self.maxlen[i] = center[i]+rad
00127
00128 def setConstant(self, name, value):
00129 """ Set a constant to a value; returns 0 if constant not found """
00130 try:
00131 self.constants[name] = value
00132 return 1
00133 except KeyError:
00134 return 0
00135
00136 def getConstant(self, name):
00137 """ Get a constant value; raises KeyError if constant not found """
00138 return self.constants[name]
00139
00140 def setLength(self, maxlen, minlen):
00141 """ Compute molecule dimensions """
00142 for i in range(3):
00143 self.olen[i] = maxlen[i] - minlen[i]
00144 if self.olen[i] < 0.1:
00145 self.olen[i] = 0.1
00146 return self.olen
00147
00148 def setCoarseGridDims(self, olen):
00149 """ Compute coarse mesh dimensions """
00150 for i in range(3):
00151 self.clen[i] = self.constants["cfac"] * olen[i]
00152 return self.clen
00153
00154 def setFineGridDims(self, olen, clen):
00155 """ Compute fine mesh dimensions """
00156 for i in range(3):
00157 self.flen[i] = olen[i] + self.constants["fadd"]
00158 if self.flen[i] > clen[i]:
00159 self.flen[i] = clen[i]
00160 return self.flen
00161
00162 def setCenter(self, maxlen, minlen):
00163 """ Compute molecule center """
00164 for i in range(3):
00165 self.cen[i] = (maxlen[i] + minlen[i]) / 2
00166 return self.cen
00167
00168 def setFineGridPoints(self, flen):
00169 """ Compute mesh grid points, assuming 4 levels in MG hierarchy """
00170 tn = [0,0,0]
00171 for i in range(3):
00172 tn[i] = int(flen[i]/self.constants["space"] + 0.5)
00173 self.n[i] = 32*(int((tn[i] - 1) / 32.0 + 0.5)) + 1
00174 if self.n[i] < 33:
00175 self.n[i] = 33
00176 return self.n
00177
00178 def setSmallest(self, n):
00179 """ Compute parallel division in case memory requirement above
00180 ceiling Find the smallest dimension and see if the number of
00181 grid points in that dimension will fit below the memory ceiling
00182 Reduce nsmall until an nsmall^3 domain will fit into memory """
00183 nsmall = []
00184 for i in range(3):
00185 nsmall.append(n[i])
00186 while 1:
00187 nsmem = 200.0 * nsmall[0] * nsmall[1] * nsmall[2] / 1024 / 1024
00188 if nsmem < self.constants["gmemceil"]:
00189 break
00190 else:
00191 i = nsmall.index(max(nsmall))
00192 nsmall[i] = 32 * ((nsmall[i] - 1)/32 - 1) + 1
00193 if nsmall <= 0:
00194 stdout.write("You picked a memory ceiling that is too small\n")
00195 sys.exit(0)
00196
00197 self.nsmall = nsmall
00198 return nsmall
00199
00200 def setProcGrid(self, n, nsmall):
00201 """ Calculate the number of processors required to span each
00202 dimension """
00203
00204 zofac = 1 + 2 * self.constants["ofrac"]
00205 for i in range(3):
00206 self.np[i] = n[i]/float(nsmall[i])
00207 if self.np[i] > 1: self.np[i] = int(zofac*n[1]/nsmall[i] + 1.0)
00208 return self.np
00209
00210 def setFocus(self, flen, np, clen):
00211 """ Calculate the number of levels of focusing required for
00212 each processor subdomain """
00213
00214 nfoc = [0,0,0]
00215 for i in range(3):
00216 nfoc[i] = int(log((flen[i]/np[i])/clen[i])/log(self.constants["redfac"]) + 1.0)
00217 nfocus = nfoc[0]
00218 if nfoc[1] > nfocus: nfocus = nfoc[1]
00219 if nfoc[2] > nfocus: nfocus = nfoc[2]
00220 if nfocus > 0: nfocus = nfocus + 1
00221 self.nfocus = nfocus
00222
00223 def setAll(self):
00224 """ Set up all of the things calculated individually above """
00225 maxlen = self.getMax()
00226 minlen = self.getMin()
00227 self.setLength(maxlen, minlen)
00228 olen = self.getLength()
00229
00230 self.setCoarseGridDims(olen)
00231 clen = self.getCoarseGridDims()
00232
00233 self.setFineGridDims(olen, clen)
00234 flen = self.getFineGridDims()
00235
00236 self.setCenter(maxlen, minlen)
00237 cen = self.getCenter()
00238
00239 self.setFineGridPoints(flen)
00240 n = self.getFineGridPoints()
00241
00242 self.setSmallest(n)
00243 nsmall = self.getSmallest()
00244
00245 self.setProcGrid(n, nsmall)
00246 np = self.getProcGrid()
00247
00248 self.setFocus(flen, np, clen)
00249 nfocus = self.getFocus()
00250
00251 def getMax(self): return self.maxlen
00252 def getMin(self): return self.minlen
00253 def getCharge(self): return self.q
00254 def getLength(self): return self.olen
00255 def getCoarseGridDims(self): return self.clen
00256 def getFineGridDims(self): return self.flen
00257 def getCenter(self): return self.cen
00258 def getFineGridPoints(self): return self.n
00259 def getSmallest(self): return self.nsmall
00260 def getProcGrid(self): return self.np
00261 def getFocus(self): return self.nfocus
00262
00263 def runPsize(self, filename):
00264 """ Parse input PQR file and set parameters """
00265 self.parseInput(filename)
00266 self.setAll()
00267
00268 def printResults(self):
00269 """ Return a string with the formatted results """
00270
00271 str = "\n"
00272
00273 if self.gotatom > 0:
00274
00275 maxlen = self.getMax()
00276 minlen = self.getMin()
00277 q = self.getCharge()
00278 olen = self.getLength()
00279 clen = self.getCoarseGridDims()
00280 flen = self.getFineGridDims()
00281 cen = self.getCenter()
00282 n = self.getFineGridPoints()
00283 nsmall = self.getSmallest()
00284 np = self.getProcGrid()
00285 nfocus = self.getFocus()
00286
00287
00288 nsmem = 200.0 * nsmall[0] * nsmall[1] * nsmall[2] / 1024 / 1024
00289 gmem = 200.0 * n[0] * n[1] * n[2] / 1024 / 1024
00290
00291
00292 str = str + "################# MOLECULE INFO ####################\n"
00293 str = str + "Number of ATOM entries = %i\n" % self.gotatom
00294 str = str + "Number of HETATM entries (ignored) = %i\n" % self.gothet
00295 str = str + "Total charge = %.3f e\n" % q
00296 str = str + "Dimensions = %.3f x %.3f x %.3f A\n" % (olen[0], olen[1], olen[2])
00297 str = str + "Center = %.3f x %.3f x %.3f A\n" % (cen[0], cen[1], cen[2])
00298 str = str + "Lower corner = %.3f x %.3f x %.3f A\n" % (minlen[0], minlen[1], minlen[2])
00299 str = str + "Upper corner = %.3f x %.3f x %.3f A\n" % (maxlen[0], maxlen[1], maxlen[2])
00300
00301 str = str + "\n"
00302 str = str + "############## GENERAL CALCULATION INFO #############\n"
00303 str = str + "Coarse grid dims = %.3f x %.3f x %.3f A\n" % (clen[0],
00304 clen[1] , clen[2])
00305 str = str + "Fine grid dims = %.3f x %.3f x %.3f A\n" % (flen[0], flen[1], flen[2])
00306 str = str + "Num. fine grid pts. = %i x %i x %i\n" % (n[0], n[1], n[2])
00307
00308 if gmem > self.constants["gmemceil"]:
00309 str = str + "Parallel solve required (%.3f MB > %.3f MB)\n" % (gmem, self.constants["gmemceil"])
00310 str = str + "Total processors required = %i\n" % (np[0]*np[1]*np[2])
00311 str = str + "Proc. grid = %i x %i x %i\n" % (np[0], np[1], np[2])
00312 str = str + "Grid pts. on each proc. = %i x %i x %i\n" % (nsmall[0], nsmall[1], nsmall[2])
00313 xglob = np[0]*round(nsmall[0]/(1 + 2*self.constants["ofrac"]) - .001)
00314 yglob = np[1]*round(nsmall[1]/(1 + 2*self.constants["ofrac"]) - .001)
00315 zglob = np[2]*round(nsmall[2]/(1 + 2*self.constants["ofrac"]) - .001)
00316 if np[0] == 1:
00317 xglob = nsmall[0]
00318 if np[1] == 1:
00319 yglob = nsmall[1]
00320 if np[2] == 1:
00321 zglob = nsmall[2]
00322 str = str + "Fine mesh spacing = %g x %g x %g A\n" % (flen[0]/(xglob-1), flen[1]/(yglob-1), flen[2]/(zglob-1))
00323 str = str + "Estimated mem. required for parallel solve = %.3f MB/proc.\n" % nsmem
00324 ntot = nsmall[0]*nsmall[1]*nsmall[2]
00325
00326 else:
00327 str = str + "Fine mesh spacing = %g x %g x %g A\n" % (flen[0]/(n[0]-1), flen[1]/(n[1]-1), flen[2]/(n[2]-1))
00328 str = str + "Estimated mem. required for sequential solve = %.3f MB\n" % gmem
00329 ntot = n[0]*n[1]*n[2]
00330
00331 str = str + "Number of focusing operations = %i\n" % nfocus
00332
00333 str = str + "\n"
00334 str = str + "################# ESTIMATED REQUIREMENTS ####################\n"
00335 str = str + "Memory per processor = %.3f MB\n" % (200.0*ntot/1024/1024)
00336 str = str + "Grid storage requirements (ASCII) = %.3f MB\n" % (8.0*12*np[0]*np[1]*np[2]*ntot/1024/1024)
00337 str = str + "\n"
00338
00339 else:
00340 str = str + "No ATOM entires in file!\n\n"
00341
00342 return str
00343
00344 def usage(rc):
00345 """ Print usage information and exit with error code rc """
00346 psize = Psize()
00347 s = "\n"
00348 s = s + "Psize script (part of APBS)\n\n"
00349 s = s + "Usage: psize.py [opts] <filename>\n\n"
00350 s = s + "Optional Arguments:\n"
00351 s = s + " --help, -h\n"
00352 s = s + " Display this text\n"
00353 s = s + " --cfac=<value> [default = %g]\n" % psize.getConstant("cfac")
00354 s = s + "\
00355 Factor by which to expand molecular dimensions to get coarse\n\
00356 grid dimensions. This value might need to be increased if\n\
00357 very simple boundary conditions or very highly charged molecules\n\
00358 are used.\n"
00359 s = s + " --fadd=<value> [default = %g]\n" % psize.getConstant("fadd")
00360 s = s + "\
00361 Amount (in A) to add to molecular dimensions to get fine grid\n\
00362 dimensions. This value might need to be increased in the\n\
00363 visualization of highly charged molecules to prevent isocontours\n\
00364 from being truncated/clipped.\n"
00365 s = s + " --space=<value> [default = %g]\n" % psize.getConstant("space")
00366 s = s + "\
00367 The desired fine mesh spacing (in A). This should be 0.5 A or\n\
00368 less for quantitative calculations but can be increased for\n\
00369 coarse visualization.\n"
00370 s = s + " --gmemceil=<value> [default = %g]\n" % psize.getConstant("gmemceil")
00371 s = s + "\
00372 Maximum memory (in MB) available per-processor for a calculation.\n\
00373 This should be adjusted to fit your machine. If the calculation\n\
00374 exceeds this value, psize will recommend settings for parallel\n\
00375 focusing.\n"
00376 s = s + " --ofrac=<value> [default = %g]\n" % psize.getConstant("ofrac")
00377 s = s + "\
00378 Desired overlap between parallel focusing grids. Although the\n\
00379 default value works for many calculations, the best setting can\n\
00380 be somewhat system-dependent. Users are encouraged to check\n\
00381 multiple values of ofrac for quantitative calcualtions.\n"
00382 s = s + " --gmemfac=<value> [default = %g]\n" % psize.getConstant("gmemfac")
00383 s = s + "\
00384 APBS memory usage (in bytes per grid point) for a sequential\n\
00385 multigrid calculatiaon. This value should not need to be\n\
00386 adjusted unless the program has been modified.\n"
00387 s = s + " --redfac=<value> [default = %g]\n" % psize.getConstant("redfac")
00388 s = s + "\
00389 APBS maximum reduction of grid spacing during focusing. This\n\
00390 value should not need to be adjusted unless the program has\n\
00391 been modified.\n"
00392
00393 stderr.write(s)
00394 sys.exit(rc)
00395
00396 def main():
00397 """ Main driver for this script """
00398 filename = ""
00399 shortOptList = "h"
00400 longOptList = ["help", "cfac=", "fadd=", "space=", "gmemfac=", "gmemceil=", "ofrac=", "redfac=" ]
00401 try:
00402 opts, args = getopt.getopt(sys.argv[1:], shortOptList, longOptList)
00403 except getopt.GetoptError, details:
00404 stderr.write("Option error (%s)!\n" % details)
00405 usage(2)
00406 if len(args) != 1:
00407 stderr.write("Invalid argument list!\n")
00408 usage(2)
00409 else:
00410 filename = args[0]
00411
00412 psize = Psize()
00413
00414 for o, a in opts:
00415 if (o.lower() == "--help") or (o == "-h"):
00416 usage(0)
00417 if o.lower() == "--cfac":
00418 psize.setConstant("cfac", float(a))
00419 if o.lower() == "--fadd":
00420 psize.setConstant("fadd", int(a))
00421 if o.lower() == "--space":
00422 psize.setConstant("space", float(a))
00423 if o.lower() == "--gmemfac":
00424 psize.setConstant("gmemfac", int(a))
00425 if o.lower() == "--gmemceil":
00426 psize.setConstant("gmemceil", int(a))
00427 if o.lower() == "--ofrac":
00428 psize.setConstant("ofrac", float(a))
00429 if o.lower() == "--redfac":
00430 psize.setConstant("redfac", float(a))
00431
00432 psize.runPsize(filename)
00433 stdout.write("# Constants used: \n");
00434 for key in psize.constants.keys():
00435 stdout.write("# \t%s: %s\n" % (key, psize.constants[key]))
00436 stdout.write("# Run:\n")
00437 stdout.write("# `%s --help`\n" % sys.argv[0])
00438 stdout.write("# for more information on these default values\n" )
00439 stdout.write(psize.printResults())
00440
00441 if __name__ == "__main__": main()