Coverage for /builds/ase/ase/ase/calculators/openmx/openmx.py: 27.33%

450 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3""" 

4 The ASE Calculator for OpenMX <http://www.openmx-square.org> 

5 A Python interface to the software package for nano-scale 

6 material simulations based on density functional theories. 

7 Copyright (C) 2017 Charles Thomas Johnson, Jae Hwan Shim and JaeJun Yu 

8 

9 This program is free software: you can redistribute it and/or modify 

10 it under the terms of the GNU Lesser General Public License as published by 

11 the Free Software Foundation, either version 2.1 of the License, or 

12 (at your option) any later version. 

13 

14 This program is distributed in the hope that it will be useful, 

15 but WITHOUT ANY WARRANTY; without even the implied warranty of 

16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17 GNU Lesser General Public License for more details. 

18 

19 You should have received a copy of the GNU Lesser General Public License 

20 along with ASE. If not, see <http://www.gnu.org/licenses/>. 

21 

22""" 

23 

24import os 

25import re 

26import subprocess 

27import time 

28import warnings 

29 

30import numpy as np 

31 

32from ase.calculators.calculator import ( 

33 Calculator, 

34 FileIOCalculator, 

35 all_changes, 

36 equal, 

37 kptdensity2monkhorstpack, 

38) 

39from ase.calculators.openmx.default_settings import default_dictionary 

40from ase.calculators.openmx.parameters import OpenMXParameters 

41from ase.calculators.openmx.reader import get_file_name, read_openmx 

42from ase.calculators.openmx.writer import write_openmx 

43from ase.config import cfg 

44from ase.geometry import cell_to_cellpar 

45 

46 

47def parse_omx_version(txt): 

48 """Parse version number from stdout header.""" 

49 match = re.search(r'Welcome to OpenMX\s+Ver\.\s+(\S+)', txt, re.M) 

50 return match.group(1) 

51 

52 

53class OpenMX(FileIOCalculator): 

54 """ 

55 Calculator interface to the OpenMX code. 

56 """ 

57 

58 implemented_properties = [ 

59 'free_energy', # Same value with energy 

60 'energy', 

61 'energies', 

62 'forces', 

63 'stress', 

64 'dipole', 

65 'chemical_potential', 

66 'magmom', 

67 'magmoms', 

68 'eigenvalues'] 

69 

70 default_parameters = OpenMXParameters() 

71 

72 default_pbs = { 

73 'processes': 1, 

74 'walltime': "10:00:00", 

75 'threads': 1, 

76 'nodes': 1 

77 } 

78 

79 default_mpi = { 

80 'processes': 1, 

81 'threads': 1 

82 } 

83 

84 default_output_setting = { 

85 'nohup': True, 

86 'debug': False 

87 } 

88 

89 def __init__(self, restart=None, 

90 ignore_bad_restart_file=FileIOCalculator._deprecated, 

91 label='./openmx', atoms=None, command=None, mpi=None, 

92 pbs=None, **kwargs): 

93 

94 # Initialize and put the default parameters. 

95 self.initialize_pbs(pbs) 

96 self.initialize_mpi(mpi) 

97 self.initialize_output_setting(**kwargs) 

98 

99 FileIOCalculator.__init__(self, restart, ignore_bad_restart_file, 

100 label, atoms, command, **kwargs) 

101 

102 def __getitem__(self, key): 

103 """Convenience method to retrieve a parameter as 

104 calculator[key] rather than calculator.parameters[key] 

105 

106 Parameters: 

107 -key : str, the name of the parameters to get. 

108 """ 

109 return self.parameters[key] 

110 

111 def __setitem__(self, key, value): 

112 self.parameters[key] = value 

113 

114 def initialize_output_setting(self, **kwargs): 

115 output_setting = {} 

116 self.output_setting = dict(self.default_output_setting) 

117 for key, value in kwargs.items(): 

118 if key in self.default_output_setting: 

119 output_setting[key] = value 

120 self.output_setting.update(output_setting) 

121 self.__dict__.update(self.output_setting) 

122 

