<filename>RunGulpNEB.py
"""Execute GULP NEB calculations in work directory (as necessary) and report resultant barrier energies.
"""
import sys
import os # access, rename, chdir, listdir, path.
import subprocess as sb # call
import traceback
from time import strftime
from configparser import ConfigParser
from Errors import AppError
from PyLib.TinyParser import TinyParser
from PyLib.Log import Log # logging
import Gulp as gulp

def RunGulpNEBCalculation(report,log,sGulpHostGuestStepDirPath,sHostGuestStepName,appConfig):
	"""Run GULP NEB calculation as necessary; log whether this was successful, and if so, include barrier energies.
	The NEB calculation does not need to run again if the NEB.gout file already exists.
	[report]: PyLib.Log, report object.
	[log]: PyLib.Log, log object. 
	[sGulpHostGuestStepDirPath]: string, [host-guest-step]/GULP/ directory path.
	[sHostGuestStepName]: string, [host-guest-step] name.
	[appConfig]: AppConfig, configuration object.
	"""
	sGulpInputFileNameNoExtn= sHostGuestStepName+"_NEB"
	sGULPNEBInputFilePathNoExt= sGulpHostGuestStepDirPath+sGulpInputFileNameNoExtn
	sGULPNEBInputFilePath= sGULPNEBInputFilePathNoExt+".gin"
	sGULPNEBOuputFilePath= sGULPNEBInputFilePathNoExt+".gout"
	
#	if log is not None: 
#		log.Info("Start [%s] at %s" % (sHostGuestStepName,strftime("%Y-%m-%d %H:%M:%S")),True)
	if not os.access(sGULPNEBInputFilePath,os.F_OK): 
		#if log is not None: log.Info("Skip - no NEB.gin",True)
		return
	log.Info("Start [%s] at %s" % (sHostGuestStepName,strftime("%Y-%m-%d %H:%M:%S")),True)
	
	# specify 'force norm' tolerance of 0.001 (as GULP manual), else uses default of 0.0001, which we find to be too strict.
	parser= TinyParser(None,sGULPNEBInputFilePath)
	if parser.Find("nebtolerance")>-1:
		parser.MoveTo("nebtolerance",0)
		parser.Anchor()
		parser.EatLine()
	else:
		parser.MoveTo("cell",0)
		parser.Anchor()
	parser.Replace("nebtolerance 0.001\n")
	parser.Save(sGULPNEBInputFilePath)
	
	bExecGulp= True
	if bExecGulp:
		iGulpResult= gulp.Optimise(appConfig.sPathToGulpExecutable,
			sGulpHostGuestStepDirPath,sGulpInputFileNameNoExtn,
			"cp850",appConfig.iNumMPIProcesses,appConfig.iNebTimeoutSeconds,bTestErrorCode=False)
	else:
		with open(sGULPNEBOuputFilePath,"r") as fGulpOutputFile:
			sGulpOutputFileBody= fGulpOutputFile.read()
		iGulpResult= gulp.GetRelaxResult(sGulpOutputFileBody)
	sReport=sHostGuestStepName+"\t"
	if gulp.RelaxResult.IsFail(iGulpResult):
		sReport+= "0\t"+str(iGulpResult)
	else:
		fEnergy1st,fEnergyLast,fEnergyMin,fEnergyMax= gulp.ReadEnergyNEBConfiguations(sGULPNEBOuputFilePath)
		sReport+= "1\t%d\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f" % ( gulp.GetNumberNEBIterations(sGULPNEBOuputFilePath),
			fEnergy1st,fEnergyMin,fEnergyMax,fEnergyMax-fEnergyMin,fEnergyLast)
	report.Log(sReport,report.INFO,bConsole=False)

