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

450 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-30 08:22 +0000

1# fmt: off 

2 

3""" 

4 The ASE Calculator for OpenMX <https://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 <https://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 ---------- 

108 key : str 

109 Name of the parameters to get. 

110 """ 

111 return self.parameters[key] 

112 

113 def __setitem__(self, key, value): 

114 self.parameters[key] = value 

115 

116 def initialize_output_setting(self, **kwargs): 

117 output_setting = {} 

118 self.output_setting = dict(self.default_output_setting) 

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

120 if key in self.default_output_setting: 

121 output_setting[key] = value 

122 self.output_setting.update(output_setting) 

123 self.__dict__.update(self.output_setting) 

124 

125 def initialize_pbs(self, pbs): 

126 if pbs: 

127 self.pbs = dict(self.default_pbs) 

128 for key in pbs: 

129 if key not in self.default_pbs: 

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

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

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

133 .format(key, allowed)) 

134 # Put dictionary into python variable 

135 self.pbs.update(pbs) 

136 self.__dict__.update(self.pbs) 

137 else: 

138 self.pbs = None 

139 

140 def initialize_mpi(self, mpi): 

141 if mpi: 

142 self.mpi = dict(self.default_mpi) 

143 for key in mpi: 

144 if key not in self.default_mpi: 

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

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

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

148 .format(key, allowed)) 

149 # Put dictionary into python variable 

150 self.mpi.update(mpi) 

151 self.__dict__.update(self.mpi) 

152 else: 

153 self.mpi = None 

154 

155 def run(self): 

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

157 if self.pbs is not None: 

158 run = self.run_pbs 

159 elif self.mpi is not None: 

160 run = self.run_mpi 

161 else: 

162 run = self.run_openmx 

163 run() 

164 

165 def run_openmx(self): 

166 def isRunning(process=None): 

167 ''' Check mpi is running''' 

168 return process.poll() is None 

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

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

171 olddir = os.getcwd() 

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

173 try: 

174 os.chdir(abs_dir) 

175 if self.command is None: 

176 self.command = 'openmx' 

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

178 command = command % (runfile, outfile) 

179 self.prind(command) 

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

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

182 finally: 

183 os.chdir(olddir) 

184 self.prind("Calculation Finished") 

185 

186 def run_mpi(self): 

187 """ 

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

189 run. 

190 """ 

191 def isRunning(process=None): 

192 ''' Check mpi is running''' 

193 return process.poll() is None 

194 processes = self.processes 

195 threads = self.threads 

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

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

198 olddir = os.getcwd() 

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

200 try: 

201 os.chdir(abs_dir) 

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

203 self.prind(command) 

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

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

206 finally: 

207 os.chdir(olddir) 

208 self.prind("Calculation Finished") 

209 

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

211 """ 

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

213 Your system should have Scheduler. PBS 

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

215 Super computer user 

216 """ 

217 nodes = self.nodes 

218 processes = self.processes 

219 

220 prefix = self.prefix 

221 olddir = os.getcwd() 

222 try: 

223 os.chdir(self.abs_directory) 

224 except AttributeError: 

225 os.chdir(self.directory) 

226 

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

228 """ 

229 Check submitted job is still Running 

230 """ 

231 def runCmd(exe): 

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

233 stderr=subprocess.STDOUT, 

234 universal_newlines=True) 

235 while True: 

236 line = p.stdout.readline() 

237 if line != '': 

238 # the real code does filtering here 

239 yield line.rstrip() 

240 else: 

241 break 

242 jobs = runCmd('qstat') 

243 columns = None 

244 for line in jobs: 

245 if str(jobNum) in line: 

246 columns = line.split() 

247 self.prind(line) 

248 if columns is not None: 

249 return columns[-2] == status 

250 else: 

251 return False 

252 

253 inputfile = self.label + '.dat' 

254 outfile = self.label + '.log' 

255 

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

257 jobName = prefix 

258 cmd = bashArgs + \ 

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

260 inputfile, outfile) 

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

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

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

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

265 self.prind(wholeCmd) 

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

267 stdout=subprocess.PIPE, universal_newlines=True) 

268 out = out.communicate()[0] 

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

270 

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

272 '\nWaiting for the Queue to start') 

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

274 time.sleep(5) 

275 self.prind('.') 

276 self.prind('Start Calculating') 

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

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

279 

280 os.chdir(olddir) 

281 self.prind('Calculation Finished!') 

282 return jobNum 

283 

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

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

286 

287 The default files generated OpenMX will be deleted IF this 

288 method is called. 

289 

