|
| 1 | +from manager_tools import resolve_status, update_attributes |
| 2 | +from crystal import CrystalReader |
| 3 | +from propertiesreader import PropertiesReader |
| 4 | +from autorunner import RunnerPBS |
| 5 | +import os |
| 6 | +import pickle as pkl |
| 7 | +import shutil as sh |
| 8 | +import crystal2qmc |
| 9 | +from autopaths import paths |
| 10 | + |
| 11 | +class CrystalManager: |
| 12 | + """ Internal class managing process of running a DFT job though crystal. |
| 13 | + Has authority over file names associated with this task.""" |
| 14 | + def __init__(self,writer,runner,creader=None,name='crystal_run',path=None, |
| 15 | + preader=None,prunner=None, |
| 16 | + trylev=False,bundle=False,max_restarts=5): |
| 17 | + ''' CrystalManager manages the writing of a Crystal input file, it's running, and keeping track of the results. |
| 18 | + Args: |
| 19 | + writer (PySCFWriter): writer for input. |
| 20 | + reader (PySCFReader): to read PySCF output. |
| 21 | + runner (runner object): to run job. |
| 22 | + creader (CrystalReader): Reads the crystal results, (None implies use default reader). |
| 23 | + preader (PropertiesReader): Reads properties results, if any (None implies use default reader). |
| 24 | + prunner (runner object): run properties if needed (None implies use same runner as crystal). |
| 25 | + name (str): identifier for this job. This names the files associated with run. |
| 26 | + trylev (bool): When restarting use LEVSHIFT option to encourage convergence, then do a rerun without LEVSHIFT. |
| 27 | + bundle (bool): Whether you'll use a bundling tool to run these jobs. |
| 28 | + max_restarts (int): maximum number of times you'll allow restarting before giving up (and manually intervening). |
| 29 | + ''' |
| 30 | + # Where to save self. |
| 31 | + self.name=name |
| 32 | + self.pickle="%s.pkl"%self.name |
| 33 | + |
| 34 | + # Ensure path is set up correctly. |
| 35 | + if path is None: |
| 36 | + path=os.path.getcwd() |
| 37 | + if path[-1]!='/': path+='/' |
| 38 | + self.path=path |
| 39 | + |
| 40 | + self.logname="%s@%s"%(self.__class__.__name__,self.path+self.name) |
| 41 | + |
| 42 | + #print(self.logname,": initializing") |
| 43 | + |
| 44 | + # Handle reader and runner defaults. |
| 45 | + self.writer=writer |
| 46 | + if creader is None: self.creader=CrystalReader() |
| 47 | + else: self.creader=creader |
| 48 | + if preader is None: self.preader=PropertiesReader() |
| 49 | + else: self.preader=preader |
| 50 | + if prunner is None: self.prunner=runner |
| 51 | + else: self.prunner=prunner |
| 52 | + if runner is None: self.runner=RunnerPBS() |
| 53 | + else: self.runner=runner |
| 54 | + |
| 55 | + # Internal. |
| 56 | + self.crysinpfn=self.name |
| 57 | + self.propinpfn=self.name+'.prop' |
| 58 | + self.crysoutfn=self.crysinpfn+'.o' |
| 59 | + self.propoutfn=self.propinpfn+'.o' |
| 60 | + self.restarts=0 |
| 61 | + self._runready=False |
| 62 | + self.scriptfile=None |
| 63 | + self.completed=False |
| 64 | + self.bundle=bundle |
| 65 | + self.qwfiles={ |
| 66 | + 'kpoints':[], |
| 67 | + 'basis':'', |
| 68 | + 'jastrow2':'', |
| 69 | + 'orbplot':{}, |
| 70 | + 'orb':{}, |
| 71 | + 'sys':{}, |
| 72 | + 'slater':{} |
| 73 | + } |
| 74 | + |
| 75 | + # Smart error detection. |
| 76 | + self.trylev=trylev |
| 77 | + self.max_restarts=max_restarts |
| 78 | + self.savebroy=[] |
| 79 | + self.lev=False |
| 80 | + |
| 81 | + # Handle old results if present. |
| 82 | + if os.path.exists(self.path+self.pickle): |
| 83 | + #print(self.logname,": rebooting old manager.") |
| 84 | + old=pkl.load(open(self.path+self.pickle,'rb')) |
| 85 | + self.recover(old) |
| 86 | + |
| 87 | + # Update the file. |
| 88 | + if not os.path.exists(self.path): os.mkdir(self.path) |
| 89 | + with open(self.path+self.pickle,'wb') as outf: |
| 90 | + pkl.dump(self,outf) |
| 91 | + |
| 92 | + #------------------------------------------------ |
| 93 | + def recover(self,other): |
| 94 | + ''' Recover old class by copying over data. Retain variables from old that may change final answer.''' |
| 95 | + # Practically speaking, the run will preserve old `take_keys` and allow new changes to `skip_keys`. |
| 96 | + # This is because you are taking the attributes from the older instance, and copying into the new instance. |
| 97 | + |
| 98 | + update_attributes(copyto=self,copyfrom=other, |
| 99 | + skip_keys=['writer','runner','creader','preader','prunner','lev','savebroy', |
| 100 | + 'path','logname','name', |
| 101 | + 'trylev','max_restarts','bundle'], |
| 102 | + take_keys=['restarts','completed','qwfiles']) |
| 103 | + |
| 104 | + # Update queue settings, but save queue information. |
| 105 | + update_attributes(copyto=self.runner,copyfrom=other.runner, |
| 106 | + skip_keys=['queue','walltime','np','nn','jobname'], |
| 107 | + take_keys=['queueid']) |
| 108 | + update_attributes(copyto=self.prunner,copyfrom=other.prunner, |
| 109 | + skip_keys=['queue','walltime','np','nn','jobname'], |
| 110 | + take_keys=['queueid']) |
| 111 | + |
| 112 | + update_attributes(copyto=self.creader,copyfrom=other.creader, |
| 113 | + skip_keys=[], |
| 114 | + take_keys=['completed','output']) |
| 115 | + |
| 116 | + update_attributes(copyto=self.preader,copyfrom=other.preader, |
| 117 | + skip_keys=[], |
| 118 | + take_keys=['completed','output']) |
| 119 | + |
| 120 | + updated=update_attributes(copyto=self.writer,copyfrom=other.writer, |
| 121 | + skip_keys=['maxcycle','edifftol'], |
| 122 | + take_keys=['completed','modisymm','restart','guess_fort','_elements']) |
| 123 | + if updated: |
| 124 | + self.writer.completed=False |
| 125 | + |
| 126 | + #---------------------------------------- |
| 127 | + def nextstep(self): |
| 128 | + ''' Determine and perform the next step in the calculation.''' |
| 129 | + self.recover(pkl.load(open(self.path+self.pickle,'rb'))) |
| 130 | + |
| 131 | + print(self.logname,": next step.") |
| 132 | + cwd=os.getcwd() |
| 133 | + os.chdir(self.path) |
| 134 | + |
| 135 | + # Generate input files. |
| 136 | + if not self.writer.completed: |
| 137 | + if self.writer.guess_fort is not None: |
| 138 | + sh.copy(self.writer.guess_fort,'fort.20') |
| 139 | + with open(self.crysinpfn,'w') as f: |
| 140 | + self.writer.write_crys_input(self.crysinpfn) |
| 141 | + with open(self.propinpfn,'w') as f: |
| 142 | + self.writer.write_prop_input(self.propinpfn) |
| 143 | + |
| 144 | + # Check on the CRYSTAL run |
| 145 | + status=resolve_status(self.runner,self.creader,self.crysoutfn) |
| 146 | + print(self.logname,": status= %s"%(status)) |
| 147 | + |
| 148 | + if status=="not_started": |
| 149 | + self.runner.add_command("cp %s INPUT"%self.crysinpfn) |
| 150 | + self.runner.add_task("%s &> %s"%(paths['Pcrystal'],self.crysoutfn)) |
| 151 | + |
| 152 | + elif status=="ready_for_analysis": |
| 153 | + #This is where we (eventually) do error correction and resubmits |
| 154 | + status=self.creader.collect(self.crysoutfn) |
| 155 | + print(self.logname,": status %s"%status) |
| 156 | + if status=='killed': |
| 157 | + if self.restarts >= self.max_restarts: |
| 158 | + print(self.logname,": restarts exhausted (%d previous restarts). Human intervention required."%self.restarts) |
| 159 | + else: |
| 160 | + print(self.logname,": attempting restart (%d previous restarts)."%self.restarts) |
| 161 | + self.writer.restart=True |
| 162 | + if self.trylev: |
| 163 | + print(self.logname,": trying LEVSHIFT.") |
| 164 | + self.writer.levshift=[10,1] # No mercy. |
| 165 | + self.savebroy=deepcopy(self.writer.broyden) |
| 166 | + self.writer.broyden=[] |
| 167 | + self.lev=True |
| 168 | + sh.copy(self.crysinpfn,"%d.%s"%(self.restarts,self.crysinpfn)) |
| 169 | + sh.copy(self.crysoutfn,"%d.%s"%(self.restarts,self.crysoutfn)) |
| 170 | + sh.copy('fort.79',"%d.fort.79"%(self.restarts)) |
| 171 | + self.writer.guess_fort='./fort.79' |
| 172 | + sh.copy(self.writer.guess_fort,'fort.20') |
| 173 | + self.writer.write_crys_input(self.crysinpfn) |
| 174 | + sh.copy(self.crysinpfn,'INPUT') |
| 175 | + self.runner.add_task("%s &> %s"%(paths['Pcrystal'],self.crysoutfn)) |
| 176 | + self.restarts+=1 |
| 177 | + elif status=='done' and self.lev: |
| 178 | + # We used levshift to converge. Now let's restart to be sure. |
| 179 | + print("Recovering from LEVSHIFTer.") |
| 180 | + self.writer.restart=True |
| 181 | + self.writer.levshift=[] |
| 182 | + self.creader.completed=False |
| 183 | + self.lev=False |
| 184 | + sh.copy(self.crysinpfn,"%d.%s"%(self.restarts,self.crysinpfn)) |
| 185 | + sh.copy(self.crysoutfn,"%d.%s"%(self.restarts,self.crysoutfn)) |
| 186 | + sh.copy('fort.79',"%d.fort.79"%(self.restarts)) |
| 187 | + self.writer.guess_fort='./fort.79' |
| 188 | + sh.copy(self.writer.guess_fort,'fort.20') |
| 189 | + self.writer.write_crys_input(self.crysinpfn) |
| 190 | + sh.copy(self.crysinpfn,'INPUT') |
| 191 | + self.runner.add_task("%s &> %s"%(paths['Pcrystal'],self.crysoutfn)) |
| 192 | + self.restarts+=1 |
| 193 | + |
| 194 | + # Ready for bundler or else just submit the jobs as needed. |
| 195 | + if self.bundle: |
| 196 | + self.scriptfile="%s.run"%self.name |
| 197 | + self.bundle_ready=self.runner.script(self.scriptfile) |
| 198 | + else: |
| 199 | + qsubfile=self.runner.submit(self.path.replace('/','-')+self.name) |
| 200 | + |
| 201 | + self.completed=self.creader.completed |
| 202 | + |
| 203 | + # Update the file. |
| 204 | + with open(self.pickle,'wb') as outf: |
| 205 | + pkl.dump(self,outf) |
| 206 | + os.chdir(cwd) |
| 207 | + |
| 208 | + #---------------------------------------- |
| 209 | + def collect(self): |
| 210 | + ''' Call the collect routine for readers.''' |
| 211 | + print(self.logname,": collecting results.") |
| 212 | + self.creader.collect(self.path+self.crysoutfn) |
| 213 | + |
| 214 | + # Update the file. |
| 215 | + with open(self.path+self.pickle,'wb') as outf: |
| 216 | + pkl.dump(self,outf) |
| 217 | + |
| 218 | + #------------------------------------------------ |
| 219 | + def script(self,jobname=None): |
| 220 | + ''' Script execution lines for a bundler to pick up and run.''' |
| 221 | + if jobname is None: jobname=self.runner.jobname |
| 222 | + self.scriptfile="%s.run"%jobname |
| 223 | + self._runready=self.runner.script(self.scriptfile) |
| 224 | + |
| 225 | + #------------------------------------------------ |
| 226 | + def submit(self,jobname=None): |
| 227 | + ''' Submit the runner's job to the queue. ''' |
| 228 | + qsubfile=self.runner.submit(jobname) |
| 229 | + return qsubfile |
| 230 | + |
| 231 | + #---------------------------------------- |
| 232 | + def to_json(self): |
| 233 | + raise NotImplementedError |
| 234 | + |
| 235 | + #---------------------------------------- |
| 236 | + def write_summary(self): |
| 237 | + self.creader.write_summary() |
| 238 | + |
| 239 | + #------------------------------------------------ |
| 240 | + def export_qwalk(self): |
| 241 | + ''' Export QWalk input files into current directory.''' |
| 242 | + self.recover(pkl.load(open(self.path+self.pickle,'rb'))) |
| 243 | + |
| 244 | + ready=False |
| 245 | + if len(self.qwfiles['slater'])==0: |
| 246 | + self.nextstep() |
| 247 | + |
| 248 | + if not self.completed: |
| 249 | + return False |
| 250 | + |
| 251 | + cwd=os.getcwd() |
| 252 | + os.chdir(self.path) |
| 253 | + |
| 254 | + print(self.logname,": %s attempting to generate QWalk files."%self.name) |
| 255 | + |
| 256 | + # Check on the properties run |
| 257 | + status=resolve_status(self.prunner,self.preader,self.propoutfn) |
| 258 | + print(self.logname,": properties status= %s"%(status)) |
| 259 | + if status=='not_started': |
| 260 | + ready=False |
| 261 | + self.prunner.add_command("cp %s INPUT"%self.propinpfn) |
| 262 | + self.prunner.add_task("%s &> %s"%(paths['Pproperties'],self.propoutfn)) |
| 263 | + |
| 264 | + if self.bundle: |
| 265 | + self.scriptfile="%s.run"%self.name |
| 266 | + self.bundle_ready=self.prunner.script(self.scriptfile,self.driverfn) |
| 267 | + else: |
| 268 | + qsubfile=self.runner.submit(self.path.replace('/','-')+self.name) |
| 269 | + elif status=='ready_for_analysis': |
| 270 | + self.preader.collect(self.propoutfn) |
| 271 | + |
| 272 | + if self.preader.completed: |
| 273 | + ready=True |
| 274 | + print(self.logname,": converting crystal to QWalk input now.") |
| 275 | + self.qwfiles=crystal2qmc.convert_crystal(base=self.name,propoutfn=self.propoutfn) |
| 276 | + else: |
| 277 | + ready=False |
| 278 | + print(self.logname,": conversion postponed because properties is not finished.") |
| 279 | + |
| 280 | + os.chdir(cwd) |
| 281 | + else: |
| 282 | + ready=True |
| 283 | + |
| 284 | + with open(self.path+self.pickle,'wb') as outf: |
| 285 | + pkl.dump(self,outf) |
| 286 | + |
| 287 | + return ready |
| 288 | + |
| 289 | + #---------------------------------------- |
| 290 | + def status(self): |
| 291 | + if self.completed: |
| 292 | + return 'ok' |
| 293 | + else: |
| 294 | + return 'not_finished' |
| 295 | + |
0 commit comments