class AppConfig:
	"""Application configuration object."""
	def __init__(self):
		"""Initialise from command line and config.ini file; quit application on fatal error.
		"""
		sConfigFilePath= sys.argv[1]
		if not os.access(sConfigFilePath,os.F_OK):
			print("Error, config file doesn't exist")
			sys.exit(1)
		sSetupId= sys.argv[2].lower().strip()
		config= ConfigParser(allow_no_value=True,inline_comment_prefixes=("#",";"))
		with open(sConfigFilePath,"r") as hFile:
				config.read_file(hFile,sConfigFilePath)
		if sSetupId not in config:
			print("Quiting: error, config setup ID ["+sSetupId+"] not found in '"+sConfigFilePath+"'")
			sys.exit(1)
		setupSection= config[sSetupId]
		
		self.sOutputBaseDir= setupSection["OutputBaseDir"]
		self.sOutputDir= self.sOutputBaseDir+setupSection["OutputSubDir"]
		self.sPathToGulpExecutable= setupSection["PathToGulpExecutable"]
		self.sGulpRelaxEndpointLibrary= setupSection["GulpRelaxEndpointLibrary"]
		self.sGulpNEBLibrary= setupSection["GulpNEBLibrary"]
		self.sGulpNEBInputFileHeader= setupSection["GulpNEBInputFileHeader"]
		self.iNumMPIProcesses= int(setupSection["NumMPIProcesses"])
		self.bRunParallelMPI= int(setupSection["RunParallelMPI"])==1
		self.iNebTimeoutSeconds= int(setupSection["NebTimeoutSeconds"])
		if self.iNebTimeoutSeconds==-1: self.iNebTimeoutSeconds=None
		self.sDebugTraceFileName= setupSection["DebugTraceFileName"]
		self.bRunGULPOptimisationEvenWhereOutputFilesExist= int(setupSection["RunGULPOptimisationEvenWhereOutputFilesExist"])==1
		if not os.access(self.sPathToGulpExecutable,os.F_OK):
			print("Error, GULP executable not found. Check config [PathToGulpExecutable] path, currently:\n"+self.sPathToGulpExecutable)
			sys.exit(1)
		if self.sGulpRelaxEndpointLibrary!="" and not os.access(self.sGulpRelaxEndpointLibrary,os.F_OK):
			print("Error, GULP relax endpoint library not found. Check config [GulpRelaxEndpointLibrary] path, currently:\n"+self.sGulpRelaxEndpointLibrary)
			sys.exit(1)
	
def Main():
	"""Program entry point.
	"""
	if len(sys.argv)!=3:
		print("Syntax: python RunGulpNEB.py <configFilePath> <setupId>,\n"
			" where <configFile> is the configuration .ini file, and <setupId> identifies the specific settings in this file to apply.\n")
		sys.exit(1)
	appConfig= AppConfig()
	log=None
	report=None
	if appConfig.sDebugTraceFileName!="":
		log= Log(appConfig.sOutputBaseDir+appConfig.sDebugTraceFileName,Log.INFO)
	report= Log(appConfig.sOutputBaseDir+"report-GULP-NEB.txt",Log.INFO)
	report.Log("#Refcode\tbSuccess\tErrCode_Or_NumIterations\t1st_eV\tMin_eV\tMax_eV\tdMaxMin_eV\tLast_eV",report.INFO,bConsole=False)
	log.Info("RunGulpNEB.py, running (%s)" % strftime("%Y-%m-%d %H:%M:%S"),True)

	# scan output dir structure
	for sEntry in os.listdir(appConfig.sOutputDir):
		sHostGuestStepDirPath= appConfig.sOutputDir+sEntry
		sGulpHostGuestStepDirPath= sHostGuestStepDirPath+"/GULP/"
		if os.path.isdir(sHostGuestStepDirPath) and os.access(sGulpHostGuestStepDirPath,os.F_OK):
			try:
				RunGulpNEBCalculation(report,log,sGulpHostGuestStepDirPath,sEntry,appConfig)
			except KeyboardInterrupt: # don't catch Ctrl+C in long loop-bodies
				raise
			except SystemExit: # likewise sys.exit(0)
				raise
			except AppError as appError: # don't quit if we know this error
				if log is not None: log.Error(appError.args[0],True)
			except:
				if log is not None: log.Error(traceback.format_exc(),True) # or last as string
				raise

if __name__=="__main__": Main()
<filename>RunGulpNEB.py
"""Execute GULP NEB calculations in work directory (as necessary) and report resultant barrier energies.
"""
import sys
import os # access, rename, chdir, listdir, path.
import subprocess as sb # call
import traceback
from time import strftime
from configparser import ConfigParser
from Errors import AppError
from PyLib.TinyParser import TinyParser
from PyLib.Log import Log # logging
import Gulp as gulp

