From 32378fa6092c8e57c1e84d7750a365cec0c79009 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 9 Mar 2020 13:39:20 +0100 Subject: [PATCH] simpler use of progress bar now written as class (avoids the managing of function attributes) which is used as a small wrapper around a loop. ETA is shown, based on datetime class which hast nice formatting --- python/damask/result.py | 9 ++- python/damask/util.py | 127 +++++++++++++++++++++++++--------------- 2 files changed, 84 insertions(+), 52 deletions(-) diff --git a/python/damask/result.py b/python/damask/result.py index f58b57f23..09952a4e3 100644 --- a/python/damask/result.py +++ b/python/damask/result.py @@ -17,7 +17,7 @@ from . import Orientation from . import Environment from . import grid_filters -class Result(): +class Result: """ Read and write to DADF5 files. @@ -925,10 +925,9 @@ class Result(): groups = self.groups_with_datasets(datasets.values()) default_arg = partial(self._job,func=func,datasets=datasets,args=args,lock=lock) - util.progressBar(iteration=0,total=len(groups)) - for i,result in enumerate(pool.imap_unordered(default_arg,groups)): - util.progressBar(iteration=i+1,total=len(groups)) - if not result: continue + for result in util.show_progress(pool.imap_unordered(default_arg,groups),len(groups)): + if not result: + continue lock.acquire() with h5py.File(self.fname, 'a') as f: try: diff --git a/python/damask/util.py b/python/damask/util.py index 412142490..365bb9262 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -1,9 +1,9 @@ import sys -import time +import datetime import os import subprocess import shlex -from fractions import Fraction +import fractions from functools import reduce from optparse import Option @@ -11,10 +11,10 @@ import numpy as np class bcolors: """ - ASCII Colors (Blender code). + ASCII Colors. https://svn.blender.org/svnroot/bf-blender/trunk/blender/build_files/scons/tools/bcolors.py - http://stackoverflow.com/questions/287871/print-in-terminal-with-colors-using-python + https://stackoverflow.com/questions/287871 """ HEADER = '\033[95m' @@ -38,18 +38,18 @@ class bcolors: self.BOLD = '' self.UNDERLINE = '' self.CROSSOUT = '' - + def srepr(arg,glue = '\n'): r""" Join arguments as individual lines. - + Parameters ---------- arg : iterable Items to join. glue : str, optional - Defaults to \n. + Defaults to \n. """ if (not hasattr(arg, "strip") and @@ -62,13 +62,13 @@ def srepr(arg,glue = '\n'): def croak(what, newline = True): """ Write formated to stderr. - + Parameters ---------- what : str or iterable Content to be displayed newline : bool, optional - Separate items of what by newline. Defaults to True. + Separate items of what by newline. Defaults to True. """ if not what: @@ -165,63 +165,96 @@ class extendableOption(Option): Option.take_action(self, action, dest, opt, value, values, parser) -def progressBar(iteration, total, prefix='', bar_length=50): +class _ProgressBar: """ - Call in a loop to create terminal progress bar. - - From https://gist.github.com/aubricus/f91fb55dc6ba5557fbab06119420dd6a + Report progress of an interation as a status bar. + + Works for 0-based loops, ETA is estimated by linear extrapolation. + """ + + def __init__(self,total,prefix,bar_length): + """ + Inititalize a progress bar to current time as basis for ETA estimation. + + Parameters + ---------- + total : int + Total # of iterations. + prefix : str + Prefix string. + bar_length : int + Character length of bar. + + """ + self.total = total + self.prefix = prefix + self.bar_length = bar_length + self.start_time = datetime.datetime.now() + self.last_fraction = 0.0 + + sys.stderr.write('{} {} 0% ETA n/a'.format(self.prefix, '░'*self.bar_length)) + sys.stderr.flush() + + def update(self,iteration): + + fraction = (iteration+1) / self.total + + if int(self.bar_length * fraction) > int(self.bar_length * self.last_fraction): + delta_time = datetime.datetime.now() - self.start_time + remaining_time = (self.total - (iteration+1)) * delta_time / (iteration+1) + remaining_time -= datetime.timedelta(microseconds=remaining_time.microseconds) # remove μs + + filled_length = int(self.bar_length * fraction) + bar = '█' * filled_length + '░' * (self.bar_length - filled_length) + sys.stderr.write('\r{} {} {:>4.0%} ETA {}'.format(self.prefix, bar, fraction, remaining_time)) + sys.stderr.flush() + + self.last_fraction = fraction + + if iteration == self.total - 1: + sys.stderr.write('\n') + sys.stderr.flush() + +def show_progress(iterable,N_iter=None,prefix='',bar_length=50): + """ + Decorate a loop with a status bar. + + Use similar like enumerate. Parameters ---------- - iteration : int - Current iteration. - total : int - Total iterations. - prefix : str, optional + iterable : iterable/function with yield statement + Iterable (or function with yield statement) to be decorated. + N_iter : int + Total # of iterations. Needed if number of iterations can not be obtained as len(iterable). + prefix : str, optional. Prefix string. bar_length : int, optional Character length of bar. Defaults to 50. """ - fraction = iteration / float(total) - if not hasattr(progressBar, "last_fraction"): # first call to function - progressBar.start_time = time.time() - progressBar.last_fraction = -1.0 - remaining_time = ' n/a' + if N_iter: + status = _ProgressBar(N_iter,prefix,bar_length) else: - if fraction <= progressBar.last_fraction or iteration == 0: # reset: called within a new loop - progressBar.start_time = time.time() - progressBar.last_fraction = -1.0 - remaining_time = ' n/a' - else: - progressBar.last_fraction = fraction - remainder = (total - iteration) * (time.time()-progressBar.start_time)/iteration - remaining_time = '{: 3d}:'.format(int( remainder//3600)) + \ - '{:02d}:'.format(int((remainder//60)%60)) + \ - '{:02d}' .format(int( remainder %60)) + status = _ProgressBar(len(iterable),prefix,bar_length) - filled_length = int(round(bar_length * fraction)) - bar = '█' * filled_length + '░' * (bar_length - filled_length) - - sys.stderr.write('\r{} {} {}'.format(prefix, bar, remaining_time)), - - if iteration == total: - sys.stderr.write('\n') - sys.stderr.flush() + for i,item in enumerate(iterable): + yield item + status.update(i) def scale_to_coprime(v): """Scale vector to co-prime (relatively prime) integers.""" MAX_DENOMINATOR = 1000 - + def get_square_denominator(x): """Denominator of the square of a number.""" - return Fraction(x ** 2).limit_denominator(MAX_DENOMINATOR).denominator - + return fractions.Fraction(x ** 2).limit_denominator(MAX_DENOMINATOR).denominator + def lcm(a, b): """Least common multiple.""" return a * b // np.gcd(a, b) - + denominators = [int(get_square_denominator(i)) for i in v] s = reduce(lcm, denominators) ** 0.5 m = (np.array(v)*s).astype(np.int) @@ -230,7 +263,7 @@ def scale_to_coprime(v): class return_message(): """Object with formatted return message.""" - + def __init__(self,message): """ Sets return message. @@ -242,7 +275,7 @@ class return_message(): """ self.message = message - + def __repr__(self): """Return message suitable for interactive shells.""" return srepr(self.message)