290 """ 

291 self.prind("Cleaning Data") 

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

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

294 files = [ 

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

296 fileName + '.cif', 

297 fileName + '.dden.cube', 

298 fileName + '.ene', 

299 fileName + '.md', 

300 fileName + '.md2', 

301 fileName + '.tden.cube', 

302 fileName + '.sden.cube', 

303 fileName + '.v0.cube', 

304 fileName + '.v1.cube', 

305 fileName + '.vhart.cube', 

306 fileName + '.den0.cube', 

307 fileName + '.bulk.xyz', 

308 fileName + '.den1.cube', 

309 fileName + '.xyz', 

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

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

312 ] 

313 for f in files: 

314 try: 

315 self.prind("Removing" + f) 

316 os.remove(f) 

317 except OSError: 

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

319 

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

321 system_changes=all_changes): 

322 """ 

323 Capture the RuntimeError from FileIOCalculator.calculate 

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

325 See base FileIOCalculator for documentation. 

326 """ 

327 if self.parameters.data_path is None: 

328 if 'OPENMX_DFT_DATA_PATH' not in cfg: 

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

330 'enviroment variable or specify "data_path" as' 

331 'a keyword argument') 

332 

333 self.prind("Start Calculation") 

334 if properties is None: 

335 properties = self.implemented_properties 

336 try: 

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

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

339 properties=properties, 

340 system_changes=system_changes) 

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

342 self.run() 

343 # self.read_results() 

344 self.version = self.read_version() 

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

346 self.output_atoms = output_atoms 

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

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

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

350 self.results = output_atoms.calc.results 

351 # self.clean() 

352 except RuntimeError as e: 

353 try: 

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

355 lines = fd.readlines() 

356 debug_lines = 10 

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

358 for line in lines[-20:]: 

359 print(line.strip()) 

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

361 raise e 

362 except RuntimeError as e: 

363 raise e 

364 

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

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

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

368 See calculator.py for further details. 

369 

370 Parameters 

371 ---------- 

372 atoms : :class:`~ase.Atoms` 

373 ASE :class:`~ase.Atoms` object to write. 

374 properties : list[str] 

375 Properties which should be calculated. 

376 system_changes : list[str] 

377 Properties changed since last run. 

378 """ 

379 # Call base calculator. 

380 if atoms is None: 

381 atoms = self.atoms 

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

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

384 properties=properties, system_changes=system_changes) 

385 

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

387 """ 

388 For a debugging purpose, print the .dat file 

389 """ 

390 if debug is None: 

391 debug = self.debug 

392 if nohup is None: 

393 nohup = self.nohup 

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

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

396 if not nohup: 

397 with open(filename) as fd: 

398 while True: 

399 line = fd.readline() 

400 print(line.strip()) 

401 if not line: 

402 break 

403 

404 def read(self, label): 

405 self.parameters = {} 

406 self.set_label(label) 

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

408 label = label[:-4] 

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

410 self.update_atoms(atoms) 

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

412 self.results = atoms.calc.results 

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

414 self.parameters['label'] = label 

415 

416 def read_version(self, label=None): 

417 version = None 

418 if label is None: 

419 label = self.label 

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

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

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

423 break 

424 return version 

425 

426 def update_atoms(self, atoms): 

427 self.atoms = atoms.copy() 

428 

429 def set(self, **kwargs): 

430 """Set all parameters. 

431 

432 Parameters 

433 ---------- 

434 kwargs : dict 

435 Dictionary containing the keywords in :class:`OpenMXParameters`. 

436 """ 

437 

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

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

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

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

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

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

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

445 default_dict = self.default_parameters[key] 

446 for subkey in kwargs[key]: 

447 if subkey not in default_dict: 

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

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

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

451 .format(subkey, key, allowed)) 

452 

453 # Find out what parameter has been changed 

454 changed_parameters = {} 

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

456 oldvalue = self.parameters.get(key) 

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

458 changed_parameters[key] = value 

459 self.parameters[key] = value 

460 

461 # Set the parameters 

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

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

464 self.parameters[key] = value 

465 

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

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

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

469 self.results = {} 

470 

471 value = kwargs.get('energy_cutoff') 

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

473 and value > 0): 

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

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

476 raise ValueError(mess) 

477 

478 atoms = kwargs.get('atoms') 

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

480 self.atoms = atoms.copy() 

481 

482 def set_results(self, results): 

483 # Not Implemented fully 

484 self.results.update(results) 

485 

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

487 # Contruct the command to send to the operating system 

488 abs_dir = os.getcwd() 

489 command = '' 

490 self.prind(self.command) 

491 if self.command is None: 

492 self.command = 'openmx' 

493 # run processes specified by the system variable OPENMX_COMMAND 

494 if processes is None: 

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

496 if command is None: 

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

498 variable or specify processes as a keyword argument') 

499 else: # run with a specified number of processes 

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

501 if threads is None: 

502 threads_string = '' 

503 command += 'mpirun -np ' + \ 

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

505 ' %s ' + threads_string + ' |tee %s' 

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

507 

508 if runfile is None: 

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

510 if outfile is None: 

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

512 try: 

513 command = command % (runfile, outfile) 

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

515 except TypeError: # in case the OPENMX_COMMAND is incompatible 