123 def initialize_pbs(self, pbs): 

124 if pbs: 

125 self.pbs = dict(self.default_pbs) 

126 for key in pbs: 

127 if key not in self.default_pbs: 

128 allowed = ', '.join(list(self.default_pbs.keys())) 

129 raise TypeError('Unexpected keyword "{}" in "pbs" ' 

130 'dictionary. Must be one of: {}' 

131 .format(key, allowed)) 

132 # Put dictionary into python variable 

133 self.pbs.update(pbs) 

134 self.__dict__.update(self.pbs) 

135 else: 

136 self.pbs = None 

137 

138 def initialize_mpi(self, mpi): 

139 if mpi: 

140 self.mpi = dict(self.default_mpi) 

141 for key in mpi: 

142 if key not in self.default_mpi: 

143 allowed = ', '.join(list(self.default_mpi.keys())) 

144 raise TypeError('Unexpected keyword "{}" in "mpi" ' 

145 'dictionary. Must be one of: {}' 

146 .format(key, allowed)) 

147 # Put dictionary into python variable 

148 self.mpi.update(mpi) 

149 self.__dict__.update(self.mpi) 

150 else: 

151 self.mpi = None 

152 

153 def run(self): 

154 '''Check Which Running method we r going to use and run it''' 

155 if self.pbs is not None: 

156 run = self.run_pbs 

157 elif self.mpi is not None: 

158 run = self.run_mpi 

159 else: 

160 run = self.run_openmx 

161 run() 

162 

163 def run_openmx(self): 

164 def isRunning(process=None): 

165 ''' Check mpi is running''' 

166 return process.poll() is None 

167 runfile = get_file_name('.dat', self.label, absolute_directory=False) 

168 outfile = get_file_name('.log', self.label) 

169 olddir = os.getcwd() 

170 abs_dir = os.path.join(olddir, self.directory) 

171 try: 

172 os.chdir(abs_dir) 

173 if self.command is None: 

174 self.command = 'openmx' 

175 command = self.command + ' %s > %s' 

176 command = command % (runfile, outfile) 

177 self.prind(command) 

178 p = subprocess.Popen(command, shell=True, universal_newlines=True) 

179 self.print_file(file=outfile, running=isRunning, process=p) 

180 finally: 

181 os.chdir(olddir) 

182 self.prind("Calculation Finished") 

183 

184 def run_mpi(self): 

185 """ 

186 Run openmx using MPI method. If keyword `mpi` is declared, it will 

187 run. 

188 """ 

189 def isRunning(process=None): 

190 ''' Check mpi is running''' 

191 return process.poll() is None 

192 processes = self.processes 

193 threads = self.threads 

194 runfile = get_file_name('.dat', self.label, absolute_directory=False) 

195 outfile = get_file_name('.log', self.label) 

196 olddir = os.getcwd() 

197 abs_dir = os.path.join(olddir, self.directory) 

198 try: 

199 os.chdir(abs_dir) 

200 command = self.get_command(processes, threads, runfile, outfile) 

201 self.prind(command) 

202 p = subprocess.Popen(command, shell=True, universal_newlines=True) 

203 self.print_file(file=outfile, running=isRunning, process=p) 

204 finally: 

205 os.chdir(olddir) 

206 self.prind("Calculation Finished") 

207 

208 def run_pbs(self, prefix='test'): 

209 """ 

210 Execute the OpenMX using Plane Batch System. In order to use this, 

211 Your system should have Scheduler. PBS 

212 Basically, it does qsub. and wait until qstat signal shows c 

213 Super computer user 

214 """ 

215 nodes = self.nodes 

216 processes = self.processes 

217 

218 prefix = self.prefix 

219 olddir = os.getcwd() 

220 try: 

221 os.chdir(self.abs_directory) 

222 except AttributeError: 

223 os.chdir(self.directory) 

224 

225 def isRunning(jobNum=None, status='Q', qstat='qstat'): 

226 """ 

227 Check submitted job is still Running 

228 """ 

229 def runCmd(exe): 

