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