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

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2 The ASE Calculator for OpenMX <http://www.openmx-square.org>
3 A Python interface to the software package for nano-scale
4 material simulations based on density functional theories.
5 Copyright (C) 2017 Charles Thomas Johnson, Jae Hwan Shim and JaeJun Yu
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation, either version 2.1 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with ASE. If not, see <http://www.gnu.org/licenses/>.
20"""
22import os
23import time
24import subprocess
25import re
26import warnings
27import numpy as np
28from ase.geometry import cell_to_cellpar
29from ase.calculators.calculator import (FileIOCalculator, Calculator, equal,
30 all_changes, kptdensity2monkhorstpack)
31from ase.calculators.openmx.parameters import OpenMXParameters
32from ase.calculators.openmx.default_settings import default_dictionary
33from ase.calculators.openmx.reader import read_openmx, get_file_name
34from ase.calculators.openmx.writer import write_openmx
37def parse_omx_version(txt):
38 """Parse version number from stdout header."""
39 match = re.search(r'Welcome to OpenMX\s+Ver\.\s+(\S+)', txt, re.M)
40 return match.group(1)
43class OpenMX(FileIOCalculator):
44 """
45 Calculator interface to the OpenMX code.
46 """
48 implemented_properties = [
49 'free_energy', # Same value with energy
50 'energy',
51 'energies',
52 'forces',
53 'stress',
54 'dipole',
55 'chemical_potential',
56 'magmom',
57 'magmoms',
58 'eigenvalues']
60 default_parameters = OpenMXParameters()
62 default_pbs = {
63 'processes': 1,
64 'walltime': "10:00:00",
65 'threads': 1,
66 'nodes': 1
67 }
69 default_mpi = {
70 'processes': 1,
71 'threads': 1
72 }
74 default_output_setting = {
75 'nohup': True,
76 'debug': False
77 }
79 def __init__(self, restart=None,
80 ignore_bad_restart_file=FileIOCalculator._deprecated,
81 label='./openmx', atoms=None, command=None, mpi=None,
82 pbs=None, **kwargs):
84 # Initialize and put the default parameters.
85 self.initialize_pbs(pbs)
86 self.initialize_mpi(mpi)
87 self.initialize_output_setting(**kwargs)
89 FileIOCalculator.__init__(self, restart, ignore_bad_restart_file,
90 label, atoms, command, **kwargs)
92 def __getitem__(self, key):
93 """Convenience method to retrieve a parameter as
94 calculator[key] rather than calculator.parameters[key]
96 Parameters:
97 -key : str, the name of the parameters to get.
98 """
99 return self.parameters[key]
101 def __setitem__(self, key, value):
102 self.parameters[key] = value
104 def initialize_output_setting(self, **kwargs):
105 output_setting = {}
106 self.output_setting = dict(self.default_output_setting)
107 for key, value in kwargs.items():
108 if key in self.default_output_setting:
109 output_setting[key] = value
110 self.output_setting.update(output_setting)
111 self.__dict__.update(self.output_setting)
113 def initialize_pbs(self, pbs):
114 if pbs:
115 self.pbs = dict(self.default_pbs)
116 for key in pbs:
117 if key not in self.default_pbs:
118 allowed = ', '.join(list(self.default_pbs.keys()))
119 raise TypeError('Unexpected keyword "{0}" in "pbs" '
120 'dictionary. Must be one of: {1}'
121 .format(key, allowed))
122 # Put dictionary into python variable
123 self.pbs.update(pbs)
124 self.__dict__.update(self.pbs)
125 else:
126 self.pbs = None
128 def initialize_mpi(self, mpi):
129 if mpi:
130 self.mpi = dict(self.default_mpi)
131 for key in mpi:
132 if key not in self.default_mpi:
133 allowed = ', '.join(list(self.default_mpi.keys()))
134 raise TypeError('Unexpected keyword "{0}" in "mpi" '
135 'dictionary. Must be one of: {1}'
136 .format(key, allowed))
137 # Put dictionary into python variable
138 self.mpi.update(mpi)
139 self.__dict__.update(self.mpi)
140 else:
141 self.mpi = None
143 def run(self):
144 '''Check Which Running method we r going to use and run it'''
145 if self.pbs is not None:
146 run = self.run_pbs
147 elif self.mpi is not None:
148 run = self.run_mpi
149 else:
150 run = self.run_openmx
151 run()
153 def run_openmx(self):
154 def isRunning(process=None):
155 ''' Check mpi is running'''
156 return process.poll() is None
157 runfile = get_file_name('.dat', self.label, absolute_directory=False)
158 outfile = get_file_name('.log', self.label)
159 olddir = os.getcwd()
160 abs_dir = os.path.join(olddir, self.directory)
161 try:
162 os.chdir(abs_dir)
163 if self.command is None:
164 self.command = 'openmx'
165 command = self.command + ' %s > %s'
166 command = command % (runfile, outfile)
167 self.prind(command)
168 p = subprocess.Popen(command, shell=True, universal_newlines=True)
169 self.print_file(file=outfile, running=isRunning, process=p)
170 finally:
171 os.chdir(olddir)
172 self.prind("Calculation Finished")
174 def run_mpi(self):
175 """
176 Run openmx using MPI method. If keyword `mpi` is declared, it will
177 run.
178 """
179 def isRunning(process=None):
180 ''' Check mpi is running'''
181 return process.poll() is None
182 processes = self.processes
183 threads = self.threads
184 runfile = get_file_name('.dat', self.label, absolute_directory=False)
185 outfile = get_file_name('.log', self.label)
186 olddir = os.getcwd()
187 abs_dir = os.path.join(olddir, self.directory)
188 try:
189 os.chdir(abs_dir)
190 command = self.get_command(processes, threads, runfile, outfile)
191 self.prind(command)
192 p = subprocess.Popen(command, shell=True, universal_newlines=True)
193 self.print_file(file=outfile, running=isRunning, process=p)
194 finally:
195 os.chdir(olddir)
196 self.prind("Calculation Finished")
198 def run_pbs(self, prefix='test'):
199 """
200 Execute the OpenMX using Plane Batch System. In order to use this,
201 Your system should have Scheduler. PBS
202 Basically, it does qsub. and wait until qstat signal shows c
203 Super computer user
204 """
205 nodes = self.nodes
206 processes = self.processes
208 prefix = self.prefix
209 olddir = os.getcwd()
210 try:
211 os.chdir(self.abs_directory)
212 except AttributeError:
213 os.chdir(self.directory)
215 def isRunning(jobNum=None, status='Q', qstat='qstat'):
216 """
217 Check submitted job is still Running
218 """
219 def runCmd(exe):
220 p = subprocess.Popen(exe, stdout=subprocess.PIPE,
221 stderr=subprocess.STDOUT,
222 universal_newlines=True)
223 while True:
224 line = p.stdout.readline()
225 if line != '':
226 # the real code does filtering here
227 yield line.rstrip()
228 else:
229 break
230 jobs = runCmd('qstat')
231 columns = None
232 for line in jobs:
233 if str(jobNum) in line:
234 columns = line.split()
235 self.prind(line)
236 if columns is not None:
237 return columns[-2] == status
238 else:
239 return False
241 inputfile = self.label + '.dat'
242 outfile = self.label + '.log'
244 bashArgs = "#!/bin/bash \n cd $PBS_O_WORKDIR\n"
245 jobName = prefix
246 cmd = bashArgs + \
247 'mpirun -hostfile $PBS_NODEFILE openmx %s > %s' % (
248 inputfile, outfile)
249 echoArgs = ["echo", "$' %s'" % cmd]
250 qsubArgs = ["qsub", "-N", jobName, "-l", "nodes=%d:ppn=%d" %
251 (nodes, processes), "-l", "walltime=" + self.walltime]
252 wholeCmd = " ".join(echoArgs) + " | " + " ".join(qsubArgs)
253 self.prind(wholeCmd)
254 out = subprocess.Popen(wholeCmd, shell=True,
255 stdout=subprocess.PIPE, universal_newlines=True)
256 out = out.communicate()[0]
257 jobNum = int(re.match(r'(\d+)', out.split()[0]).group(1))
259 self.prind('Queue number is ' + str(jobNum) +
260 '\nWaiting for the Queue to start')
261 while isRunning(jobNum, status='Q'):
262 time.sleep(5)
263 self.prind('.')
264 self.prind('Start Calculating')
265 self.print_file(file=outfile, running=isRunning,
266 jobNum=jobNum, status='R', qstat='qstat')
268 os.chdir(olddir)
269 self.prind('Calculation Finished!')
270 return jobNum
272 def clean(self, prefix='test', queue_num=None):
273 """Method which cleans up after a calculation.
275 The default files generated OpenMX will be deleted IF this
276 method is called.
278 """
279 self.prind("Cleaning Data")
280 fileName = get_file_name('', self.label)
281 pbs_Name = get_file_name('', self.label)
282 files = [
283 # prefix+'.out',#prefix+'.dat',#prefix+'.BAND*',
284 fileName + '.cif', fileName + '.dden.cube', fileName + \
285 '.ene', fileName + '.md', fileName + '.md2',
286 fileName + '.tden.cube', fileName + '.sden.cube', fileName + \
287 '.v0.cube', fileName + '.v1.cube',
288 fileName + '.vhart.cube', fileName + '.den0.cube', fileName + \
289 '.bulk.xyz', fileName + '.den1.cube',
290 fileName + '.xyz', pbs_Name + '.o' + \
291 str(queue_num), pbs_Name + '.e' + str(queue_num)
292 ]
293 for f in files:
294 try:
295 self.prind("Removing" + f)
296 os.remove(f)
297 except OSError:
298 self.prind("There is no such file named " + f)
300 def calculate(self, atoms=None, properties=None,
301 system_changes=all_changes):
302 """
303 Capture the RuntimeError from FileIOCalculator.calculate
304 and add a little debug information from the OpenMX output.
305 See base FileIOCalculator for documentation.
306 """
307 if self.parameters.data_path is None:
308 if 'OPENMX_DFT_DATA_PATH' not in os.environ:
309 warnings.warn('Please either set OPENMX_DFT_DATA_PATH as an'
310 'enviroment variable or specify "data_path" as'
311 'a keyword argument')
313 self.prind("Start Calculation")
314 if properties is None:
315 properties = self.implemented_properties
316 try:
317 Calculator.calculate(self, atoms, properties, system_changes)
318 self.write_input(atoms=self.atoms, parameters=self.parameters,
319 properties=properties,
320 system_changes=system_changes)
321 self.print_input(debug=self.debug, nohup=self.nohup)
322 self.run()
323 # self.read_results()
324 self.version = self.read_version()
325 output_atoms = read_openmx(filename=self.label, debug=self.debug)
326 self.output_atoms = output_atoms
327 # XXX The parameters are supposedly inputs, so it is dangerous
328 # to update them from the outputs. --askhl
329 self.parameters.update(output_atoms.calc.parameters)
330 self.results = output_atoms.calc.results
331 # self.clean()
332 except RuntimeError as e:
333 try:
334 with open(get_file_name('.log'), 'r') as fd:
335 lines = fd.readlines()
336 debug_lines = 10
337 print('##### %d last lines of the OpenMX output' % debug_lines)
338 for line in lines[-20:]:
339 print(line.strip())
340 print('##### end of openMX output')
341 raise e
342 except RuntimeError as e:
343 raise e
345 def write_input(self, atoms=None, parameters=None,
346 properties=[], system_changes=[]):
347 """Write input (dat)-file.
348 See calculator.py for further details.
350 Parameters:
351 - atoms : The Atoms object to write.
352 - properties : The properties which should be calculated.
353 - system_changes : List of properties changed since last run.
354 """
355 # Call base calculator.
356 if atoms is None:
357 atoms = self.atoms
358 FileIOCalculator.write_input(self, atoms, properties, system_changes)
359 write_openmx(label=self.label, atoms=atoms, parameters=self.parameters,
360 properties=properties, system_changes=system_changes)
362 def print_input(self, debug=None, nohup=None):
363 """
364 For a debugging purpose, print the .dat file
365 """
366 if debug is None:
367 debug = self.debug
368 if nohup is None:
369 nohup = self.nohup
370 self.prind('Reading input file' + self.label)
371 filename = get_file_name('.dat', self.label)
372 if not nohup:
373 with open(filename, 'r') as fd:
374 while True:
375 line = fd.readline()
376 print(line.strip())
377 if not line:
378 break
380 def read(self, label):
381 self.parameters = {}
382 self.set_label(label)
383 if label[-5:] in ['.dat', '.out', '.log']:
384 label = label[:-4]
385 atoms = read_openmx(filename=label, debug=self.debug)
386 self.update_atoms(atoms)
387 self.parameters.update(atoms.calc.parameters)
388 self.results = atoms.calc.results
389 self.parameters['restart'] = self.label
390 self.parameters['label'] = label
392 def read_version(self, label=None):
393 version = None
394 if label is None:
395 label = self.label
396 for line in open(get_file_name('.out', label)):
397 if line.find('Ver.') != -1:
398 version = line.split()[-1]
399 break
400 return version
402 def update_atoms(self, atoms):
403 self.atoms = atoms.copy()
405 def set(self, **kwargs):
406 """Set all parameters.
408 Parameters:
409 -kwargs : Dictionary containing the keywords defined in
410 OpenMXParameters.
411 """
413 for key, value in kwargs.items():
414 if key not in self.default_parameters.keys():
415 raise KeyError('Unkown keyword "%s" and value "%s".' %
416 (key, value))
417 if key == 'xc' and value not in self.default_parameters.allowed_xc:
418 raise KeyError('Given xc "%s" is not allowed' % value)
419 if key in ['dat_arguments'] and isinstance(value, dict):
420 # For values that are dictionaries, verify subkeys, too.
421 default_dict = self.default_parameters[key]
422 for subkey in kwargs[key]:
423 if subkey not in default_dict:
424 allowed = ', '.join(list(default_dict.keys()))
425 raise TypeError('Unknown subkeyword "{0}" of keyword '
426 '"{1}". Must be one of: {2}'
427 .format(subkey, key, allowed))
429 # Find out what parameter has been changed
430 changed_parameters = {}
431 for key, value in kwargs.items():
432 oldvalue = self.parameters.get(key)
433 if key not in self.parameters or not equal(value, oldvalue):
434 changed_parameters[key] = value
435 self.parameters[key] = value
437 # Set the parameters
438 for key, value in kwargs.items():
439 # print(' Setting the %s as %s'%(key, value))
440 self.parameters[key] = value
442 # If Changed Parameter is Critical, we have to reset the results
443 for key, value in changed_parameters.items():
444 if key in ['xc', 'kpts', 'energy_cutoff']:
445 self.results = {}
447 value = kwargs.get('energy_cutoff')
448 if value is not None and not (isinstance(value, (float, int))
449 and value > 0):
450 mess = "'%s' must be a positive number(in eV), \
451 got '%s'" % ('energy_cutoff', value)
452 raise ValueError(mess)
454 atoms = kwargs.get('atoms')
455 if atoms is not None and self.atoms is None:
456 self.atoms = atoms.copy()
458 def set_results(self, results):
459 # Not Implemented fully
460 self.results.update(results)
462 def get_command(self, processes, threads, runfile=None, outfile=None):
463 # Contruct the command to send to the operating system
464 abs_dir = os.getcwd()
465 command = ''
466 self.prind(self.command)
467 if self.command is None:
468 self.command = 'openmx'
469 # run processes specified by the system variable OPENMX_COMMAND
470 if processes is None:
471 command += os.environ.get('OPENMX_COMMAND')
472 if command is None:
473 warnings.warn('Either specify OPENMX_COMMAND as an environment\
474 variable or specify processes as a keyword argument')
475 else: # run with a specified number of processes
476 threads_string = ' -nt ' + str(threads)
477 if threads is None:
478 threads_string = ''
479 command += 'mpirun -np ' + \
480 str(processes) + ' ' + self.command + \
481 ' %s ' + threads_string + ' |tee %s'
482 # str(processes) + ' openmx %s' + threads_string + ' > %s'
484 if runfile is None:
485 runfile = abs_dir + '/' + self.prefix + '.dat'
486 if outfile is None:
487 outfile = abs_dir + '/' + self.prefix + '.log'
488 try:
489 command = command % (runfile, outfile)
490 # command += '" > ./%s &' % outfile # outputs
491 except TypeError: # in case the OPENMX_COMMAND is incompatible
492 raise ValueError(
493 "The 'OPENMX_COMMAND' environment must " +
494 "be a format string" +
495 " with four string arguments.\n" +
496 "Example : 'mpirun -np 4 openmx ./%s -nt 2 > ./%s'.\n" +
497 "Got '%s'" % command)
498 return command
500 def get_stress(self, atoms=None):
501 if atoms is None:
502 atoms = self.atoms
504 # Note: Stress is only supported from OpenMX 3.8+.
505 stress = self.get_property('stress', atoms)
507 return stress
509 def get_band_structure(self, atoms=None, calc=None):
510 """
511 This is band structure function. It is compatible to
512 ase dft module """
513 from ase.dft import band_structure
514 if isinstance(self['kpts'], tuple):
515 self['kpts'] = self.get_kpoints(band_kpath=self['band_kpath'])
516 return band_structure.get_band_structure(self.atoms, self, )
518 def get_bz_k_points(self):
519 kgrid = self['kpts']
520 if type(kgrid) in [int, float]:
521 kgrid = kptdensity2monkhorstpack(self.atoms, kgrid, False)
522 bz_k_points = []
523 n1 = kgrid[0]
524 n2 = kgrid[1]
525 n3 = kgrid[2]
526 for i in range(n1):
527 for j in range(n2):
528 # Monkhorst Pack Grid [H.J. Monkhorst and J.D. Pack,
529 # Phys. Rev. B 13, 5188 (1976)]
530 for k in range(n3):
531 bz_k_points.append((0.5 * float(2 * i - n1 + 1) / n1,
532 0.5 * float(2 * j - n2 + 1) / n2,
533 0.5 * float(2 * k - n3 + 1) / n3))
534 return np.array(bz_k_points)
536 def get_ibz_k_points(self):
537 if self['band_kpath'] is None:
538 return self.get_bz_k_points()
539 else:
540 return self.get_kpoints(band_kpath=self['band_kpath'])
542 def get_kpoints(self, kpts=None, symbols=None, band_kpath=None, eps=1e-5):
543 """Convert band_kpath <-> kpts"""
544 if kpts is None:
545 kpts = []
546 band_kpath = np.array(band_kpath)
547 band_nkpath = len(band_kpath)
548 for i, kpath in enumerate(band_kpath):
549 end = False
550 nband = int(kpath[0])
551 if band_nkpath == i:
552 end = True
553 nband += 1
554 ini = np.array(kpath[1:4], dtype=float)
555 fin = np.array(kpath[4:7], dtype=float)
556 x = np.linspace(ini[0], fin[0], nband, endpoint=end)
557 y = np.linspace(ini[1], fin[1], nband, endpoint=end)
558 z = np.linspace(ini[2], fin[2], nband, endpoint=end)
559 kpts.extend(np.array([x, y, z]).T)
560 return np.array(kpts, dtype=float)
561 elif band_kpath is None:
562 band_kpath = []
563 points = np.asarray(kpts)
564 diffs = points[1:] - points[:-1]
565 kinks = abs(diffs[1:] - diffs[:-1]).sum(1) > eps
566 N = len(points)
567 indices = [0]
568 indices.extend(np.arange(1, N - 1)[kinks])
569 indices.append(N - 1)
570 for start, end, s_sym, e_sym in zip(indices[1:], indices[:-1],
571 symbols[1:], symbols[:-1]):
572 band_kpath.append({'start_point': start, 'end_point': end,
573 'kpts': 20,
574 'path_symbols': (s_sym, e_sym)})
575 return band_kpath
577 def get_lattice_type(self):
578 cellpar = cell_to_cellpar(self.atoms.cell)
579 abc = cellpar[:3]
580 angles = cellpar[3:]
581 min_lv = min(abc)
582 if abc.ptp() < 0.01 * min_lv:
583 if abs(angles - 90).max() < 1:
584 return 'cubic'
585 elif abs(angles - 60).max() < 1:
586 return 'fcc'
587 elif abs(angles - np.arccos(-1 / 3.) * 180 / np.pi).max < 1:
588 return 'bcc'
589 elif abs(angles - 90).max() < 1:
590 if abs(abc[0] - abc[1]).min() < 0.01 * min_lv:
591 return 'tetragonal'
592 else:
593 return 'orthorhombic'
594 elif abs(abc[0] - abc[1]) < 0.01 * min_lv and \
595 abs(angles[2] - 120) < 1 and abs(angles[:2] - 90).max() < 1:
596 return 'hexagonal'
597 else:
598 return 'not special'
600 def get_number_of_spins(self):
601 try:
602 magmoms = self.atoms.get_initial_magnetic_moments()
603 if self['scf_spinpolarization'] is None:
604 if isinstance(magmoms[0], float):
605 if abs(magmoms).max() < 0.1:
606 return 1
607 else:
608 return 2
609 else:
610 raise NotImplementedError
611 else:
612 if self['scf_spinpolarization'] == 'on':
613 return 2
614 elif self['scf_spinpolarization'] == 'nc' or \
615 np.any(self['initial_magnetic_moments_euler_angles']) \
616 is not None:
617 return 1
618 except KeyError:
619 return 1
621 def get_eigenvalues(self, kpt=None, spin=None):
622 if self.results.get('eigenvalues') is None:
623 self.calculate(self.atoms)
624 if kpt is None and spin is None:
625 return self.results['eigenvalues']
626 else:
627 return self.results['eigenvalues'][spin, kpt, :]
629 def get_fermi_level(self):
630 try:
631 fermi_level = self.results['chemical_potential']
632 except KeyError:
633 self.calculate()
634 fermi_level = self.results['chemical_potential']
635 return fermi_level
637 def get_number_of_bands(self):
638 pag = self.parameters.get
639 dfd = default_dictionary
640 if 'number_of_bands' not in self.results:
641 n = 0
642 for atom in self.atoms:
643 sym = atom.symbol
644 orbitals = pag('dft_data_dict', dfd)[sym]['orbitals used']
645 d = 1
646 for orbital in orbitals:
647 n += d * orbital
648 d += 2
649 self.results['number_of_bands'] = n
650 return self.results['number_of_bands']
652 def dirG(self, dk, bzone=(0, 0, 0)):
653 nx, ny, nz = self['wannier_kpts']
654 dx = dk // (ny * nz) + bzone[0] * nx
655 dy = (dk // nz) % ny + bzone[1] * ny
656 dz = dk % nz + bzone[2] * nz
657 return dx, dy, dz
659 def dk(self, dirG):
660 dx, dy, dz = dirG
661 nx, ny, nz = self['wannier_kpts']
662 return ny * nz * (dx % nx) + nz * (dy % ny) + dz % nz
664 def get_wannier_localization_matrix(self, nbands, dirG, nextkpoint=None,
665 kpoint=None, spin=0, G_I=(0, 0, 0)):
666 # only expected to work for no spin polarization
667 try:
668 self['bloch_overlaps']
669 except KeyError:
670 self.read_bloch_overlaps()
671 dirG = tuple(dirG)
672 nx, ny, nz = self['wannier_kpts']
673 nr3 = nx * ny * nz
674 if kpoint is None and nextkpoint is None:
675 return {kpoint: self['bloch_overlaps'
676 ][kpoint][dirG][:nbands, :nbands
677 ] for kpoint in range(nr3)}
678 if kpoint is None:
679 kpoint = (nextkpoint - self.dk(dirG)) % nr3
680 if nextkpoint is None:
681 nextkpoint = (kpoint + self.dk(dirG)) % nr3
682 if dirG not in self['bloch_overlaps'][kpoint].keys():
683 return np.zeros((nbands, nbands), complex)
684 return self['bloch_overlaps'][kpoint][dirG][:nbands, :nbands]
686 def prind(self, line, debug=None):
687 ''' Print the value if debugging mode is on.
688 Otherwise, it just ignored'''
689 if debug is None:
690 debug = self.debug
691 if debug:
692 print(line)
694 def print_file(self, file=None, running=None, **args):
695 ''' Print the file while calculation is running'''
696 prev_position = 0
697 last_position = 0
698 while not os.path.isfile(file):
699 self.prind('Waiting for %s to come out' % file)
700 time.sleep(5)
701 with open(file, 'r') as fd:
702 while running(**args):
703 fd.seek(last_position)
704 new_data = fd.read()
705 prev_position = fd.tell()
706 # self.prind('pos', prev_position != last_position)
707 if prev_position != last_position:
708 if not self.nohup:
709 print(new_data)
710 last_position = prev_position
711 time.sleep(1)