230 p = subprocess.Popen(exe, stdout=subprocess.PIPE, 

231 stderr=subprocess.STDOUT, 

232 universal_newlines=True) 

233 while True: 

234 line = p.stdout.readline() 

235 if line != '': 

236 # the real code does filtering here 

237 yield line.rstrip() 

238 else: 

239 break 

240 jobs = runCmd('qstat') 

241 columns = None 

242 for line in jobs: 

243 if str(jobNum) in line: 

244 columns = line.split() 

245 self.prind(line) 

246 if columns is not None: 

247 return columns[-2] == status 

248 else: 

249 return False 

250 

251 inputfile = self.label + '.dat' 

252 outfile = self.label + '.log' 

253 

254 bashArgs = "#!/bin/bash \n cd $PBS_O_WORKDIR\n" 

255 jobName = prefix 

256 cmd = bashArgs + \ 

257 'mpirun -hostfile $PBS_NODEFILE openmx {} > {}'.format( 

258 inputfile, outfile) 

259 echoArgs = ["echo", f"$' {cmd}'"] 

260 qsubArgs = ["qsub", "-N", jobName, "-l", "nodes=%d:ppn=%d" % 

261 (nodes, processes), "-l", "walltime=" + self.walltime] 

262 wholeCmd = " ".join(echoArgs) + " | " + " ".join(qsubArgs) 

263 self.prind(wholeCmd) 

264 out = subprocess.Popen(wholeCmd, shell=True, 

265 stdout=subprocess.PIPE, universal_newlines=True) 

266 out = out.communicate()[0] 

267 jobNum = int(re.match(r'(\d+)', out.split()[0]).group(1)) 

268 

269 self.prind('Queue number is ' + str(jobNum) + 

270 '\nWaiting for the Queue to start') 

271 while isRunning(jobNum, status='Q'): 

272 time.sleep(5) 

273 self.prind('.') 

274 self.prind('Start Calculating') 

275 self.print_file(file=outfile, running=isRunning, 

276 jobNum=jobNum, status='R', qstat='qstat') 

277 

278 os.chdir(olddir) 

279 self.prind('Calculation Finished!') 

280 return jobNum 

281 

282 def clean(self, prefix='test', queue_num=None): 

283 """Method which cleans up after a calculation. 

284 

285 The default files generated OpenMX will be deleted IF this 

286 method is called. 

287 

288 """ 

289 self.prind("Cleaning Data") 

290 fileName = get_file_name('', self.label) 

291 pbs_Name = get_file_name('', self.label) 

292 files = [ 

293 # prefix+'.out',#prefix+'.dat',#prefix+'.BAND*', 

294 fileName + '.cif', 

295 fileName + '.dden.cube', 

296 fileName + '.ene', 

297 fileName + '.md', 

298 fileName + '.md2', 

299 fileName + '.tden.cube', 

300 fileName + '.sden.cube', 

301 fileName + '.v0.cube', 

302 fileName + '.v1.cube', 

303 fileName + '.vhart.cube', 

304 fileName + '.den0.cube', 

305 fileName + '.bulk.xyz', 

306 fileName + '.den1.cube', 

307 fileName + '.xyz', 

308 pbs_Name + '.o' + str(queue_num), 

309 pbs_Name + '.e' + str(queue_num) 

310 ] 

311 for f in files: 

312 try: 

313 self.prind("Removing" + f) 

314 os.remove(f) 

315 except OSError: 

316 self.prind("There is no such file named " + f) 

317 

318 def calculate(self, atoms=None, properties=None, 

319 system_changes=all_changes): 

320 """ 

321 Capture the RuntimeError from FileIOCalculator.calculate 

322 and add a little debug information from the OpenMX output. 

323 See base FileIOCalculator for documentation. 

324 """ 

325 if self.parameters.data_path is None: 

326 if 'OPENMX_DFT_DATA_PATH' not in cfg: 

327 warnings.warn('Please either set OPENMX_DFT_DATA_PATH as an' 

328 'enviroment variable or specify "data_path" as' 

329 'a keyword argument') 

330 

331 self.prind("Start Calculation") 

332 if properties is None: 

