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
35#from ase.calculators.openmx.dos import DOS
38def parse_omx_version(txt):
39 """Parse version number from stdout header."""
40 match = re.search(r'Welcome to OpenMX\s+Ver\.\s+(\S+)', txt, re.M)
41 return match.group(1)
44class OpenMX(FileIOCalculator):
45 """
46 Calculator interface to the OpenMX code.
47 """
49 implemented_properties = [
50 'free_energy', # Same value with energy
51 'energy',
52 'energies',
53 'forces',
54 'stress',
55 'dipole',
56 'chemical_potential',
57 'magmom',
58 'magmoms',
59 'eigenvalues']
61 default_parameters = OpenMXParameters()
63 default_pbs = {
64 'processes': 1,
65 'walltime': "10:00:00",
66 'threads': 1,
67 'nodes': 1
68 }
70 default_mpi = {
71 'processes': 1,
72 'threads': 1
73 }
75 default_output_setting = {
76 'nohup': True,
77 'debug': False
78 }
80 def __init__(self, restart=None,
81 ignore_bad_restart_file=FileIOCalculator._deprecated,
82 label='./openmx', atoms=None, command=None, mpi=None,
83 pbs=None, **kwargs):
85 # Initialize and put the default parameters.
86 self.initialize_pbs(pbs)
87 self.initialize_mpi(mpi)
88 self.initialize_output_setting(**kwargs)
90 FileIOCalculator.__init__(self, restart, ignore_bad_restart_file,
91 label, atoms, command, **kwargs)
93 def __getitem__(self, key):
94 """Convenience method to retrieve a parameter as
95 calculator[key] rather than calculator.parameters[key]
97 Parameters:
98 -key : str, the name of the parameters to get.
99 """
100 return self.parameters[key]
102 def __setitem__(self, key, value):
103 self.parameters[key] = value
105 def initialize_output_setting(self, **kwargs):
106 output_setting = {}
107 self.output_setting = dict(self.default_output_setting)
108 for key, value in kwargs.items():
109 if key in self.default_output_setting:
110 output_setting[key] = value
111 self.output_setting.update(output_setting)
112 self.__dict__.update(self.output_setting)
114 def initialize_pbs(self, pbs):
115 if pbs:
116 self.pbs = dict(self.default_pbs)
117 for key in pbs:
118 if key not in self.default_pbs:
119 allowed = ', '.join(list(self.default_pbs.keys()))
120 raise TypeError('Unexpected keyword "{0}" in "pbs" '
121 'dictionary. Must be one of: {1}'
122 .format(key, allowed))
123 # Put dictionary into python variable
124 self.pbs.update(pbs)
125 self.__dict__.update(self.pbs)
126 else:
127 self.pbs = None
129 def initialize_mpi(self, mpi):
130 if mpi:
131 self.mpi = dict(self.default_mpi)
132 for key in mpi:
133 if key not in self.default_mpi:
134 allowed = ', '.join(list(self.default_mpi.keys()))
135 raise TypeError('Unexpected keyword "{0}" in "mpi" '
136 'dictionary. Must be one of: {1}'
137 .format(key, allowed))
138 # Put dictionary into python variable
139 self.mpi.update(mpi)
140 self.__dict__.update(self.mpi)
141 else:
142 self.mpi = None
144 def run(self):
145 '''Check Which Running method we r going to use and run it'''
146 if self.pbs is not None:
147 run = self.run_pbs
148 elif self.mpi is not None:
149 run = self.run_mpi
150 else:
151 run = self.run_openmx
152 run()
154 def run_openmx(self):
155 def isRunning(process=None):
156 ''' Check mpi is running'''
157 return process.poll() is None
158 runfile = get_file_name('.dat', self.label, absolute_directory=False)
159 outfile = get_file_name('.log', self.label)
160 olddir = os.getcwd()
161 abs_dir = os.path.join(olddir, self.directory)
162 try:
163 os.chdir(abs_dir)
164 if self.command is None:
165 self.command = 'openmx'
166 command = self.command + ' %s > %s'
167 command = command % (runfile, outfile)
168 self.prind(command)
169 p = subprocess.Popen(command, shell=True, universal_newlines=True)
170 self.print_file(file=outfile, running=isRunning, process=p)
171 finally:
172 os.chdir(olddir)
173 self.prind("Calculation Finished")
175 def run_mpi(self):
176 """
177 Run openmx using MPI method. If keyword `mpi` is declared, it will
178 run.
179 """
180 def isRunning(process=None):
181 ''' Check mpi is running'''
182 return process.poll() is None
183 processes = self.processes
184 threads = self.threads
185 runfile = get_file_name('.dat', self.label, absolute_directory=False)
186 outfile = get_file_name('.log', self.label)
187 olddir = os.getcwd()
188 abs_dir = os.path.join(olddir, self.directory)
189 try:
190 os.chdir(abs_dir)
191 command = self.get_command(processes, threads, runfile, outfile)
192 self.prind(command)
193 p = subprocess.Popen(command, shell=True, universal_newlines=True)
194 self.print_file(file=outfile, running=isRunning, process=p)
195 finally:
196 os.chdir(olddir)
197 self.prind("Calculation Finished")
199 def run_pbs(self, prefix='test'):
200 """
201 Execute the OpenMX using Plane Batch System. In order to use this,
202 Your system should have Scheduler. PBS
203 Basically, it does qsub. and wait until qstat signal shows c
204 Super computer user
205 """
206 nodes = self.nodes
207 processes = self.processes
209 prefix = self.prefix
210 olddir = os.getcwd()
211 try:
212 os.chdir(self.abs_directory)
213 except AttributeError:
214 os.chdir(self.directory)
216 def isRunning(jobNum=None, status='Q', qstat='qstat'):
217 """
218 Check submitted job is still Running
219 """
220 def runCmd(exe):
221 p = subprocess.Popen(exe, stdout=subprocess.PIPE,
222 stderr=subprocess.STDOUT,
223 universal_newlines=True)
224 while True:
225 line = p.stdout.readline()
226 if line != '':
227 # the real code does filtering here
228 yield line.rstrip()
229 else:
230 break
231 jobs = runCmd('qstat')
232 columns = None
233 for line in jobs:
234 if str(jobNum) in line:
235 columns = line.split()
236 self.prind(line)
237 if columns is not None:
238 return columns[-2] == status
239 else:
240 return False
242 inputfile = self.label + '.dat'
243 outfile = self.label + '.log'
245 bashArgs = "#!/bin/bash \n cd $PBS_O_WORKDIR\n"
246 jobName = prefix
247 cmd = bashArgs + \
248 'mpirun -hostfile $PBS_NODEFILE openmx %s > %s' % (
249 inputfile, outfile)
250 echoArgs = ["echo", "$' %s'" % cmd]
251 qsubArgs = ["qsub", "-N", jobName, "-l", "nodes=%d:ppn=%d" %
252 (nodes, processes), "-l", "walltime=" + self.walltime]
253 wholeCmd = " ".join(echoArgs) + " | " + " ".join(qsubArgs)
254 self.prind(wholeCmd)
255 out = subprocess.Popen(wholeCmd, shell=True,
256 stdout=subprocess.PIPE, universal_newlines=True)
257 out = out.communicate()[0]
258 jobNum = int(re.match(r'(\d+)', out.split()[0]).group(1))
260 self.prind('Queue number is ' + str(jobNum) +
261 '\nWaiting for the Queue to start')
262 while isRunning(jobNum, status='Q'):
263 time.sleep(5)
264 self.prind('.')
265 self.prind('Start Calculating')
266 self.print_file(file=outfile, running=isRunning,
267 jobNum=jobNum, status='R', qstat='qstat')
269 os.chdir(olddir)
270 self.prind('Calculation Finished!')
271 return jobNum
273 def clean(self, prefix='test', queue_num=None):
274 """Method which cleans up after a calculation.
276 The default files generated OpenMX will be deleted IF this
277 method is called.
279 """
280 self.prind("Cleaning Data")
281 fileName = get_file_name('', self.label)
282 pbs_Name = get_file_name('', self.label)
283 files = [
284 # prefix+'.out',#prefix+'.dat',#prefix+'.BAND*',
285 fileName + '.cif', fileName + '.dden.cube', fileName + \
286 '.ene', fileName + '.md', fileName + '.md2',
287 fileName + '.tden.cube', fileName + '.sden.cube', fileName + \
288 '.v0.cube', fileName + '.v1.cube',
289 fileName + '.vhart.cube', fileName + '.den0.cube', fileName + \
290 '.bulk.xyz', fileName + '.den1.cube',
291 fileName + '.xyz', pbs_Name + '.o' + \
292 str(queue_num), pbs_Name + '.e' + str(queue_num)
293 ]
294 for f in files:
295 try:
296 self.prind("Removing" + f)
297 os.remove(f)
298 except OSError:
299 self.prind("There is no such file named " + f)
301 def calculate(self, atoms=None, properties=None,
302 system_changes=all_changes):
303 """
304 Capture the RuntimeError from FileIOCalculator.calculate
305 and add a little debug information from the OpenMX output.
306 See base FileIOCalculator for documentation.
307 """
308 if self.parameters.data_path is None:
309 if 'OPENMX_DFT_DATA_PATH' not in os.environ:
310 warnings.warn('Please either set OPENMX_DFT_DATA_PATH as an'
311 'enviroment variable or specify "data_path" as'
312 'a keyword argument')
314 self.prind("Start Calculation")
315 if properties is None:
316 properties = self.implemented_properties
317 try:
318 Calculator.calculate(self, atoms, properties, system_changes)
319 self.write_input(atoms=self.atoms, parameters=self.parameters,
320 properties=properties,
321 system_changes=system_changes)
322 self.print_input(debug=self.debug, nohup=self.nohup)
323 self.run()
324 # self.read_results()
325 self.version = self.read_version()
326 output_atoms = read_openmx(filename=self.label, debug=self.debug)
327 self.output_atoms = output_atoms
328 # XXX The parameters are supposedly inputs, so it is dangerous
329 # to update them from the outputs. --askhl
330 self.parameters.update(output_atoms.calc.parameters)
331 self.results = output_atoms.calc.results
332 # self.clean()
333 except RuntimeError as e:
334 try:
335 with open(get_file_name('.log'), 'r') as fd:
336 lines = fd.readlines()
337 debug_lines = 10
338 print('##### %d last lines of the OpenMX output' % debug_lines)
339 for line in lines[-20:]:
340 print(line.strip())
341 print('##### end of openMX output')
342 raise e
343 except RuntimeError as e:
344 raise e
346 def write_input(self, atoms=None, parameters=None,
347 properties=[], system_changes=[]):
348 """Write input (dat)-file.
349 See calculator.py for further details.
351 Parameters:
352 - atoms : The Atoms object to write.
353 - properties : The properties which should be calculated.
354 - system_changes : List of properties changed since last run.
355 """
356 # Call base calculator.
357 if atoms is None:
358 atoms = self.atoms
359 FileIOCalculator.write_input(self, atoms, properties, system_changes)
360 write_openmx(label=self.label, atoms=atoms, parameters=self.parameters,
361 properties=properties, system_changes=system_changes)
363 def print_input(self, debug=None, nohup=None):
364 """
365 For a debugging purpose, print the .dat file
366 """
367 if debug is None:
368 debug = self.debug
369 if nohup is None:
370 nohup = self.nohup
371 self.prind('Reading input file'+self.label)
372 filename = get_file_name('.dat', self.label)
373 if not nohup:
374 with open(filename, 'r') as fd:
375 while True:
376 line = fd.readline()
377 print(line.strip())
378 if not line:
379 break
381 def read(self, label):
382 self.parameters = {}
383 self.set_label(label)
384 if label[-5:] in ['.dat', '.out', '.log']:
385 label = label[:-4]
386 atoms = read_openmx(filename=label, debug=self.debug)
387 self.update_atoms(atoms)
388 self.parameters.update(atoms.calc.parameters)
389 self.results = atoms.calc.results
390 self.parameters['restart'] = self.label
391 self.parameters['label'] = label
393 def read_version(self, label=None):
394 version = None
395 if label is None:
396 label = self.label
397 for line in open(get_file_name('.out', label)):
398 if line.find('Ver.') != -1:
399 version = line.split()[-1]
400 break
401 return version
403 def update_atoms(self, atoms):
404 self.atoms = atoms.copy()
406 def set(self, **kwargs):
407 """Set all parameters.
409 Parameters:
410 -kwargs : Dictionary containing the keywords defined in
411 OpenMXParameters.
412 """
414 for key, value in kwargs.items():
415 if key not in self.default_parameters.keys():
416 raise KeyError('Unkown keyword "%s" and value "%s".' %
417 (key, value))
418 if key == 'xc' and value not in self.default_parameters.allowed_xc:
419 raise KeyError('Given xc "%s" is not allowed' % value)
420 if key in ['dat_arguments'] and isinstance(value, dict):
421 # For values that are dictionaries, verify subkeys, too.
422 default_dict = self.default_parameters[key]
423 for subkey in kwargs[key]:
424 if subkey not in default_dict:
425 allowed = ', '.join(list(default_dict.keys()))
426 raise TypeError('Unknown subkeyword "{0}" of keyword '
427 '"{1}". Must be one of: {2}'
428 .format(subkey, key, allowed))
430 # Find out what parameter has been changed
431 changed_parameters = {}
432 for key, value in kwargs.items():
433 oldvalue = self.parameters.get(key)
434 if key not in self.parameters or not equal(value, oldvalue):
435 changed_parameters[key] = value
436 self.parameters[key] = value
438 # Set the parameters
439 for key, value in kwargs.items():
440 # print(' Setting the %s as %s'%(key, value))
441 self.parameters[key] = value
443 # If Changed Parameter is Critical, we have to reset the results
444 for key, value in changed_parameters.items():
445 if key in ['xc', 'kpts', 'energy_cutoff']:
446 self.results = {}
448 value = kwargs.get('energy_cutoff')
449 if value is not None and not (isinstance(value, (float, int))
450 and value > 0):
451 mess = "'%s' must be a positive number(in eV), \
452 got '%s'" % ('energy_cutoff', value)
453 raise ValueError(mess)
455 atoms = kwargs.get('atoms')
456 if atoms is not None and self.atoms is None:
457 self.atoms = atoms.copy()
459 def set_results(self, results):
460 # Not Implemented fully
461 self.results.update(results)
463 def get_command(self, processes, threads, runfile=None, outfile=None):
464 # Contruct the command to send to the operating system
465 abs_dir = os.getcwd()
466 command = ''
467 self.prind(self.command)
468 if self.command is None:
469 self.command = 'openmx'
470 # run processes specified by the system variable OPENMX_COMMAND
471 if processes is None:
472 command += os.environ.get('OPENMX_COMMAND')
473 if command is None:
474 warnings.warn('Either specify OPENMX_COMMAND as an environment\
475 variable or specify processes as a keyword argument')
476 else: # run with a specified number of processes
477 threads_string = ' -nt ' + str(threads)
478 if threads is None:
479 threads_string = ''
480 command += 'mpirun -np ' + \
481 str(processes) + ' ' + self.command + ' %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 type(self['kpts']) is 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)