def RunGulpNEBCalculation(report,log,sGulpHostGuestStepDirPath,sHostGuestStepName,appConfig):
	"""Run GULP NEB calculation as necessary; log whether this was successful, and if so, include barrier energies.
	The NEB calculation does not need to run again if the NEB.gout file already exists.
	[report]: PyLib.Log, report object.
	[log]: PyLib.Log, log object. 
	[sGulpHostGuestStepDirPath]: string, [host-guest-step]/GULP/ directory path.
	[sHostGuestStepName]: string, [host-guest-step] name.
	[appConfig]: AppConfig, configuration object.
	"""
	sGulpInputFileNameNoExtn= sHostGuestStepName+"_NEB"
	sGULPNEBInputFilePathNoExt= sGulpHostGuestStepDirPath+sGulpInputFileNameNoExtn
	sGULPNEBInputFilePath= sGULPNEBInputFilePathNoExt+".gin"
	sGULPNEBOuputFilePath= sGULPNEBInputFilePathNoExt+".gout"
	
#	if log is not None: 
#		log.Info("Start [%s] at %s" % (sHostGuestStepName,strftime("%Y-%m-%d %H:%M:%S")),True)
	if not os.access(sGULPNEBInputFilePath,os.F_OK): 
		#if log is not None: log.Info("Skip - no NEB.gin",True)
		return
	log.Info("Start [%s] at %s" % (sHostGuestStepName,strftime("%Y-%m-%d %H:%M:%S")),True)
	
	# specify 'force norm' tolerance of 0.001 (as GULP manual), else uses default of 0.0001, which we find to be too strict.
	parser= TinyParser(None,sGULPNEBInputFilePath)
	if parser.Find("nebtolerance")>-1:
		parser.MoveTo("nebtolerance",0)
		parser.Anchor()
		parser.EatLine()
	else:
		parser.MoveTo("cell",0)
		parser.Anchor()
	parser.Replace("nebtolerance 0.001\n")
	parser.Save(sGULPNEBInputFilePath)
	
	bExecGulp= True
	if bExecGulp:
		iGulpResult= gulp.Optimise(appConfig.sPathToGulpExecutable,
			sGulpHostGuestStepDirPath,sGulpInputFileNameNoExtn,
			"cp850",appConfig.iNumMPIProcesses,appConfig.iNebTimeoutSeconds,bTestErrorCode=False)
	else:
		with open(sGULPNEBOuputFilePath,"r") as fGulpOutputFile:
			sGulpOutputFileBody= fGulpOutputFile.read()
		iGulpResult= gulp.GetRelaxResult(sGulpOutputFileBody)
	sReport=sHostGuestStepName+"\t"
	if gulp.RelaxResult.IsFail(iGulpResult):
		sReport+= "0\t"+str(iGulpResult)
	else:
		fEnergy1st,fEnergyLast,fEnergyMin,fEnergyMax= gulp.ReadEnergyNEBConfiguations(sGULPNEBOuputFilePath)
		sReport+= "1\t%d\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f" % ( gulp.GetNumberNEBIterations(sGULPNEBOuputFilePath),
			fEnergy1st,fEnergyMin,fEnergyMax,fEnergyMax-fEnergyMin,fEnergyLast)
	report.Log(sReport,report.INFO,bConsole=False)