333 properties = self.implemented_properties 

334 try: 

335 Calculator.calculate(self, atoms, properties, system_changes) 

336 self.write_input(atoms=self.atoms, parameters=self.parameters, 

337 properties=properties, 

338 system_changes=system_changes) 

339 self.print_input(debug=self.debug, nohup=self.nohup) 

340 self.run() 

341 # self.read_results() 

342 self.version = self.read_version() 

343 output_atoms = read_openmx(filename=self.label, debug=self.debug) 

344 self.output_atoms = output_atoms 

345 # XXX The parameters are supposedly inputs, so it is dangerous 

346 # to update them from the outputs. --askhl 

347 self.parameters.update(output_atoms.calc.parameters) 

348 self.results = output_atoms.calc.results 

349 # self.clean() 

350 except RuntimeError as e: 

351 try: 

352 with open(get_file_name('.log')) as fd: 

353 lines = fd.readlines() 

354 debug_lines = 10 

355 print('##### %d last lines of the OpenMX output' % debug_lines) 

356 for line in lines[-20:]: 

357 print(line.strip()) 

358 print('##### end of openMX output') 

359 raise e 

360 except RuntimeError as e: 

361 raise e 

362 

363 def write_input(self, atoms=None, parameters=None, 

364 properties=[], system_changes=[]): 

365 """Write input (dat)-file. 

366 See calculator.py for further details. 

367 

368 Parameters: 

369 - atoms : The Atoms object to write. 

370 - properties : The properties which should be calculated. 

371 - system_changes : List of properties changed since last run. 

372 """ 

373 # Call base calculator. 

374 if atoms is None: 

375 atoms = self.atoms 

376 FileIOCalculator.write_input(self, atoms, properties, system_changes) 

377 write_openmx(label=self.label, atoms=atoms, parameters=self.parameters, 

378 properties=properties, system_changes=system_changes) 

379 

380 def print_input(self, debug=None, nohup=None): 

381 """ 

382 For a debugging purpose, print the .dat file 

383 """ 

384 if debug is None: 

385 debug = self.debug 

386 if nohup is None: 

387 nohup = self.nohup 

388 self.prind('Reading input file' + self.label) 

389 filename = get_file_name('.dat', self.label) 

390 if not nohup: 

391 with open(filename) as fd: 

392 while True: 

393 line = fd.readline() 

394 print(line.strip()) 

395 if not line: 

396 break 

397 

398 def read(self, label): 

399 self.parameters = {} 

400 self.set_label(label) 

401 if label[-5:] in ['.dat', '.out', '.log']: 

402 label = label[:-4] 

403 atoms = read_openmx(filename=label, debug=self.debug) 

404 self.update_atoms(atoms) 

405 self.parameters.update(atoms.calc.parameters) 

406 self.results = atoms.calc.results 

407 self.parameters['restart'] = self.label 

408 self.parameters['label'] = label 

409 

410 def read_version(self, label=None): 

411 version = None 

412 if label is None: 

413 label = self.label 

414 for line in open(get_file_name('.log', label)): 

415 if line.find('Ver.') != -1: 

416 version = line.split()[-1] 

417 break 

418 return version 

419 

420 def update_atoms(self, atoms): 

421 self.atoms = atoms.copy() 

422 

423 def set(self, **kwargs): 

424 """Set all parameters. 

425 

426 Parameters: 

427 -kwargs : Dictionary containing the keywords defined in 

428 OpenMXParameters. 

429 """ 

430 

431 for key, value in kwargs.items(): 

432 if key not in self.default_parameters.keys(): 

433 raise KeyError(f'Unkown keyword "{key}" and value "{value}".') 

434 if key == 'xc' and value not in self.default_parameters.allowed_xc: 

435 raise KeyError(f'Given xc "{value}" is not allowed') 

436 if key in ['dat_arguments'] and isinstance(value, dict): 

437 # For values that are dictionaries, verify subkeys, too. 

438 default_dict = self.default_parameters[key] 

439 for subkey in kwargs[key]: 

440 if subkey not in default_dict: 

