1################################################################## 2## (c) Copyright 2018- by Jaron T. Krogel ## 3################################################################## 4 5 6#====================================================================# 7# quantum_package.py # 8# Nexus interface for the Quantum Package simulation code. # 9# # 10# Content summary: # 11# QuantumPackage # 12# Simulation class for Quantum Package. # 13# # 14# generate_quantum_package # 15# User-facing function to generate Quantum Package simulation # 16# objects. # 17#====================================================================# 18 19 20import os 21from generic import obj 22from execute import execute 23from nexus_base import nexus_core 24from simulation import Simulation 25from quantum_package_input import QuantumPackageInput,generate_quantum_package_input,read_qp_value 26from quantum_package_analyzer import QuantumPackageAnalyzer 27from gamess import Gamess 28from developer import ci 29 30 31 32class QuantumPackage(Simulation): 33 input_type = QuantumPackageInput 34 analyzer_type = QuantumPackageAnalyzer 35 generic_identifier = 'qp' 36 infile_extension = '.ezfio' 37 application = 'qp_run' 38 application_properties = set(['serial','mpi']) 39 application_results = set(['orbitals']) 40 41 allow_overlapping_files = True 42 43 qprc = None 44 45 slave_partners = obj( 46 scf = 'scf', 47 fci = 'fci', 48 ) 49 50 @staticmethod 51 def settings(qprc=None): 52 # path to quantum_package.rc file 53 QuantumPackage.qprc = qprc 54 if qprc is not None and not nexus_core.status_only: 55 if not isinstance(qprc,str): 56 QuantumPackage.class_error('settings input "qprc" must be a path\nreceived type: {0}\nwith value: {1}'.format(qprc.__class__.__name__,qprc)) 57 elif not os.path.exists(qprc): 58 QuantumPackage.class_error('quantum_package.rc file does not exist\nfile path provided via "qprc" in settings\nfile path: {0}'.format(qprc)) 59 #end if 60 #end if 61 #end def settings 62 63 @staticmethod 64 def restore_default_settings(): 65 QuantumPackage.qprc = None 66 #end def restore_default_settings 67 68 def pre_init(self): 69 prefix = self.input.run_control.prefix 70 self.infile = prefix + self.infile_extension 71 #end def pre_init 72 73 74 def post_init(self): 75 qprc = QuantumPackage.qprc 76 if qprc is None: 77 self.error('cannot run quantum package\nplease provide path to quantum_package.rc in settings via argument "qprc"') 78 #end if 79 self.job.presub += '\nsource {0}\n'.format(os.path.abspath(qprc)) 80 #end def post_init 81 82 83 def write_prep(self): 84 # write an ascii representation of the input changes 85 infile = self.identifier+'.in' 86 infile = os.path.join(self.locdir,infile) 87 f = open(infile,'w') 88 s = self.input.delete_optional('structure',None) 89 f.write(str(self.input)) 90 if s is not None: 91 self.input.structure = s 92 #end if 93 f.close() 94 95 # copy ezfio directory from dependencies 96 qp_dirs = [] 97 for dep in self.dependencies: 98 dsim = dep.sim 99 if isinstance(dsim,QuantumPackage): 100 d_ezfio = os.path.join(dsim.locdir,dsim.infile) 101 s_ezfio = os.path.join(self.locdir,self.infile) 102 d_ezfio = os.path.abspath(d_ezfio) 103 s_ezfio = os.path.abspath(s_ezfio) 104 sync_record = os.path.join(self.locdir,self.identifier+'.sync_record') 105 if s_ezfio!=d_ezfio: 106 qp_dirs.append(d_ezfio) 107 if not os.path.exists(sync_record): 108 if not os.path.exists(s_ezfio): 109 os.makedirs(s_ezfio) 110 #end if 111 command = 'rsync -av {0}/ {1}/'.format(d_ezfio,s_ezfio) 112 out,err,rc = execute(command) 113 if rc!=0: 114 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)) 115 self.failed = True 116 self.block_dependents() 117 else: 118 f = open(sync_record,'w') 119 f.write(command+'\n') 120 f.close() 121 execute('qp_edit -c {0}'.format(d_ezfio)) 122 #end if 123 #end if 124 #end if 125 #end if 126 #end for 127 if len(qp_dirs)>1: 128 qpd = '' 129 for d in qp_dirs: 130 qpd += d+'\n' 131 #end for 132 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)) 133 #end if 134 #end def write_prep 135 136 137 def check_result(self,result_name,sim): 138 calculating_result = False 139 rc = self.input.run_control 140 if result_name=='orbitals': 141 calculating_result = rc.run_type=='save_for_qmcpack' 142 calculating_result |= rc.save_for_qmcpack 143 #end if 144 return calculating_result 145 #end def check_result 146 147 148 def get_result(self,result_name,sim): 149 result = obj() 150 rc = self.input.run_control 151 if result_name=='orbitals': 152 if rc.run_type=='save_for_qmcpack': 153 result.outfile = os.path.join(self.locdir,self.outfile) 154 elif rc.save_for_qmcpack: 155 result.outfile = os.path.join(self.locdir,'{0}_savewf.out'.format(self.identifier)) 156 else: 157 self.error("cannot get orbitals\ntracking of save_for_qmcpack is somehow corrupted\nthis is a developer error") 158 #end if 159 else: 160 self.error('ability to get result '+result_name+' has not been implemented') 161 #end if 162 return result 163 #end def get_result 164 165 166 def incorporate_result(self,result_name,result,sim): 167 not_implemented = False 168 if isinstance(sim,Gamess): 169 if result_name=='orbitals': 170 loc_file = self.input.run_control.prefix 171 loc_out = os.path.join(self.locdir,loc_file) 172 gms_out = result.outfile 173 command = 'cp {0} {1}'.format(gms_out,loc_out) 174 out,err,rc = execute(command) 175 if rc!=0: 176 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)) 177 self.failed = True 178 self.block_dependents() 179 #end if 180 command = 'qp_convert_output_to_ezfio '+loc_file 181 cwd = os.getcwd() 182 os.chdir(self.locdir) 183 out,err,rc = execute(command) 184 os.chdir(cwd) 185 if rc!=0: 186 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)) 187 self.failed = True 188 self.block_dependents() 189 #end if 190 else: 191 not_implemented = True 192 #end if 193 else: 194 not_implemented = True 195 #end if 196 if not_implemented: 197 self.error('ability to incorporate result "{}" from {} has not been implemented',result_name,sim.__class__.__name__) 198 #end if 199 #end def incorporate_result 200 201 202 def attempt_files(self): 203 return (self.outfile,self.errfile) 204 #end def attempt_files 205 206 207 def check_sim_status(self): 208 # get the run type 209 input = self.input 210 rc = self.input.run_control 211 scf = rc.run_type=='scf' 212 sel_ci = rc.run_type=='fci' 213 214 # assess successful completion of the run 215 # currently a check only exists for HF/SCF runs 216 # more sophisticated checks can be added in the future 217 failed = False 218 if scf: 219 outfile = os.path.join(self.locdir,self.outfile) 220 f = open(outfile,'r') 221 output = f.read() 222 f.close() 223 hf_not_converged = '* SCF energy' not in output 224 failed |= hf_not_converged 225 #end if 226 self.failed = failed 227 self.finished = self.job.finished 228 229 # check to see if the job needs to be restarted 230 conv_dets = 'converge_dets' in rc and rc.converge_dets 231 n_det_max = input.get('n_det_max') 232 if sel_ci and conv_dets and n_det_max is not None: 233 n_det = None 234 n_det_path = os.path.join(self.locdir,self.infile,'determinants/n_det') 235 if os.path.exists(n_det_path): 236 n_det = read_qp_value(n_det_path) 237 if isinstance(n_det,int) and n_det<n_det_max: 238 self.save_attempt() 239 input.set(read_wf=True) 240 self.reset_indicators() 241 #end if 242 #end if 243 #end if 244 #end def check_sim_status 245 246 247 def get_output_files(self): 248 output_files = [] 249 return output_files 250 #end def get_output_files 251 252 253 def get_slave(self): 254 rc = self.input.run_control 255 sp = QuantumPackage.slave_partners 256 slave = None 257 if 'slave' in rc: 258 slave = rc.slave 259 elif rc.run_type in sp: 260 slave = sp[rc.run_type] 261 #end if 262 return slave 263 #end def get_slave 264 265 266 def app_command(self): 267 268 # get run controls 269 input = self.input 270 rc = input.run_control 271 272 # make the basic app command, no splitting etc 273 run_type = rc.run_type 274 app_command = self.app_name+' '+run_type+' '+self.infile 275 276 # prepare local vars in case splitting or other tricks are needed 277 fc = '' 278 job = self.job 279 280 # add cis-loop runs if requested 281 if 'cis_loop' in rc: 282 nloop = 0 283 if isinstance(rc.cis_loop,bool) and rc.cis_loop: 284 nloop = 2 285 else: 286 nloop = rc.cis_loop 287 #end for 288 if nloop>0: 289 jloop = job.clone() 290 fc+='\n' 291 for n in range(nloop): 292 jloop.app_command = self.app_name+' cis '+self.infile 293 fc += jloop.run_command()+' >{0}_{1}.out 2>{0}_{1}.err\n'.format(self.identifier,n) 294 jloop.app_command = self.app_name+' save_natorb '+self.infile 295 fc += jloop.run_command()+'\n' 296 #end for 297 fc+='\n' 298 integrals = [ 299 'ao_one_e_ints/io_ao_one_e_integrals', 300 'mo_one_e_ints/io_mo_one_e_integrals', 301 'ao_two_e_ints/io_ao_two_e_integrals', 302 'mo_two_e_ints/io_mo_two_e_integrals', 303 ] 304 cl = '' 305 for integral in integrals: 306 isec,ivar = integral.split('/') 307 if input.present(ivar): 308 val = input.delete(ivar) 309 cl += 'echo "{0}" > {1}/{2}\n'.format(val,self.infile,integral) 310 #end if 311 #end for 312 if len(cl)>0: 313 fc+=cl+'\n' 314 #end if 315 #end if 316 #end if 317 318 # check for post-processing operations and save the job in current state 319 postprocessors = ['save_natorb', 320 'four_idx_transform', 321 'save_for_qmcpack'] 322 postprocess = obj() 323 jpost = None 324 for pp in postprocessors: 325 if pp in rc and rc[pp]: 326 postprocess[pp] = True 327 if jpost is None: 328 jpost = job.clone() 329 #end if 330 else: 331 postprocess[pp] = False 332 #end if 333 #end for 334 335 # perform master-slave job splitting if necessary 336 slave = self.get_slave() 337 split_nodes = job.nodes is not None and job.nodes>1 and job.full_command is None 338 split_nodes &= slave is not None 339 if split_nodes: 340 slave_command = self.app_name+' -slave {0} {1}'.format(slave,self.infile) 341 outfile = self.outfile 342 errfile = self.errfile 343 prefix,ext = outfile.split('.',1) 344 slave_outfile = prefix+'_slave.'+ext 345 prefix,ext = errfile.split('.',1) 346 slave_errfile = prefix+'_slave.'+ext 347 348 job.divert_out_err() 349 job1,job2 = job.split_nodes(1) 350 job1.app_command = app_command 351 job2.app_command = slave_command 352 fc += job1.run_command()+' >{0} 2>{1}&\n'.format(outfile,errfile) 353 fc += 'sleep {0}\n'.format(self.input.run_control.sleep) 354 fc += job2.run_command()+' >{0} 2>{1}\n'.format(slave_outfile,slave_errfile) 355 356 if 'fci' in slave and not input.present('distributed_davidson'): 357 input.set(distributed_davidson=True) 358 #end if 359 elif len(fc)>0 or jpost is not None: 360 job.divert_out_err() 361 job.app_command = app_command 362 fc += job.run_command()+' >{0} 2>{1}\n'.format(self.outfile,self.errfile) 363 #end if 364 365 if postprocess.save_natorb: 366 jno = jpost.serial_clone() 367 fc += '\n' 368 jno.app_command = self.app_name+' save_natorb '+self.infile 369 fc += jno.run_command()+' >{0}_natorb.out 2>{0}_natorb.err\n'.format(self.identifier) 370 #end if 371 372 if postprocess.four_idx_transform: 373 jfit = jpost.serial_clone() 374 fc += '\n' 375 fc += 'echo "Write" > {}/mo_two_e_ints/io_mo_two_e_integrals\n'.format(self.infile) 376 jfit.app_command = self.app_name+' four_idx_transform '+self.infile 377 fc += jfit.run_command()+' >{0}_fit.out 2>{0}_fit.err\n'.format(self.identifier) 378 #end if 379 380 if postprocess.save_for_qmcpack: 381 jsq = jpost.serial_clone() 382 fc += '\n' 383 jsq.app_command = self.app_name+' save_for_qmcpack '+self.infile 384 fc += jsq.run_command()+' >{0}_savewf.out 2>{0}_savewf.err\n'.format(self.identifier) 385 #end if 386 387 if len(fc)>0: 388 job.full_command = fc 389 #end if 390 391 return app_command 392 #end def app_command 393 394#end class QuantumPackage 395 396 397 398def generate_quantum_package(**kwargs): 399 sim_args,inp_args = QuantumPackage.separate_inputs(kwargs) 400 401 if not 'input' in sim_args: 402 if 'input_type' in inp_args: 403 input_type = inp_args.input_type 404 del inp_args.input_type 405 #end if 406 if 'prefix' not in inp_args and 'identifier' in sim_args: 407 inp_args['prefix'] = sim_args['identifier'] 408 #end if 409 sim_args.input = generate_quantum_package_input(**inp_args) 410 #end if 411 qp = QuantumPackage(**sim_args) 412 413 return qp 414#end def generate_quantum_package 415 416