516 raise ValueError( 

517 "The 'OPENMX_COMMAND' environment must " + 

518 "be a format string" + 

519 " with four string arguments.\n" + 

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

521 f"Got '{command}'") 

522 return command 

523 

524 def get_stress(self, atoms=None): 

525 if atoms is None: 

526 atoms = self.atoms 

527 

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

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

530 

531 return stress 

532 

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

534 """ 

535 This is band structure function. It is compatible to 

536 ase dft module """ 

537 from ase.dft import band_structure 

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

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

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

541 

542 def get_bz_k_points(self): 

543 kgrid = self['kpts'] 

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

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

546 bz_k_points = [] 

547 n1 = kgrid[0] 

548 n2 = kgrid[1] 

549 n3 = kgrid[2] 

550 for i in range(n1): 

551 for j in range(n2): 

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

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

554 for k in range(n3): 

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

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

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

558 return np.array(bz_k_points) 

559 

560 def get_ibz_k_points(self): 

561 if self['band_kpath'] is None: 

562 return self.get_bz_k_points() 

563 else: 

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

565 

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

567 """Convert band_kpath <-> kpts""" 

568 if kpts is None: 

569 kpts = [] 

570 band_kpath = np.array(band_kpath) 

571 band_nkpath = len(band_kpath) 

572 for i, kpath in enumerate(band_kpath): 

573 end = False 

574 nband = int(kpath[0]) 

575 if band_nkpath == i: 

576 end = True 

577 nband += 1 

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

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

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

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

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

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

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

585 elif band_kpath is None: 

586 band_kpath = [] 

587 points = np.asarray(kpts) 

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

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

590 N = len(points) 

591 indices = [0] 

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

593 indices.append(N - 1) 

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

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

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

597 'kpts': 20, 

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

599 return band_kpath 

600 

601 def get_lattice_type(self): 

602 cellpar = cell_to_cellpar(self.atoms.cell) 

603 abc = cellpar[:3] 

604 angles = cellpar[3:] 

605 min_lv = min(abc) 

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

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

608 return 'cubic' 

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

610 return 'fcc' 

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

612 return 'bcc' 

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

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

615 return 'tetragonal' 

616 else: 

617 return 'orthorhombic' 

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

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

620 return 'hexagonal' 

621 else: 

622 return 'not special' 

623 

624 def get_number_of_spins(self): 

625 try: 

626 magmoms = self.atoms.get_initial_magnetic_moments() 

627 if self['scf_spinpolarization'] is None: 

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

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

630 return 1 

631 else: 

632 return 2 

633 else: 

634 raise NotImplementedError 

635 else: 

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

637 return 2 

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

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

640 is not None: 

641 return 1 

642 except KeyError: 

643 return 1 

644 

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

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

647 self.calculate(self.atoms) 

648 if kpt is None and spin is None: 

649 return self.results['eigenvalues'] 

650 else: 

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

652 

653 def get_fermi_level(self): 

654 try: 

655 fermi_level = self.results['chemical_potential'] 

656 except KeyError: 

657 self.calculate() 

658 fermi_level = self.results['chemical_potential'] 

659 return fermi_level 

660 

661 def get_number_of_bands(self): 

662 pag = self.parameters.get 

663 dfd = default_dictionary 

664 if 'number_of_bands' not in self.results: 

665 n = 0 

666 for atom in self.atoms: 

667 sym = atom.symbol 

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

669 d = 1 

670 for orbital in orbitals: 

671 n += d * orbital 

672 d += 2 

673 self.results['number_of_bands'] = n 

674 return self.results['number_of_bands'] 

675 

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

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

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

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

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

681 return dx, dy, dz 

682 

683 def dk(self, dirG): 

684 dx, dy, dz = dirG 

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

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

687 

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

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

690 # only expected to work for no spin polarization 

691 try: 

692 self['bloch_overlaps'] 

693 except KeyError: 

694 self.read_bloch_overlaps() 

695 dirG = tuple(dirG) 

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

697 nr3 = nx * ny * nz 

698 if kpoint is None and nextkpoint is None: 

699 return {kpoint: self['bloch_overlaps' 

700 ][kpoint][dirG][:nbands, :nbands 

701 ] for kpoint in range(nr3)} 

702 if kpoint is None: 

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

704 if nextkpoint is None: 

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

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

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

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

709 

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

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

712 Otherwise, it just ignored''' 

713 if debug is None: 

714 debug = self.debug 

715 if debug: 

716 print(line) 

717 

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

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

720 prev_position = 0 

721 last_position = 0 

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

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

724 time.sleep(5) 

725 with open(file) as fd: 

726 while running(**args): 

727 fd.seek(last_position) 

728 new_data = fd.read() 

729 prev_position = fd.tell() 

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

731 if prev_position != last_position: 

732 if not self.nohup: 

733 print(new_data) 

734 last_position = prev_position 

735 time.sleep(1)