441 allowed = ', '.join(list(default_dict.keys())) 

442 raise TypeError('Unknown subkeyword "{}" of keyword ' 

443 '"{}". Must be one of: {}' 

444 .format(subkey, key, allowed)) 

445 

446 # Find out what parameter has been changed 

447 changed_parameters = {} 

448 for key, value in kwargs.items(): 

449 oldvalue = self.parameters.get(key) 

450 if key not in self.parameters or not equal(value, oldvalue): 

451 changed_parameters[key] = value 

452 self.parameters[key] = value 

453 

454 # Set the parameters 

455 for key, value in kwargs.items(): 

456 # print(' Setting the %s as %s'%(key, value)) 

457 self.parameters[key] = value 

458 

459 # If Changed Parameter is Critical, we have to reset the results 

460 for key, value in changed_parameters.items(): 

461 if key in ['xc', 'kpts', 'energy_cutoff']: 

462 self.results = {} 

463 

464 value = kwargs.get('energy_cutoff') 

465 if value is not None and not (isinstance(value, (float, int)) 

466 and value > 0): 

467 mess = "'{}' must be a positive number(in eV), \ 

468 got '{}'".format('energy_cutoff', value) 

469 raise ValueError(mess) 

470 

471 atoms = kwargs.get('atoms') 

472 if atoms is not None and self.atoms is None: 

473 self.atoms = atoms.copy() 

474 

475 def set_results(self, results): 

476 # Not Implemented fully 

477 self.results.update(results) 

478 

479 def get_command(self, processes, threads, runfile=None, outfile=None): 

480 # Contruct the command to send to the operating system 

481 abs_dir = os.getcwd() 

482 command = '' 

483 self.prind(self.command) 

484 if self.command is None: 

485 self.command = 'openmx' 

486 # run processes specified by the system variable OPENMX_COMMAND 

487 if processes is None: 

488 command += cfg.get('OPENMX_COMMAND') 

489 if command is None: 