class AppConfig:
	"""Application configuration object."""
	def __init__(self):
		"""Initialise from command line and config.ini file; quit application on fatal error.
		"""
		sConfigFilePath= sys.argv[1]
		if not os.access(sConfigFilePath,os.F_OK):
			print("Error, config file doesn't exist")
			sys.exit(1)
		sSetupId= sys.argv[2].lower().strip()
		config= ConfigParser(allow_no_value=True,inline_comment_prefixes=("#",";"))
		with open(sConfigFilePath,"r") as hFile:
				config.read_file(hFile,sConfigFilePath)
		if sSetupId not in config:
			print("Quiting: error, config setup ID ["+sSetupId+"] not found in '"+sConfigFilePath+"'")
			sys.exit(1)
		setupSection= config[sSetupId]
		
		self.sOutputBaseDir= setupSection["OutputBaseDir"]
		self.sOutputDir= self.sOutputBaseDir+setupSection["OutputSubDir"]
		self.sPathToGulpExecutable= setupSection["PathToGulpExecutable"]
		self.sGulpRelaxEndpointLibrary= setupSection["GulpRelaxEndpointLibrary"]
		self.sGulpNEBLibrary= setupSection["GulpNEBLibrary"]
		self.sGulpNEBInputFileHeader= setupSection["GulpNEBInputFileHeader"]
		self.iNumMPIProcesses= int(setupSection["NumMPIProcesses"])
		self.bRunParallelMPI= int(setupSection["RunParallelMPI"])==1
		self.iNebTimeoutSeconds= int(setupSection["NebTimeoutSeconds"])
		if self.iNebTimeoutSeconds==-1: self.iNebTimeoutSeconds=None
		self.sDebugTraceFileName= setupSection["DebugTraceFileName"]
		self.bRunGULPOptimisationEvenWhereOutputFilesExist= int(setupSection["RunGULPOptimisationEvenWhereOutputFilesExist"])==1
		if not os.access(self.sPathToGulpExecutable,os.F_OK):
			print("Error, GULP executable not found. Check config [PathToGulpExecutable] path, currently:\n"+self.sPathToGulpExecutable)
			sys.exit(1)
		if self.sGulpRelaxEndpointLibrary!="" and not os.access(self.sGulpRelaxEndpointLibrary,os.F_OK):
			print("Error, GULP relax endpoint library not found. Check config [GulpRelaxEndpointLibrary] path, currently:\n"+self.sGulpRelaxEndpointLibrary)
			sys.exit(1)
	
def Main():
	"""Program entry point.
	"""
	if len(sys.argv)!=3:
		print("Syntax: python RunGulpNEB.py <configFilePath> <setupId>,\n"
			" where <configFile> is the configuration .ini file, and <setupId> identifies the specific settings in this file to apply.\n")
		sys.exit(1)
	appConfig= AppConfig()
	log=None
	report=None
	if appConfig.sDebugTraceFileName!="":
		log= Log(appConfig.sOutputBaseDir+appConfig.sDebugTraceFileName,Log.INFO)
	report= Log(appConfig.sOutputBaseDir+"report-GULP-NEB.txt",Log.INFO)
	report.Log("#Refcode\tbSuccess\tErrCode_Or_NumIterations\t1st_eV\tMin_eV\tMax_eV\tdMaxMin_eV\tLast_eV",report.INFO,bConsole=False)
	log.Info("RunGulpNEB.py, running (%s)" % strftime("%Y-%m-%d %H:%M:%S"),True)

	# scan output dir structure
	for sEntry in os.listdir(appConfig.sOutputDir):
		sHostGuestStepDirPath= appConfig.sOutputDir+sEntry
		sGulpHostGuestStepDirPath= sHostGuestStepDirPath+"/GULP/"
		if os.path.isdir(sHostGuestStepDirPath) and os.access(sGulpHostGuestStepDirPath,os.F_OK):
			try:
				RunGulpNEBCalculation(report,log,sGulpHostGuestStepDirPath,sEntry,appConfig)
			except KeyboardInterrupt: # don't catch Ctrl+C in long loop-bodies
				raise
			except SystemExit: # likewise sys.exit(0)
				raise
			except AppError as appError: # don't quit if we know this error
				if log is not None: log.Error(appError.args[0],True)
			except:
				if log is not None: log.Error(traceback.format_exc(),True) # or last as string
				raise

if __name__=="__main__": Main()
Execute GULP NEB calculations in work directory (as necessary) and report resultant barrier energies. # access, rename, chdir, listdir, path. # call # logging Run GULP NEB calculation as necessary; log whether this was successful, and if so, include barrier energies. 	The NEB calculation does not need to run again if the NEB.gout file already exists. 	[report]: PyLib.Log, report object. 	[log]: PyLib.Log, log object.  	[sGulpHostGuestStepDirPath]: string, [host-guest-step]/GULP/ directory path. 	[sHostGuestStepName]: string, [host-guest-step] name. 	[appConfig]: AppConfig, configuration object. #	if log is not None: #		log.Info("Start [%s] at %s" % (sHostGuestStepName,strftime("%Y-%m-%d %H:%M:%S")),True) #if log is not None: log.Info("Skip - no NEB.gin",True) # specify 'force norm' tolerance of 0.001 (as GULP manual), else uses default of 0.0001, which we find to be too strict. Application configuration object. Initialise from command line and config.ini file; quit application on fatal error. Program entry point. # scan output dir structure # don't catch Ctrl+C in long loop-bodies # likewise sys.exit(0) # don't quit if we know this error # or last as string