qmcpack/nexus/lib/quantum_package.py

416 lines
16 KiB
Python

##################################################################
## (c) Copyright 2018- by Jaron T. Krogel ##
##################################################################
#====================================================================#
# quantum_package.py #
# Nexus interface for the Quantum Package simulation code. #
# #
# Content summary: #
# QuantumPackage #
# Simulation class for Quantum Package. #
# #
# generate_quantum_package #
# User-facing function to generate Quantum Package simulation #
# objects. #
#====================================================================#
import os
from generic import obj
from execute import execute
from nexus_base import nexus_core
from simulation import Simulation
from quantum_package_input import QuantumPackageInput,generate_quantum_package_input,read_qp_value
from quantum_package_analyzer import QuantumPackageAnalyzer
from gamess import Gamess
from developer import ci
class QuantumPackage(Simulation):
input_type = QuantumPackageInput
analyzer_type = QuantumPackageAnalyzer
generic_identifier = 'qp'
infile_extension = '.ezfio'
application = 'qp_run'
application_properties = set(['serial','mpi'])
application_results = set(['orbitals'])
allow_overlapping_files = True
qprc = None
slave_partners = obj(
scf = 'scf',
fci = 'fci',
)
@staticmethod
def settings(qprc=None):
# path to quantum_package.rc file
QuantumPackage.qprc = qprc
if qprc is not None and not nexus_core.status_only:
if not isinstance(qprc,str):
QuantumPackage.class_error('settings input "qprc" must be a path\nreceived type: {0}\nwith value: {1}'.format(qprc.__class__.__name__,qprc))
elif not os.path.exists(qprc):
QuantumPackage.class_error('quantum_package.rc file does not exist\nfile path provided via "qprc" in settings\nfile path: {0}'.format(qprc))
#end if
#end if
#end def settings
@staticmethod
def restore_default_settings():
QuantumPackage.qprc = None
#end def restore_default_settings
def pre_init(self):
prefix = self.input.run_control.prefix
self.infile = prefix + self.infile_extension
#end def pre_init
def post_init(self):
qprc = QuantumPackage.qprc
if qprc is None:
self.error('cannot run quantum package\nplease provide path to quantum_package.rc in settings via argument "qprc"')
#end if
self.job.presub += '\nsource {0}\n'.format(os.path.abspath(qprc))
#end def post_init
def write_prep(self):
# write an ascii representation of the input changes
infile = self.identifier+'.in'
infile = os.path.join(self.locdir,infile)
f = open(infile,'w')
s = self.input.delete_optional('structure',None)
f.write(str(self.input))
if s is not None:
self.input.structure = s
#end if
f.close()
# copy ezfio directory from dependencies
qp_dirs = []
for dep in self.dependencies:
dsim = dep.sim
if isinstance(dsim,QuantumPackage):
d_ezfio = os.path.join(dsim.locdir,dsim.infile)
s_ezfio = os.path.join(self.locdir,self.infile)
d_ezfio = os.path.abspath(d_ezfio)
s_ezfio = os.path.abspath(s_ezfio)
sync_record = os.path.join(self.locdir,self.identifier+'.sync_record')
if s_ezfio!=d_ezfio:
qp_dirs.append(d_ezfio)
if not os.path.exists(sync_record):
if not os.path.exists(s_ezfio):
os.makedirs(s_ezfio)
#end if
command = 'rsync -av {0}/ {1}/'.format(d_ezfio,s_ezfio)
out,err,rc = execute(command)
if rc!=0:
self.warn('rsync of ezfio directory failed\nall runs depending on this one will be blocked\nsimulation identifier: {0}\nlocal directory: {1}\nattempted rsync command: {2}'.format(self.identifier,self.locdir,command))
self.failed = True
self.block_dependents()
else:
f = open(sync_record,'w')
f.write(command+'\n')
f.close()
execute('qp_edit -c {0}'.format(d_ezfio))
#end if
#end if
#end if
#end if
#end for
if len(qp_dirs)>1:
qpd = ''
for d in qp_dirs:
qpd += d+'\n'
#end for
self.error('quantum package run depends on multiple others with distinct ezfio directories\ncannot determine which run to copy ezfio directory from\nezfio directories from prior runs:\n{0}'.format(qpd))
#end if
#end def write_prep
def check_result(self,result_name,sim):
calculating_result = False
rc = self.input.run_control
if result_name=='orbitals':
calculating_result = rc.run_type=='save_for_qmcpack'
calculating_result |= rc.save_for_qmcpack
#end if
return calculating_result
#end def check_result
def get_result(self,result_name,sim):
result = obj()
rc = self.input.run_control
if result_name=='orbitals':
if rc.run_type=='save_for_qmcpack':
result.outfile = os.path.join(self.locdir,self.outfile)
elif rc.save_for_qmcpack:
result.outfile = os.path.join(self.locdir,'{0}_savewf.out'.format(self.identifier))
else:
self.error("cannot get orbitals\ntracking of save_for_qmcpack is somehow corrupted\nthis is a developer error")
#end if
else:
self.error('ability to get result '+result_name+' has not been implemented')
#end if
return result
#end def get_result
def incorporate_result(self,result_name,result,sim):
not_implemented = False
if isinstance(sim,Gamess):
if result_name=='orbitals':
loc_file = self.input.run_control.prefix
loc_out = os.path.join(self.locdir,loc_file)
gms_out = result.outfile
command = 'cp {0} {1}'.format(gms_out,loc_out)
out,err,rc = execute(command)
if rc!=0:
self.warn('copying GAMESS output failed\nall runs depending on this one will be blocked\nsimulation identifier: {0}\nlocal directory: {1}\nattempted command: {2}'.format(self.identifier,self.locdir,command))
self.failed = True
self.block_dependents()
#end if
command = 'qp_convert_output_to_ezfio '+loc_file
cwd = os.getcwd()
os.chdir(self.locdir)
out,err,rc = execute(command)
os.chdir(cwd)
if rc!=0:
self.warn('creation of ezfio file from GAMESS output failed\nall runs depending on this one will be blocked\nsimulation identifier: {0}\nlocal directory: {1}\nattempted command: {2}'.format(self.identifier,self.locdir,command))
self.failed = True
self.block_dependents()
#end if
else:
not_implemented = True
#end if
else:
not_implemented = True
#end if
if not_implemented:
self.error('ability to incorporate result "{}" from {} has not been implemented',result_name,sim.__class__.__name__)
#end if
#end def incorporate_result
def attempt_files(self):
return (self.outfile,self.errfile)
#end def attempt_files
def check_sim_status(self):
# get the run type
input = self.input
rc = self.input.run_control
scf = rc.run_type=='scf'
sel_ci = rc.run_type=='fci'
# assess successful completion of the run
# currently a check only exists for HF/SCF runs
# more sophisticated checks can be added in the future
failed = False
if scf:
outfile = os.path.join(self.locdir,self.outfile)
f = open(outfile,'r')
output = f.read()
f.close()
hf_not_converged = '* SCF energy' not in output
failed |= hf_not_converged
#end if
self.failed = failed
self.finished = self.job.finished
# check to see if the job needs to be restarted
conv_dets = 'converge_dets' in rc and rc.converge_dets
n_det_max = input.get('n_det_max')
if sel_ci and conv_dets and n_det_max is not None:
n_det = None
n_det_path = os.path.join(self.locdir,self.infile,'determinants/n_det')
if os.path.exists(n_det_path):
n_det = read_qp_value(n_det_path)
if isinstance(n_det,int) and n_det<n_det_max:
self.save_attempt()
input.set(read_wf=True)
self.reset_indicators()
#end if
#end if
#end if
#end def check_sim_status
def get_output_files(self):
output_files = []
return output_files
#end def get_output_files
def get_slave(self):
rc = self.input.run_control
sp = QuantumPackage.slave_partners
slave = None
if 'slave' in rc:
slave = rc.slave
elif rc.run_type in sp:
slave = sp[rc.run_type]
#end if
return slave
#end def get_slave
def app_command(self):
# get run controls
input = self.input
rc = input.run_control
# make the basic app command, no splitting etc
run_type = rc.run_type
app_command = self.app_name+' '+run_type+' '+self.infile
# prepare local vars in case splitting or other tricks are needed
fc = ''
job = self.job
# add cis-loop runs if requested
if 'cis_loop' in rc:
nloop = 0
if isinstance(rc.cis_loop,bool) and rc.cis_loop:
nloop = 2
else:
nloop = rc.cis_loop
#end for
if nloop>0:
jloop = job.clone()
fc+='\n'
for n in range(nloop):
jloop.app_command = self.app_name+' cis '+self.infile
fc += jloop.run_command()+' >{0}_{1}.out 2>{0}_{1}.err\n'.format(self.identifier,n)
jloop.app_command = self.app_name+' save_natorb '+self.infile
fc += jloop.run_command()+'\n'
#end for
fc+='\n'
integrals = [
'ao_one_e_ints/io_ao_one_e_integrals',
'mo_one_e_ints/io_mo_one_e_integrals',
'ao_two_e_ints/io_ao_two_e_integrals',
'mo_two_e_ints/io_mo_two_e_integrals',
]
cl = ''
for integral in integrals:
isec,ivar = integral.split('/')
if input.present(ivar):
val = input.delete(ivar)
cl += 'echo "{0}" > {1}/{2}\n'.format(val,self.infile,integral)
#end if
#end for
if len(cl)>0:
fc+=cl+'\n'
#end if
#end if
#end if
# check for post-processing operations and save the job in current state
postprocessors = ['save_natorb',
'four_idx_transform',
'save_for_qmcpack']
postprocess = obj()
jpost = None
for pp in postprocessors:
if pp in rc and rc[pp]:
postprocess[pp] = True
if jpost is None:
jpost = job.clone()
#end if
else:
postprocess[pp] = False
#end if
#end for
# perform master-slave job splitting if necessary
slave = self.get_slave()
split_nodes = job.nodes is not None and job.nodes>1 and job.full_command is None
split_nodes &= slave is not None
if split_nodes:
slave_command = self.app_name+' -slave {0} {1}'.format(slave,self.infile)
outfile = self.outfile
errfile = self.errfile
prefix,ext = outfile.split('.',1)
slave_outfile = prefix+'_slave.'+ext
prefix,ext = errfile.split('.',1)
slave_errfile = prefix+'_slave.'+ext
job.divert_out_err()
job1,job2 = job.split_nodes(1)
job1.app_command = app_command
job2.app_command = slave_command
fc += job1.run_command()+' >{0} 2>{1}&\n'.format(outfile,errfile)
fc += 'sleep {0}\n'.format(self.input.run_control.sleep)
fc += job2.run_command()+' >{0} 2>{1}\n'.format(slave_outfile,slave_errfile)
if 'fci' in slave and not input.present('distributed_davidson'):
input.set(distributed_davidson=True)
#end if
elif len(fc)>0 or jpost is not None:
job.divert_out_err()
job.app_command = app_command
fc += job.run_command()+' >{0} 2>{1}\n'.format(self.outfile,self.errfile)
#end if
if postprocess.save_natorb:
jno = jpost.serial_clone()
fc += '\n'
jno.app_command = self.app_name+' save_natorb '+self.infile
fc += jno.run_command()+' >{0}_natorb.out 2>{0}_natorb.err\n'.format(self.identifier)
#end if
if postprocess.four_idx_transform:
jfit = jpost.serial_clone()
fc += '\n'
fc += 'echo "Write" > {}/mo_two_e_ints/io_mo_two_e_integrals\n'.format(self.infile)
jfit.app_command = self.app_name+' four_idx_transform '+self.infile
fc += jfit.run_command()+' >{0}_fit.out 2>{0}_fit.err\n'.format(self.identifier)
#end if
if postprocess.save_for_qmcpack:
jsq = jpost.serial_clone()
fc += '\n'
jsq.app_command = self.app_name+' save_for_qmcpack '+self.infile
fc += jsq.run_command()+' >{0}_savewf.out 2>{0}_savewf.err\n'.format(self.identifier)
#end if
if len(fc)>0:
job.full_command = fc
#end if
return app_command
#end def app_command
#end class QuantumPackage
def generate_quantum_package(**kwargs):
sim_args,inp_args = QuantumPackage.separate_inputs(kwargs)
if not 'input' in sim_args:
if 'input_type' in inp_args:
input_type = inp_args.input_type
del inp_args.input_type
#end if
if 'prefix' not in inp_args and 'identifier' in sim_args:
inp_args['prefix'] = sim_args['identifier']
#end if
sim_args.input = generate_quantum_package_input(**inp_args)
#end if
qp = QuantumPackage(**sim_args)
return qp
#end def generate_quantum_package