490 warnings.warn('Either specify OPENMX_COMMAND as an environment\ 

491 variable or specify processes as a keyword argument') 

492 else: # run with a specified number of processes 

493 threads_string = ' -nt ' + str(threads) 

494 if threads is None: 

495 threads_string = '' 

496 command += 'mpirun -np ' + \ 

497 str(processes) + ' ' + self.command + \ 

498 ' %s ' + threads_string + ' |tee %s' 

499 # str(processes) + ' openmx %s' + threads_string + ' > %s' 

500 

501 if runfile is None: 

502 runfile = os.path.join(abs_dir, f'{self.prefix} .dat') 

503 if outfile is None: 

504 outfile = os.path.join(abs_dir, f'{self.prefix} .log') 

505 try: 

506 command = command % (runfile, outfile) 

507 # command += '" > ./%s &' % outfile # outputs 

508 except TypeError: # in case the OPENMX_COMMAND is incompatible 

509 raise ValueError( 

510 "The 'OPENMX_COMMAND' environment must " + 

511 "be a format string" + 

512 " with four string arguments.\n" + 

513 "Example : 'mpirun -np 4 openmx ./%s -nt 2 > ./%s'.\n" + 

514 f"Got '{command}'") 

515 return command 

516 

517 def get_stress(self, atoms=None): 

518 if atoms is None: 

519 atoms = self.atoms 

520 

521 # Note: Stress is only supported from OpenMX 3.8+. 

522 stress = self.get_property('stress', atoms) 

523 

524 return stress 

525 

526 def get_band_structure(self, atoms=None, calc=None): 

527 """ 

528 This is band structure function. It is compatible to 

529 ase dft module """ 

530 from ase.dft import band_structure 

531 if isinstance(self['kpts'], tuple): 

532 self['kpts'] = self.get_kpoints(band_kpath=self['band_kpath']) 

533 return band_structure.get_band_structure(self.atoms, self, ) 

534 

535 def get_bz_k_points(self): 

536 kgrid = self['kpts'] 

537 if type(kgrid) in [int, float]: 

538 kgrid = kptdensity2monkhorstpack(self.atoms, kgrid, False) 

539 bz_k_points = [] 

540 n1 = kgrid[0] 

541 n2 = kgrid[1] 

542 n3 = kgrid[2] 

543 for i in range(n1): 

544 for j in range(n2): 

545 # Monkhorst Pack Grid [H.J. Monkhorst and J.D. Pack, 

546 # Phys. Rev. B 13, 5188 (1976)] 

547 for k in range(n3): 

548 bz_k_points.append((0.5 * float(2 * i - n1 + 1) / n1, 

549 0.5 * float(2 * j - n2 + 1) / n2, 

550 0.5 * float(2 * k - n3 + 1) / n3)) 

551 return np.array(bz_k_points) 

552 

553 def get_ibz_k_points(self): 

554 if self['band_kpath'] is None: 

555 return self.get_bz_k_points() 

556 else: 

557 return self.get_kpoints(band_kpath=self['band_kpath']) 

558 

559 def get_kpoints(self, kpts=None, symbols=None, band_kpath=None, eps=1e-5): 

560 """Convert band_kpath <-> kpts""" 

561 if kpts is None: 

562 kpts = [] 

563 band_kpath = np.array(band_kpath) 

564 band_nkpath = len(band_kpath) 

565 for i, kpath in enumerate(band_kpath): 

566 end = False 

567 nband = int(kpath[0]) 

568 if band_nkpath == i: 

569 end = True 

570 nband += 1 

571 ini = np.array(kpath[1:4], dtype=float) 

572 fin = np.array(kpath[4:7], dtype=float) 

573 x = np.linspace(ini[0], fin[0], nband, endpoint=end) 

574 y = np.linspace(ini[1], fin[1], nband, endpoint=end) 

575 z = np.linspace(ini[2], fin[2], nband, endpoint=end) 

576 kpts.extend(np.array([x, y, z]).T) 

577 return np.array(kpts, dtype=float) 

578 elif band_kpath is None: 

579 band_kpath = [] 

580 points = np.asarray(kpts) 

581 diffs = points[1:] - points[:-1] 

582 kinks = abs(diffs[1:] - diffs[:-1]).sum(1) > eps 

583 N = len(points) 

584 indices = [0] 

585 indices.extend(np.arange(1, N - 1)[kinks]) 

586 indices.append(N - 1) 

587 for start, end, s_sym, e_sym in zip(indices[1:], indices[:-1], 

588 symbols[1:], symbols[:-1]): 

589 band_kpath.append({'start_point': start, 'end_point': end, 

590 'kpts': 20, 

591 'path_symbols': (s_sym, e_sym)}) 

592 return band_kpath 

593 

594 def get_lattice_type(self): 

595 cellpar = cell_to_cellpar(self.atoms.cell) 

596 abc = cellpar[:3] 

597 angles = cellpar[3:] 

598 min_lv = min(abc) 

599 if np.ptp(abc) < 0.01 * min_lv: 

600 if abs(angles - 90).max() < 1: 

601 return 'cubic' 

602 elif abs(angles - 60).max() < 1: 

603 return 'fcc' 

604 elif abs(angles - np.arccos(-1 / 3.) * 180 / np.pi).max < 1: 

605 return 'bcc' 

606 elif abs(angles - 90).max() < 1: 

607 if abs(abc[0] - abc[1]).min() < 0.01 * min_lv: 

608 return 'tetragonal' 

609 else: 

610 return 'orthorhombic' 

611 elif abs(abc[0] - abc[1]) < 0.01 * min_lv and \ 

612 abs(angles[2] - 120) < 1 and abs(angles[:2] - 90).max() < 1: 

613 return 'hexagonal' 

614 else: 

615 return 'not special' 

616 

617 def get_number_of_spins(self): 

618 try: 

619 magmoms = self.atoms.get_initial_magnetic_moments() 

620 if self['scf_spinpolarization'] is None: 

621 if isinstance(magmoms[0], float): 

622 if abs(magmoms).max() < 0.1: 

623 return 1 

624 else: 

625 return 2 

626 else: 

627 raise NotImplementedError 

628 else: 

629 if self['scf_spinpolarization'] == 'on': 

630 return 2 

631 elif self['scf_spinpolarization'] == 'nc' or \ 

632 np.any(self['initial_magnetic_moments_euler_angles']) \ 

633 is not None: 

634 return 1 

635 except KeyError: 

636 return 1 

637 

638 def get_eigenvalues(self, kpt=None, spin=None): 

639 if self.results.get('eigenvalues') is None: 

640 self.calculate(self.atoms) 

641 if kpt is None and spin is None: 

642 return self.results['eigenvalues'] 

643 else: 

644 return self.results['eigenvalues'][spin, kpt, :] 

645 

646 def get_fermi_level(self): 

647 try: 

648 fermi_level = self.results['chemical_potential'] 

649 except KeyError: 

650 self.calculate() 

651 fermi_level = self.results['chemical_potential'] 

652 return fermi_level 

653 

654 def get_number_of_bands(self): 

655 pag = self.parameters.get 

656 dfd = default_dictionary 

657 if 'number_of_bands' not in self.results: 

658 n = 0 

659 for atom in self.atoms: 

660 sym = atom.symbol 

661 orbitals = pag('dft_data_dict', dfd)[sym]['orbitals used'] 

662 d = 1 

663 for orbital in orbitals: 

664 n += d * orbital 

665 d += 2 

666 self.results['number_of_bands'] = n 

667 return self.results['number_of_bands'] 

668 

669 def dirG(self, dk, bzone=(0, 0, 0)): 

670 nx, ny, nz = self['wannier_kpts'] 

671 dx = dk // (ny * nz) + bzone[0] * nx 

672 dy = (dk // nz) % ny + bzone[1] * ny 

673 dz = dk % nz + bzone[2] * nz 

674 return dx, dy, dz 

675 

676 def dk(self, dirG): 

677 dx, dy, dz = dirG 

678 nx, ny, nz = self['wannier_kpts'] 

679 return ny * nz * (dx % nx) + nz * (dy % ny) + dz % nz 

680 

681 def get_wannier_localization_matrix(self, nbands, dirG, nextkpoint=None, 

682 kpoint=None, spin=0, G_I=(0, 0, 0)): 

683 # only expected to work for no spin polarization 

684 try: 

685 self['bloch_overlaps'] 

686 except KeyError: 

687 self.read_bloch_overlaps() 

688 dirG = tuple(dirG) 

689 nx, ny, nz = self['wannier_kpts'] 

690 nr3 = nx * ny * nz 

691 if kpoint is None and nextkpoint is None: 

692 return {kpoint: self['bloch_overlaps' 

693 ][kpoint][dirG][:nbands, :nbands 

694 ] for kpoint in range(nr3)} 

695 if kpoint is None: 

696 kpoint = (nextkpoint - self.dk(dirG)) % nr3 

697 if nextkpoint is None: 

698 nextkpoint = (kpoint + self.dk(dirG)) % nr3 

699 if dirG not in self['bloch_overlaps'][kpoint].keys(): 

700 return np.zeros((nbands, nbands), complex) 

701 return self['bloch_overlaps'][kpoint][dirG][:nbands, :nbands] 

702 

703 def prind(self, line, debug=None): 

704 ''' Print the value if debugging mode is on. 

705 Otherwise, it just ignored''' 

706 if debug is None: 

707 debug = self.debug 

708 if debug: 

709 print(line) 

710 

711 def print_file(self, file=None, running=None, **args): 

712 ''' Print the file while calculation is running''' 

713 prev_position = 0 

714 last_position = 0 

715 while not os.path.isfile(file): 

716 self.prind(f'Waiting for {file} to come out') 

717 time.sleep(5) 

718 with open(file) as fd: 

719 while running(**args): 

720 fd.seek(last_position) 

721 new_data = fd.read() 

722 prev_position = fd.tell() 

723 # self.prind('pos', prev_position != last_position) 

724 if prev_position != last_position: 

725 if not self.nohup: 

726 print(new_data) 

727 last_position = prev_position 

728 time.sleep(1)