00001
00002 """
00003 Opal client for remote APBS execution.
00004
00005 Written by Samir Unni, Dave Gohara, Nathan Baker, Yong Huang based on example code from Sriram Krishnan
00006
00007 ----------------------------------------------------------------------
00008 APBS -- Adaptive Poisson-Boltzmann Solver
00009 Version 1.3
00010
00011 Nathan A. Baker (baker@biochem.wustl.edu)
00012 Dept. Biochemistry and Molecular Biophysics
00013 Center for Computational Biology
00014 Washington University in St. Louis
00015
00016 Additional contributing authors listed in the code documentation.
00017
00018 Copyright (c) 2002-2010, Washington University in St. Louis.
00019 Portions Copyright (c) 2002-2010. Nathan A. Baker
00020 Portions Copyright (c) 1999-2002. The Regents of the University of California.
00021 Portions Copyright (c) 1995. Michael Holst
00022
00023 All rights reserved.
00024
00025 Redistribution and use in source and binary forms, with or without
00026 modification, are permitted provided that the following conditions are met:
00027
00028 * Redistributions of source code must retain the above copyright notice, this
00029 list of conditions and the following disclaimer.
00030
00031 * Redistributions in binary form must reproduce the above copyright notice,
00032 this list of conditions and the following disclaimer in the documentation
00033 and/or other materials provided with the distribution.
00034
00035 * Neither the name of Washington University in St. Louis nor the names of its
00036 contributors may be used to endorse or promote products derived from this
00037 software without specific prior written permission.
00038
00039 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00040 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00041 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00042 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
00043 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00044 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00045 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00046 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00047 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00048 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00049 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00050 ----------------------------------------------------------------------
00051 APBS uses FETK (the Finite Element ToolKit) to solve the
00052 Poisson-Boltzmann equation numerically. FETK is a portable collection
00053 of finite element modeling class libraries developed by the Michael Holst
00054 research group and written in an object-oriented form of C. FEtk is
00055 designed to solve general coupled systems of nonlinear partial differential
00056 equations using adaptive finite element methods, inexact Newton methods,
00057 and algebraic multilevel methods. More information about FEtk may be found
00058 at <http://www.FEtk.ORG>.
00059 ----------------------------------------------------------------------
00060 APBS also uses Aqua to solve the Poisson-Boltzmann equation numerically.
00061 Aqua is a modified form of the Holst group PMG library <http://www.FEtk.ORG>
00062 which has been modified by Patrice Koehl
00063 <http://koehllab.genomecenter.ucdavis.edu/> for improved efficiency and
00064 memory usage when solving the Poisson-Boltzmann equation.
00065 ----------------------------------------------------------------------
00066 Please cite your use of APBS as:
00067
00068 Baker NA, Sept D, Joseph S, Holst MJ, McCammon JA. Electrostatics of
00069 nanosystems: application to microtubules and the ribosome. Proc.
00070 Natl. Acad. Sci. USA 98, 10037-10041 2001.
00071 ----------------------------------------------------------------------
00072 """
00073 __date__ = "3 September 2009"
00074 __author__ = "Samir Unni, Dave Gohara, Nathan Baker, Yong Huang"
00075 __version__ = "1.3"
00076
00077 import sys
00078 from sys import stdout, stderr
00079 import time
00080 import httplib, urllib
00081 import string
00082 import os, os.path
00083 import subprocess
00084 import getopt
00085
00086
00087
00088
00089
00090
00091
00092 userPath = "/usr/local/apbs-1.3"
00093
00094
00095
00096 creditString = __doc__
00097 helpString = "\n\n\
00098 This driver program calculates electrostatic potentials, energies, and forces\n\
00099 using both multigrid and finite element methods. It is invoked as:\n\
00100 \n\
00101 ApbsClient.py [options] apbs.in\n\
00102 \n\
00103 where apbs.in is a formatted input file and [options] are:\n\
00104 \n\
00105 --output-file=<name> Enables output logging to the path listed in <name>.\n\
00106 --output-format=<type> Specifies format for logging. Options for type are\n\
00107 either \"xml\" or \"flat\". Uses flat-file format if --output-format is not used.\n\
00108 --help Display this help information.\n\
00109 \n\
00110 Options specific to the Opal web client are:\n\
00111 \n\
00112 --fetch=<output file location>\n\
00113 Used to specify the location to which the files should be\n\
00114 downloaded. Files are downloaded to current directory if\n\
00115 <output file location> or the entire flag is omitted when\n\
00116 expected.\n\
00117 --library-location=<directory>\n\
00118 This path variable is only needed if you are using a binary\n\
00119 distribution of APBS. It should point to the location where\n\
00120 APBS was installed; e.g., the directory that contains the APBS\n\
00121 bin, include, lib, and share sub-directories. The following\n\
00122 value is just an example and may not be appropriate for your\n\
00123 system, depending on where you installed APBS:\n\
00124 --library-location=/usr/local/apbs-1.3\n\
00125 Note that you can also edit this Python file to eliminate the\n\
00126 need to specify this option.\n\
00127 --job-id=<job ID> Specifies the job ID of the run for which results are to be\n\
00128 downloaded. Expects the '--fetch' flag to be present as well.\n\
00129 --no-fetch Disables fetching of files following (only) a blocked run.\n\
00130 --non-blocking Disables blocking and outputs a URL at which the data can be\n\
00131 retreived later.\n\
00132 --service-location=<URL> Specifies the location of the Opal server. Defaults to\n\
00133 http://kryptonite.nbcr.net/opal2/services/apbs-1.3\n\
00134 --local Perform a local APBS run using whatever APBS executable is\n\
00135 available in the path \
00136 \n----------------------------------------------------------------------\n\
00137 \n"
00138
00139 def processOptions():
00140 """ A function that transforms command line options into a dictionary for further processing. However,
00141 presence of the -h or --help option causes the script to print help information and exit. """
00142 global helpString
00143 global service_url
00144 shortOptions = "h"
00145 longOptions = ["help", "local", "library-location=", "output-format=", "output-file=", "fetch=", "job-id=", "no-fetch", "non-blocking", "service-location="]
00146 opts, args = getopt.getopt(sys.argv[1:], shortOptions, longOptions)
00147 optionDict = {}
00148 for o, a in opts:
00149 if o in ("-h", "--help"):
00150 stdout.write("%s\n" % helpString)
00151 sys.exit()
00152 elif o == "--local":
00153 optionDict["local"] = True
00154 elif o == "--output-format":
00155 if a in ("xml", "flat"):
00156 optionDict["output-format"] = a
00157 else:
00158 stderr.write("Invalid argument (%s) for --output-format!\n" % a)
00159 sys.exit(13)
00160 elif o == "--output-file":
00161 optionDict["output-file"] = a
00162 elif o == "--fetch":
00163 optionDict["fetch"] = a
00164 elif o == "--job-id":
00165 optionDict["job-id"] = a
00166 elif o == "--no-fetch":
00167 optionDict["no-fetch"] = True
00168 elif o == "--non-blocking":
00169 optionDict["non-blocking"] = True
00170 elif o == "--service-location":
00171 optionDict["service-location"] = a
00172 service_url = optionDict["service-location"]
00173 elif o == "--library-location":
00174 optionDict["library-location"] = a
00175 else:
00176 stderr.write("Ignoring unrecognized option %s.\n" % o)
00177 optionDict["args"] = args
00178 if not (optionDict.has_key("fetch") and optionDict.has_key("job-id")):
00179 if (optionDict.has_key("fetch") or optionDict.has_key("job-id")):
00180 stderr.write("Error. Please use both \"--fetch\" and \"--job-id\" flags together.\n")
00181 stderr.write("Ignoring these flags...\n")
00182 return optionDict
00183
00184
00185 sys.path.append(userPath)
00186
00187
00188 configPath = "/home/bake113/lib/python2.6/site-packages"
00189 modPaths = [configPath, os.path.join(configPath, "ZSI")]
00190 sys.path = sys.path + modPaths
00191
00192
00193 sys.path.append(os.getcwd())
00194
00195
00196 myExecPath = sys.argv[0]
00197 pathSplit = os.path.split(myExecPath)
00198 libPath = os.path.join(pathSplit[0], "../lib/python2.5/site-packages")
00199 sys.path.append(os.path.abspath(libPath))
00200
00201
00202 if __name__ == "__main__":
00203 optDict = processOptions()
00204 if optDict.has_key("library-location"):
00205 sys.path.append(optDict["library-location"])
00206 try:
00207 import ZSI
00208 from AppService_client import AppServiceLocator, AppServicePortTypeSoapBindingSOAP, getAppMetadataRequest, launchJobRequest, queryStatusRequest, getOutputsRequest, launchJobBlockingRequest, getOutputAsBase64ByNameRequest
00209 from AppService_types import ns0
00210 from ZSI.TC import String
00211 except ImportError, errstr:
00212 stderr.write("ImportError: %s\n" % errstr)
00213 stderr.write("ApbsClient.py could not find the necessary Python libraries in any of the following locations:\n")
00214 for path in sys.path:
00215 stderr.write("\t%s\n" % path)
00216 stderr.write("Please edit the ApbsClient.py file using your favorite text editor and add the location\n")
00217 stderr.write("of your APBS installation to the section of the ApbsClient.py file labeled\n")
00218 stderr.write("\"#### USERS SHOULD ADD THEIR OWN PATH TO THE APBS INSTALLATION HERE! ####\"\n")
00219 raise ImportError, errstr
00220
00221
00222 service_url = "http://kryptonite.nbcr.net/opal2/services/apbs-1.3"
00223 parallel_service_url = "http://oolite.calit2.optiputer.net/opal2/services/apbs-parallel-1.3"
00224 local_version = "1.3"
00225 maxmem = -1
00226
00227 def enoughMemory(inputFileName):
00228 if(maxmem == -1):
00229 return True
00230
00231 inputFile = file(inputFileName, 'r')
00232 for line in inputFile:
00233 line = line.strip()
00234 if(line[:4].lower()=='dime'):
00235 grid_dimensions = line.split()
00236 return ((int(grid_dimensions[1])*int(grid_dimensions[2])*int(grid_dimensions[3])*160/(1024*1024) < maxmem))
00237
00238 def fetchResults(jobID,outputDirectory,outputFiles,fetchAll):
00239 """ Downloads files from Opal server (only if automatic downloading is enabled). """
00240 stdout.write("Downloading select results:\n")
00241 if outputDirectory != None:
00242 stdout.write("\tOutput directory: %s\n" % outputDirectory)
00243 try:
00244 os.makedirs(outputDirectory)
00245 except OSError:
00246 pass
00247 os.chdir(outputDirectory)
00248
00249 for file in outputFiles:
00250 fileName = file._name
00251 urllib.urlretrieve(file._url, fileName)
00252 stdout.write("\tDownloading %s...\n" % fileName)
00253
00254 def pollStatus(jobID,outputDirectory):
00255 """ Determines current status of run and executes fetching of results if the run is completed. """
00256 global service_url
00257 appServicePort = AppServiceLocator().getAppServicePort(service_url)
00258 status = appServicePort.queryStatus(queryStatusRequest(jobID))
00259
00260 if status._code == 4:
00261 stderr.write("Error! The calculation failed!\n")
00262 stderr.write("Message: %s\n" % status._message)
00263 sys.exit(13)
00264 elif status._code != 8:
00265 stderr.write("Sorry, the calculation hasn't been completed yet. Please wait a short while and attempt to fetch the files again.\n")
00266 sys.exit(13)
00267 else:
00268 resp = appServicePort.getOutputs(getOutputsRequest(jobID))
00269 fetchResults(jobID, outputDirectory, resp._outputFile, status._code==4)
00270
00271 def initLocalVars():
00272 """ Initializes variables for local usage. This should eventually be merged with processOptions """
00273 vars = {'typeOfRun' : 'local'}
00274 optionDict = processOptions()
00275
00276
00277 if optionDict.has_key("no-fetch"):
00278 vars['fetchFiles'] = False
00279 else:
00280 vars["fetchFiles"] = True
00281 if optionDict.has_key("non-blocking"):
00282 vars['blocking'] = False
00283 else:
00284 vars["blocking"] = True
00285
00286 vars['fetchFileDescriptionLocation'] = None
00287
00288
00289 if optionDict.has_key("fetch") and vars["blocking"]:
00290 vars["outputDirectory"] = optionDict["fetch"]
00291 else:
00292 vars['outputDirectory'] = None
00293
00294
00295 if optionDict.has_key("service-location"):
00296 vars["service_url"] = optionDict["service-location"]
00297
00298
00299 vars['outFile'] = False
00300 if optionDict.has_key("output-file"):
00301 vars["argList"] = "--output-file=%s" % optionDict["output-file"]
00302 vars["outFile"] = True
00303 if optionDict.has_key("output-format") and vars["outFile"]:
00304 if not vars.has_key("argList"):
00305 vars["argList"] = ""
00306 argString = "--output-format=%s" % optionDict["output-format"]
00307 vars["argList"] = "%s %s" % (vars["argList"], argString)
00308
00309 return vars
00310
00311 def initRemoteVars(argv):
00312 """ Initializes variables for remote usage """
00313 vars = {'typeOfRun':'remote'}
00314
00315
00316 vars['outFileName'] = os.path.basename(argv[-1])
00317 for i in (1, len(vars['outFileName'])):
00318 if(vars['outFileName'][-i]=="."):
00319 vars['outFileName']=vars['outFileName'][:-i]+".out"
00320 break
00321 return vars
00322
00323 def displayResults(jobID):
00324 """ Displays URLs of resulting files, if they are not to be fetched automatically. """
00325 global service_url
00326 appServicePort = AppServiceLocator().getAppServicePort(service_url)
00327 resp = appServicePort.getOutputs(getOutputsRequest(jobID))
00328
00329
00330 stdout.write("\tStandard Output: %s\n" % resp._stdOut, "\n")
00331 stdout.write("\tStandard Error: %s\n", resp._stdErr)
00332 if (resp._outputFile != None):
00333 for i in range(0, resp._outputFile.__len__()):
00334 stdout.write("\t%s: %s\n" % (resp._outputFile[i]._name, resp._outputFile[i]._url))
00335 stdout.write("\tStandard Error: %s\n" % resp._stdErr)
00336
00337 def execApbs(vars=None, argv=None):
00338 """ Executes APBS and regulates checking of job status and retrieval of data if job is successfully completed. """
00339 if argv is None:
00340
00341 argv = sys.argv
00342 webRun = False
00343 else:
00344 webRun = True
00345 custom_service_url = None
00346 if vars != None:
00347 if vars.has_key('service_url'):
00348 custom_service_url = vars['service_url']
00349 vars = initRemoteVars(argv)
00350 if custom_service_url != None:
00351 vars['service_url'] = custom_service_url
00352 global service_url
00353
00354
00355 vars['inFile'] = argv[-1]
00356
00357
00358 if(vars['inFile'].find("/")==-1):
00359 directory=""
00360 else:
00361 directory = os.path.dirname(vars['inFile'])+'/'
00362 vars['inFile'] = os.path.basename(vars['inFile'])
00363
00364 nprocs = 1
00365 if not vars.has_key('service_url'):
00366
00367 tempFile = open(directory+vars['inFile'], 'r')
00368 version_check_flag = True
00369 for line in tempFile:
00370
00371 line=line.strip()
00372 if(line[:5]=='pdime'):
00373 dimension_array = line.split()
00374 nprocs = int(dimension_array[1])*int(dimension_array[2])*int(dimension_array[3])
00375 global parallel_service_url
00376 vars['service_url'] = parallel_service_url
00377 version_check_flag = False
00378 if(line[:5]=='async'):
00379 vars['service_url'] = service_url
00380 version_check_flag = True
00381 break
00382 if version_check_flag:
00383 vars['service_url'] = service_url
00384
00385 tempFile.close()
00386 else:
00387 version_check_flag = True
00388 service_url = vars['service_url']
00389
00390
00391 appServicePort = AppServiceLocator().getAppServicePort(vars['service_url'])
00392
00393
00394 req = launchJobRequest()
00395
00396 if version_check_flag:
00397 opal_version = AppServicePortTypeSoapBindingSOAP(vars['service_url']).getAppMetadata(getAppMetadataRequest())._usage.split()[-1]
00398 if opal_version != local_version:
00399 stderr.write("WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!\n")
00400 stderr.write("It appears that the remote server version of APBS (%s) does not match\nthe local version (%s)!\n" % (opal_version,local_version))
00401 stderr.write("Proceed at your own risk!!\n")
00402 stderr.write("WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!\n")
00403 if webRun:
00404 return False
00405
00406 if(vars.has_key('argList')):
00407 vars['argList'] = vars['argList'] + " " + vars['inFile']
00408 else:
00409 vars['argList']=vars['inFile']
00410
00411 req._argList = vars['argList']
00412 req._numProcs = nprocs
00413
00414
00415 inputFiles = []
00416
00417 inputFiles.append(ns0.InputFileType_Def('inputFile'))
00418
00419 inputFiles[-1]._name = vars['inFile']
00420 tempFile = open(directory+vars['inFile'], 'r')
00421 inputFiles[-1]._contents = tempFile.read()
00422 tempFile.close()
00423
00424
00425 start = False
00426 tempFile = open(directory+vars['inFile'], 'r')
00427 for line in tempFile:
00428
00429 line=line.strip()
00430 if(line=="end"):
00431 break
00432 if(start and line.find("#")!=0):
00433
00434
00435 if(line.find("#")!=-1):
00436 line = line[:line.find("#")]
00437
00438 line=line.strip()
00439
00440 count = -1
00441 while line[count]!=' ':
00442 count = count-1
00443 fileName=line[count+1:]
00444 inputFiles.append(ns0.InputFileType_Def('inputFile'))
00445 inputFiles[-1]._name=fileName
00446 tempFile2 = open(directory+fileName, "r")
00447 inputFiles[-1]._contents = tempFile2.read()
00448 tempFile2.close()
00449 if(line=="read"):
00450 start = True
00451
00452 tempFile.close()
00453
00454
00455 req._inputFile = inputFiles
00456
00457 if vars['typeOfRun']=='remote':
00458 appServicePort.launchJob(req)
00459 return [appServicePort, appServicePort.launchJob(req)]
00460
00461
00462 print "Launching remote APBS job"
00463 try:
00464 resp = appServicePort.launchJob(req)
00465 except ZSI.FaultException, errstr:
00466 stderr.write("Error! Failed to execute Opal job. Please send the entire output to the APBS development team.\n")
00467 stderr.write("%s\n" % errstr.fault.AsSoap())
00468 sys.exit(13)
00469
00470 jobID = resp._jobID
00471 print "Received Job ID:", jobID
00472
00473 status = resp._status
00474
00475 if(vars['blocking']):
00476
00477 print "Polling job status"
00478 while 1:
00479
00480 stdout.write("Status:\n")
00481 stdout.write("\tCode: %s\n" % status._code)
00482 stdout.write("\tMessage: %s\n" % status._message)
00483 stdout.write("\tURL for all results: %s\n" % status._baseURL)
00484
00485 if (status._code == 8) or (status._code == 4) or (not vars['blocking']):
00486
00487 break
00488
00489
00490 stdout.write("Waiting 30 seconds...\n")
00491 time.sleep(30)
00492
00493
00494 status = appServicePort.queryStatus(queryStatusRequest(jobID))
00495
00496
00497 if vars['fetchFiles']:
00498 pollStatus(jobID, vars['outputDirectory'])
00499 else:
00500 displayResults(jobID)
00501
00502
00503 else:
00504 stdout.write("When the job is complete, the results can be retrieved at: \n")
00505 stdout.write("\t%s\n" % status._baseURL)
00506 stdout.write("If you want to use the APBS Opal client to download the results for you, the job ID is:\n")
00507 stdout.write("\t%s\n" % jobID)
00508
00509 def main():
00510 """ Parses input, runs local jobs, and fetches files from previously completed calculations. """
00511
00512 global helpString
00513 print creditString
00514
00515 if len(sys.argv) == 1:
00516 stderr.write("Error! This program must be called with at least one option!\n")
00517 stderr.write("%s\n" % helpString)
00518 sys.exit()
00519
00520
00521 optionDict = processOptions()
00522 if optionDict.has_key("help"):
00523 stdout.write("%s\n" % helpString)
00524 sys.exit()
00525
00526
00527 if optionDict.has_key("local"):
00528
00529 args = []
00530 args.append('apbs')
00531 if optionDict.has_key("output-file"):
00532 argString = "--output-file=%s" % optionDict["output-file"]
00533 args.append(argString)
00534 if optionDict.has_key("output-format"):
00535 argString = "--output-format=%s" % optionDict["output-format"]
00536 args.append(argString)
00537 if optionDict.has_key("args"):
00538 args = args + optionDict["args"]
00539 if(not enoughMemory(optionDict["args"][-1])):
00540
00541 print "Job will use too much memory to complete calculation"
00542 sys.exit()
00543 stdout.write("Running: %s...\n" % " ".join(args))
00544 subprocess.call(args)
00545 sys.exit()
00546
00547
00548 if optionDict.has_key("job-id"):
00549 jobID = optionDict["job-id"]
00550 outputDirectory = None
00551 if optionDict.has_key("fetch"):
00552 outputDirectory = optionDict["fetch"]
00553 pollStatus(jobID,outputDirectory)
00554 sys.exit()
00555
00556
00557
00558
00559 if __name__ == "__main__":
00560 main()
00561 execApbs(vars=initLocalVars())