#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright notice
# ----------------
#
# Copyright (C) 2011-2014 Daniel Jung
# Contact: djungbremen@gmail.com
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
#
"""This module offers different methods for showing the current status of an
iterative algorithm, or to control its execution, for example to abort it as
soon as certain abort criterions are met."""
#
# To do:
# --> write variant of Converge or extend Converge to use standard error as
# criterion
#
__created__ = '2011-09-13'
__modified__ = '2014-01-14'
import calendar
import commands
import datetime
import numpy
import os
import select
import signal
import sys
import termios
import time
import tty
[docs]class Bar(object):
"""Display progress bar of a certain loop. Instantiate it before loop. Call
the method "step" at the end of each loop (don't forget to do the same
before any occurence of "continue"). If loop is left early (break), or if
you want to make sure the line break is done after the loop, call the
method "end". To reset the counter, call the method "reset".
On initialization, the number of iterations "nstep" must be specified.
Additional keyword arguments: text : User-defined text message (instead
of "Progress") width : Width of the current shell window. Default: 80
verbose : If set to False, the progress bar isn't shown etc : If set to
True, show estimated time to complete
This class is only meant to be used in loops where the number of steps is
known before entering the loop. This excludes all iterative algorithms that
leave the loop only on some convergence criterions after a variable number
of steps (e.g., while-loops). Of course, one could "estimate" the number of
loops that will probably be used, but this is not the way this class is
intended to be used.
Future ideas:
--> multiple progress bars at once, to show status of multiple subprocesses
--> allow nested progress bars, passing and multiplying step numbers to the
inner-next instance
--> report elapsed time instead of printing "ETC 0 sec"
--> save start and end times (and duration), to check them later
Written by Daniel Jung, Jacobs University Bremen, Germany (2011-2012).
"""
__created__ = '2011-09-13'
__modified__ = '2012-09-04'
# former tb.Progress from 2011-02-13 until 2011-06-09
def __init__(self, nstep=1, text='progress', width=None, verbose=True,
etc=True):
now = time.time()
self._nstep = nstep
self._step = 0
self._jump = 0
self._oldline = ''
self._end = False
self._text = text
self._verbose = verbose
self._etc = etc # show estimated time of completion
self._starttime = now # starting time
self._lastcall = now # time when step or jump was last called
self._jumptime = 0 # count time that was used only for jumping
# do not show the error bar if number of steps is zero
if self._nstep == 0:
self._verbose = False
# get width of the terminal window
if width is not None:
self._width = width
else:
self._width = get_columns()
# print initial status to the screen
if not self._verbose:
return
self.write()
[docs] def step(self, howmany=1):
"""Move one or several steps forward."""
if not self._verbose:
return
self._step += howmany
if self._step > self._nstep:
self._step = self._nstep
# print new status
self.write()
# note time of this call
now = time.time()
self._lastcall = now
[docs] def jump(self, howmany=1):
"""Skip one or several steps. Using this instead of step is only
important for the correct calculation of the estimated time to complete
(ETC)."""
if not self._verbose:
return
self._step += howmany
if self._step > self._nstep:
self._step = self._nstep
self._jump += howmany
if self._jump > self._step:
self._jump = self._step
# add time difference since last call to the jump time counter
now = time.time()
self._jumptime += now-self._lastcall
# print new status
self.write()
# remember time of this call
self._lastcall = now
[docs] def write(self):
if not self._verbose:
return
now = time.time()
if self._nstep == 1:
# just say "done" when finished
line = self._text+': '
if self._step == 1:
line += 'Done.'
else:
# calculate progress
progress = float(self._step)/self._nstep
# calculate estimated time to complete (ETC), use linear
# extrapolation
etcstring = ''
if self._etc:
if self._nstep-self._jump != 0:
jprogress = \
float(self._step-self._jump)/(self._nstep-self._jump)
jelapsed = now-self._starttime-self._jumptime
if jprogress > 0:
jestimate = jelapsed/jprogress
jdiff = jestimate-jelapsed
etcstring = ' ETC %s' % _nicetime(jdiff)
# calculate length of the progress bar
barlen = self._width - 9 - len(self._text) - len(etcstring) - 1
# create the new line
line = '%s:% 4i%%' % (self._text, progress*100)
if barlen > 0:
line += ' [%- *s]' % (barlen, '='*int(progress*barlen))
line += etcstring
# if line has changed, overwrite the old one
if line != self._oldline:
sys.stdout.write('\r'+line)
sys.stdout.flush()
# remember old line
self._oldline = line
# check if progress bar is complete
if self._step == self._nstep:
self.end()
[docs] def end(self):
if not self._verbose:
return
if not self._end:
sys.stdout.write('\n')
self._end = True
[docs] def reset(self):
self.end()
now = time.time()
self._step = 0
self._jump = 0
self._oldline = ''
self._end = False
self._starttime = now
self._lastcall = now
self._jumptime = 0
def __del__(self):
self.end()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
self.end()
[docs]class OpenBar(object):
"""Display progress bar of an unlimited iteration process ("open-end"). Use
this if the total number of iterations is unclear, i.e. the loop may be
exited at any point (e.g., while loop). However, to display a progress bar,
some measure of progress must still be defined (start and target values).
If the start value is None, use the value given by the first call of step.
Intended use: For example, an iterative averaging process will be aborted
as soon as a certain accuracy has been reached. The progress measure will
be this accuracy.
Future plans:
--> use something different than linear extrapolation"""
# 2012-03-05
def __init__(self, target, start=None, text='progress', width=None,
verbose=True, etc=False):
pass
[docs]class Monitor(object):
"""Monitor the values of certain variables within a loop (e.g., for
iterative algorithms). Uses the class StatusLine.
Update the line by using carriage return each time the method "step" is
called. Do this until the method "end" is called (then, a line break is
issued).
Future plans:
--> support complex numbers"""
# 2011-09-13 - 2012-07-16
# former tb.Monitor from 2011-05-26 - 2011-06-09
def __init__(self, **kwargs):
# define standard formats for some types
self._stdformats = {int: '%i', str: '%s', float: '%.2f',
long: '%lo'} # complex: '%.2f+%.2fi' ??
# fetch special keyword arguments
self._stdformats.update(**kwargs.pop('stdformats', {}))
self._verbose = kwargs.pop('verbose', True)
self._sep = kwargs.pop('sep', ' ')
cols = kwargs.pop('cols', get_columns())
delay = kwargs.pop('delay', 1.)
self._formats = kwargs.pop('formats', {}) # formats dictionary
self._order = kwargs.pop('order', []) # force a certain display order
# store remaining keyword arguments
self._values = kwargs
# define additional attributes
self._lengths = {} # lengths dictionary
# initialize status line object
self._line = StatusLine(delay=delay, cols=cols)
# first output is already displayed at initialisation
if not self._verbose:
return
self._write()
[docs] def update(self, **values):
"""Update one or more values, or add new variables to monitor."""
if not self._verbose:
return
self._values.update(**values)
self._write()
#def update_value(self, name, value):
# """Update one value, or add a new variable to monitor. This is for
# calls from other Cython modules only, as Cython cannot handle
# variable argument lists. So, for each value to update, a separate
# call of this method must be made."""
# 2012-04-25 - 2012-04-26
#if not self._verbose:
#return
#self._values[name] = value
#self._write()
[docs] def end(self):
"""Finish monitoring values, make the line break (if not already done).
Update the line one last time."""
if not self._verbose:
return
self._line.end()
[docs] def reset(self):
"""Reset this object. Empty the list of values and begin a new status
line."""
# 2012-04-26
if not self._verbose:
return
self.end()
self._values = {}
self._write()
def _write(self):
"""Write new line to stdout (overwrite existing line using carriage
return). If now is True, tell the StatusLine instance to ignore the
delay."""
# 2012-07-16
if not self._verbose:
return
# create list of keys. Begin with ordered keys, sort the rest by
# alphabet
keys = self._order[:]
for key in keys[:]:
if key not in self._values:
keys.remove(key)
restkeys = self._values.keys()
restkeys.sort()
for key in restkeys:
if key not in keys:
keys.append(key)
# collect all string representations in a dictionary
strings = {}
for key in keys:
value = self._values[key]
valuetype = type(value)
if value is not None and key in self._formats:
strings[key] = '%s=%s' \
% (key, self._formats[key].__mod__(value))
elif value is not None and valuetype in self._stdformats:
strings[key] = '%s=%s' \
% (key, self._stdformats[valuetype].__mod__(value))
else:
strings[key] = '%s=%s' % (key, repr(value))
# update maximal lengths of the strings
for key in keys:
if key not in self._lengths \
or len(strings[key]) > self._lengths[key]:
self._lengths[key] = len(strings[key])
# create resulting line and pass it to the status line object
line = self._sep.join('%- *s' % (self._lengths[key], strings[key])
for key in keys)
self._line.update(line)
def __del__(self):
return self.end()
[docs] def remove(self, *names):
"""Stop monitoring the specified variables."""
for name in names:
del self._values[name]
del self._lengths[name]
del self._formats[name]
self._write()
[docs] def remove_value(self, name):
"""Stop monitoring the specified variable."""
del self._values[name]
del self._lengths[name]
del self._formats[name]
self._write()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
return self.end()
[docs] def set_delay(self, delay):
"""Set the delay of the StatusLine instance."""
# 2012-07-16
if delay < 0:
raise ValueError('delay must be non-negative float')
self._line.delay = float(delay)
[docs]class Abort(object):
"""Check keyboard buffer if a certain key has been pressed.
Initialize before your loop (e.g., of an iterative algorithm). Check within
the loop body (e.g., break the loop on positive check). Finalize (end)
after the loop. Do not forget to finalize, so that your terminal is put
back into normal mode! Or use it as context manager (use the with
statement), then finalization should be done automatically.
Example:
> import time
> with Abort() as a:
> for i in range(10):
> time.sleep(1) # do stuff
> if a.check():
> break # leave loop early, because "q" has been pressed
> print e.buff"""
# 2012-01-18 - 2013-07-24
def __init__(self, key='q', timeout=0):
"""Initialize. Specify key and timeout. Put terminal into cbreak mode.
Future plans:
--> enable key combinations (e.g., CTRL+q)
--> enable special keys (e.g., ESC)"""
# get key
if len(key) != 1:
raise ValueError('invalid key')
self.key = key
### how to use the ESC key?
# enable this class
self.disabled = False
# initialize the "aborted" flag
self.aborted = False
# initialize other attributes
try:
self.oldattr = termios.tcgetattr(sys.stdin)
except:
self.disabled = True # disable, probably does not work with nohup
return
self.buff = '' # collect all other pressed keys, in case needed
self.timeout = timeout
self.count = 0 # count the total number of checks made
# enter cbreak mode
tty.setcbreak(sys.stdin.fileno())
[docs] def check(self):
"""Check if the key has been pressed in the meanwhile. All other
contents of the keyboard buffer will be lost and cannot be
recovered."""
if self.disabled:
return
if self.aborted:
return True
self.count += 1
while len(select.select([sys.stdin], [], [], self.timeout)[0]) > 0:
char = sys.stdin.read(1)
if char == self.key:
self.aborted = True
return True
self.buff += char
return False
[docs] def end(self):
"""Finalize. Put terminal back into normal mode. Return string
buffer."""
if self.disabled:
return
try:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.oldattr)
except:
pass # should we issue a warning?
return # self.buff # creates a lot of output ("qqqqqqqqqqqqq")
def __del__(self):
self.end()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
self.end()
[docs] def reset(self):
"""Reset the abort handler."""
self.aborted = False
[docs] def report(self):
"""If this handler was triggered, display a message."""
if self.aborted:
print 'Process has been aborted by key press (%s)' % self.key
[docs]class Until(object):
"""Check if a certain date/time has been reached. Can be used to
conveniently confine the execution time of an iterative algorithm.
To do:
--> define word "next", excluding the present day, to allow something like
"next tuesday" ==> even if it is tuesday right now, run until next
tuesday"""
# 2012-06-19 - 2013-07-23
MONTHS = [month.lower() for month in calendar.month_name[1:]]
MONTHS_SHORT = [month.lower() for month in calendar.month_abbr[1:]]
DAYS = [day.lower() for day in calendar.day_name]
DAYS_SHORT = [day.lower() for day in calendar.day_abbr]
DAYNUM = dict(zip(DAYS+DAYS_SHORT, range(7)+range(7)))
MONTHNUM = dict(zip(MONTHS+MONTHS_SHORT, range(1, 13)+range(1, 13)))
SPECIAL = ['tomorrow']
def __init__(self, until=None):
"""Initialize. Specify datetime or duration."""
# 2012-06-25 - 2012-07-05
# remember time when object was created
self.created = time.time() # [seconds since begin of epoch]
# initialize timestamp which will be determined from "until"
self.timestamp = self.created # [seconds since begin of epoch]
if until is None:
self.timestamp = None
elif isinstance(until, int) or isinstance(until, float):
# assume that timestamp is directly given
self.timestamp = float(until)
elif isinstance(until, datetime.datetime):
# support datetime.datetime objects
self.timestamp = time.mktime(until.timetuple())
elif isinstance(until, datetime.timedelta):
# support datetime.timedelta objects
self.timestamp = self.created+until.total_seconds()
elif isinstance(until, basestring):
# parse string
# decide if a date-time combination or a duration is given
# criterion: for a date/time, at least one character out of "/-:."
# or some known phrases (for weekdays or months) must be given
if until == '':
# run infinitely
self.timestamp = None
elif self.isdatetime(until):
# parse datetime
until = until.lower()
for word in until.split(' '):
if self.one_eq(self.DAYS+self.DAYS_SHORT, word):
self.goto_day(word)
elif self.one_eq(self.MONTHS+self.MONTHS_SHORT, word):
self.goto_month(word)
elif word == 'tomorrow':
self.goto_tomorrow()
elif word.count(':') == 1:
hours, minutes = word.split(':')
self.goto_time(hours, minutes)
elif word.count(':') == 2:
hours, minutes, seconds = word.split(':')
self.goto_time(hours, minutes, seconds)
elif len(word) == 4 and self.only_digits(word):
self.goto_year(int(word))
elif len(word) in (1, 2) and self.only_digits(word):
self.goto_mday(int(word))
elif word.count('/') == 2:
### also respect korean dates here, e.g. 10/29/2012
year, month, mday = word.split('/')
self.goto_date(year, month, mday)
elif word.count('-') == 2:
year, month, mday = word.split('-')
self.goto_date(year, month, mday)
elif word.count('.') == 2:
mday, month, year = word.split('.')
#year = None if year == ''
self.goto_date(year, month, mday)
else:
raise ValueError('bad datetime word: %s' % word)
else:
# parse duration
current = ''
dur = 0 # duration in seconds
for char in until:
if char is ' ':
continue
elif char in 'hmsayMwd':
# process current value
if char == 's':
dur += int(current)
elif char == 'm':
dur += int(current)*60
elif char == 'h':
dur += int(current)*3600
elif char == 'd':
dur += int(current)*86400
elif char == 'w':
dur += int(current)*604800
elif char == 'M':
dur += int(current)*2592000 # 1 month == 30 days
elif char in 'ay':
dur += int(current)*31104000 # 1 year == 360 days
# reset current word
current = ''
elif char in '0123456789':
current += char
else:
raise ValueError('bad character: %s' % char)
if current != '':
raise ValueError('bad duration word (missing unit): %s'
% current)
# set timestamp
self.timestamp += dur
# check if timestamp is in the past
if self.timestamp is not None and self.timestamp < self.created:
raise ValueError('given timestamp is in the past: %s'
% time.ctime(self.timestamp))
[docs] def goto_tomorrow(self):
"""Move timestamp forward to the next day."""
# 2012-06-26
tdict = time.localtime(self.timestamp)
tlist = list(tdict)
tlist[2] += 1
self.timestamp = time.mktime(tlist)
self.goback_midnight()
[docs] def goto_date(self, year, month, mday):
"""Move timestamp to the given date."""
# 2012-06-26
# get timestamp
tdict = time.localtime(self.timestamp)
tlist = list(tdict)
# force integer values
month = int(month)
mday = int(mday)
if year != '':
year = int(year)
tlist[:3] = year, month, mday
else:
if month > tdict.tm_mon:
tlist[1:3] = month, mday
elif month < tdict.tm_mon:
tlist[0] += 1 # go to next year
tlist[1:3] = month, mday
else:
if mday > tdict.tm_mday:
tlist[2] = mday
else:
tlist[0] += 1 # go to next year
tlist[2] = mday
# overwrite timestamp
self.timestamp = time.mktime(tlist)
self.goback_midnight()
[docs] def goto_year(self, year):
"""Move timestamp forward to the given year."""
# 2012-06-26
# get timestamp
tdict = time.localtime(self.timestamp)
tlist = list(tdict)
year = int(year)
if year > tdict.tm_year:
tlist[0] = year
elif year < tdict.tm_year:
raise ValueError('given year is in the past: %s' % year)
# overwrite timestamp
self.timestamp = time.mktime(tlist)
[docs] def goto_mday(self, mday):
"""Move timestamp forward to the given day of the month."""
# 2012-06-26
# get timestamp
tdict = time.localtime(self.timestamp)
tlist = list(tdict)
mday = int(mday)
if mday > tdict.tm_mday:
tlist[2] = mday
else:
tlist[1] += 1 # go to next month
tlist[2] = mday
# overwrite timestamp
self.timestamp = time.mktime(tlist)
[docs] def goto_time(self, hours=0, minutes=0, seconds=None):
"""Move timestamp forward to the next given time."""
# 2012-06-26
# force integer values
hours = int(hours)
minutes = int(minutes)
seconds = int(seconds) if seconds is not None else None
# get timestamp
tdict = time.localtime(self.timestamp)
tlist = list(tdict)
# case study: go to next day or not?
if hours > tdict.tm_hour:
tlist[3:6] = [hours, minutes, 0 if seconds is None else seconds]
elif hours < tdict.tm_hour:
tlist[2] += 1 # go to next day
tlist[3:6] = [hours, minutes, 0 if seconds is None else seconds]
else:
if minutes > tdict.tm_min:
tlist[4:6] = [minutes, 0 if seconds is None else seconds]
elif minutes < tdict.tm_min:
tlist[2] += 1 # go to next day
tlist[4:6] = [minutes, 0 if seconds is None else seconds]
else:
if seconds is None:
tlist[2] += 1 # go to next day
tlist[5] = 0
else:
if seconds > tdict.tm_sec:
tlist[5] = seconds
else:
tlist[2] += 1 # go to next day
tlist[5] = seconds
# overwrite timestamp
self.timestamp = time.mktime(tlist)
[docs] def goto_day(self, day):
"""Move timestamp forward to the next given day of the week."""
# 2012-06-25
# move forward a certain number of days
while time.localtime(self.timestamp).tm_wday != self.DAYNUM[day]:
self.timestamp += 86400
self.goback_midnight()
[docs] def goback_midnight(self):
"""Move backward to the beginning of that day (midnight)."""
# 2012-06-25
timetuple = list(time.localtime(self.timestamp))
timetuple[3] = 0
timetuple[4] = 0
timetuple[5] = 0
#timetuple[8] = -1 # because of summertime
self.timestamp = time.mktime(timetuple)
[docs] def goto_month(self, month):
"""Move timestamp forward to the next given month."""
# 2012-06-25 - 2012-06-26
# move forward a certain number of months
timetuple = list(time.localtime(self.timestamp))
while timetuple[1] != self.MONTHNUM[month]:
timetuple[1] += 1
timetuple = list(time.localtime(time.mktime(timetuple)))
self.timestamp = time.mktime(timetuple)
self.goback_first()
self.goback_midnight()
[docs] def goback_first(self):
"""Move backward to the beginning of the first day of the month."""
# 2012-06-26
timetuple = list(time.localtime(self.timestamp))
timetuple[2] = 1
#timetuple[8] = -1 # because of summertime
self.timestamp = time.mktime(timetuple)
[docs] def check(self):
"""Check if the specified time has already been passed."""
# 2012-06-25 - 2012-06-27
return False if self.timestamp is None \
else time.time() > self.timestamp
[docs] def isdatetime(self, string):
"""Decide if the given string contains a date-time combination. If so,
return True. Otherwise, it will contain a duration, and False is
returned."""
# 2012-06-25 - 2012-06-26
return self.one_in(['/', ':', '-', '.', 'tomorrow']+self.MONTHS +
self.MONTHS_SHORT+self.DAYS+self.DAYS_SHORT,
string.lower())
@staticmethod
[docs] def one_in(seq, obj):
"""Return True if at least one element of the given sequence "seq" is
contained in the given object "obj". Otherwise, return False."""
# 2012-06-25
for elem in seq:
if elem in obj:
return True
return False
@staticmethod
[docs] def one_is(seq, obj):
"""Return True if at least one element of the given sequence "seq" is
identical to the object "obj". Otherwise, return False."""
# 2012-06-25
for elem in seq:
if elem is obj:
return True
return False
@staticmethod
[docs] def one_eq(seq, obj):
"""Return True if at least one element of the given sequence "seq" is
equal to the object "obj". Otherwise, return False."""
# 2012-06-25
for elem in seq:
if elem == obj:
return True
return False
@staticmethod
[docs] def only_digits(string):
"""Return True if the given string contains only digits. Otherwise,
return False."""
# 2012-06-26
for char in string:
if char not in '0123456789':
return False
return True
def __iter__(self):
"""Return iterator."""
# 2012-06-26 - 2012-09-04
for element in list(time.localtime(self.timestamp)):
yield element
def __len__(self):
return len(time.localtime(self.timestamp))
def __repr__(self):
# 2012-06-26 - 2012-12-10
if self.timestamp is None:
return '%s()' % self.__class__.__name__
else:
return '%s("%s")' % (self.__class__.__name__,
time.strftime('%a %b %d %X %Z %Y',
time.localtime(self.timestamp)))
def __enter__(self):
# 2012-06-26
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
# 2012-06-26
pass
[docs] def report(self):
"""If this handler was triggered, display a message."""
if self.check():
print 'Timelimit has been reached (%s).' \
% time.ctime(self.timestamp)
[docs]class Converge(object):
"""Check data for convergence criterion in some sort of iteration step of
an interative algorithm. Specify a certain tolerance (accuracy) that should
be reached. Increase the "smooth value" to average over the last few
Deltas.
Future plans:
--> choose from various convergence criterions (also based on standard
error)
--> could choose from mean, gmean, min, max, max-min (peak-to-peak), ...
(find more under http://en.wikipedia.org/wiki/Average)
--> offer relative and absolute versions of each criterion
--> let the user specify his own criterion (as a function object)
--> use Cython, write version that is callable from C, support OpenMP
--> add feature to remember several values (e.g., 5) and check that all the
deltas are small enough (then, it is not enought that "by chance" the
delta value drops below the tolerance)"""
# 2012-03-02 - 2014-01-14
def __init__(self, tol=None, smooth=1): # active=True, criterion=None
# dtype=None, shape=None
# relative=False
self.tol = tol # requested tolerance
self.smooth = int(smooth) # smooth level (average over this number of
# deltas)
# initialize old-data buffer
self._data_old = None
# initialize delta list
# the list will be filled with the number of delta values given by
# smooth
self._delta = []
[docs] def check(self, data):
"""Check convergence criterion. Return True if the requested accuracy
is reached, False otherwise. If data is None, return False."""
if data is None:
return False
# if requested tolerance is None or non-positive, do not check anything
if self.tol is None or self.tol <= 0:
return False
# force numpy array
data = numpy.array(data) # force float datatype?
# on first call, cannot yet check for convergence, return always False
if self._data_old is None:
# write to old-data buffer for the first time
self._data_old = data
return False
# calculate new delta (use quadratic mean for now)
#self._delta.append(numpy.linalg.norm((data-self._data_old)/data)/ \
#numpy.sqrt(data.size))
self._delta.append(numpy.mean(numpy.abs((data-self._data_old)/data)))
# only check the criterion if the number of deltas given by smooth is
# already reached
if len(self._delta) < self.smooth:
return False
# forget oldest delta
if len(self._delta) > self.smooth:
self._delta.pop(0)
# check convergence criterion (average all available deltas)
converged = numpy.isnan(self.tol) \
or numpy.abs(numpy.mean(self._delta)) < self.tol
# update old-data buffer
self._data_old = data
# return truth value
return converged
[docs] def delta(self):
"""Return the current mean delta (based on the last call of "check").
Return -1 if the delta list is still empty."""
if len(self._delta) > 0:
mdelta = numpy.mean(self._delta)
if not numpy.isnan(mdelta):
return mdelta
else:
return -1
else:
return -1
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
#if self._data_old is not None:
#del self._data_old[:] ## difficult in Cython...
pass
[docs] def report(self):
"""If this handler was triggered, display a message."""
if self.check():
print 'Data converged within a tolerance of %g.' % self.tol
[docs]def get_columns():
"""Try to get number of columns of the current terminal window. Otherwise,
return standard (80 columns)."""
# 2012-03-05
try:
# then try environment variable
columns = int(os.environ['COLUMNS'])
except (KeyError, ValueError):
# try tput
try:
columns = int(commands.getoutput('tput cols'))
except ValueError:
# otherwise, assume standard width
columns = 80
return columns
[docs]class StatusLine(object):
"""Show a status line that is updated by the user every once in a while
using "carriage return"."""
# 2012-04-10 - 2012-07-16
def __init__(self, line='', delay=0., cols=None):
"""Initialize status line object. Can be given an initial line, a
minimum delay time and a custom number of columns (terminal width, will
be obtained automatically on Unix systems)."""
# 2012-04-10 - 2012-04-26
# set delay
self.delay = float(delay)
# get number of columns (terminal width)
if cols is None:
self.cols = get_columns()
else:
self.cols = cols
# initialize old line buffer
self._oldline = ''
self._oldtime = time.time()
# remember if line break already occured
self._ended = False
# only reset status line if it was already used
self._started = False
# print line for the first time
self._write(line)
def _write(self, line):
"""Write the given line, replacing the old one."""
# 2012-04-10 - 2012-04-26
if line != '':
self._started = True
oldlen = len(self._oldline)
newlen = len(line)
spaces_needed = oldlen-newlen if oldlen > newlen else 0
sys.stdout.write('\r'+line[:(self.cols-1)]+' '*spaces_needed)
sys.stdout.flush()
# update old line buffer
self._oldline = line
self._oldtime = time.time()
[docs] def update(self, line):
"""Update the line by the given string, replacing the old line.
The line only gets printed if the line has changed, and if the given
delay time has been passed. If now is True, ignore the delay."""
# 2012-04-10 - 2012-07-16
# respect delay time
if time.time()-self._oldtime < self.delay:
return
# check if line has changed
if line == self._oldline:
return
# ok, then replace the old line by the new one
self._write(line)
[docs] def end(self):
"""End status line, make line break (only if this status line has
already been used)."""
# 2012-04-10 - 2012-04-26
if self._started and not self._ended:
sys.stdout.write('\n')
sys.stdout.flush()
self._ended = True
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
return self.end()
def __del__(self):
return self.end()
[docs] def reset(self):
"""Reset status line (make a line break if neccessary and begin a new
status line)."""
# 2012-04-26
self.end()
self._oldline = ''
self._started = False
self._ended = False
[docs]class Terminate(object):
"""Trap the TERM signal (15). For example, you can stop an interative
algorithm in a controlled way, leaving the loop after the next iteration
and still saving the results. This is done by sending the TERM signal to
the process (also remotely), i.e. in the shell you can do "kill 12345", or
you can use "top".
Works on Unix systems (Linux etc.)."""
# 2012-07-16 - 2012-07-17
def __init__(self):
"""Initialize the terminate handler."""
# 2012-07-16
# initialize the flag
self.terminated = False
# set up the signal trap
signal.signal(signal.SIGTERM, self.terminate)
[docs] def terminate(self, signal, frame):
"""If the TERM signal is received, this method is executed, setting the
flag to True."""
# 2012-07-16
# former tb.kpm.__init__._Ados.terminate and
# tb.kpm.__init__._Gdos.terminate
self.terminated = True
[docs] def check(self):
"""Return True if a TERM signal has been received, otherwise False."""
# 2012-07-16
return self.terminated
[docs] def reset(self):
"""Reset the terminate handler, setting the flag back to False and
waiting for a new TERM signal."""
# 2012-07-16
self.terminated = False
signal.signal(signal.SIGTERM, self.terminate)
[docs] def end(self):
"""Remove the trap. Set the action for the TERM signal back to system
standard."""
# 2012-07-16
if signal.getsignal(signal.SIGTERM) != signal.SIG_DFL:
signal.signal(signal.SIGTERM, signal.SIG_DFL)
def __del__(self):
"""Remove the trap, set action back to system defaults before the
handler is deleted."""
# 2012-07-16
self.end()
def __enter__(self):
"""Enable context manager functionality, enter context."""
# 2012-07-16
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
"""Leave context."""
# 2012-07-16
self.end()
[docs] def report(self):
"""If this handler was triggered, display a message."""
if self.terminated:
print 'Process has been terminated'
[docs]def _nicetime(seconds):
"""Return nice string representation of the given number of seconds in a
human-readable format (approximated). Example: 3634 s --> 1 h."""
# 2012-09-04
# copied from tb.misc.nicetime (written 2012-02-17)
from itertools import izip
# create list of time units (must be sorted from small to large units)
units = [{'factor': 1, 'name': 'sec'},
{'factor': 60, 'name': 'min'},
{'factor': 60, 'name': 'hrs'},
{'factor': 24, 'name': 'dys'},
{'factor': 7, 'name': 'wks'},
{'factor': 4, 'name': 'mns'},
{'factor': 12, 'name': 'yrs'}]
value = int(seconds)
for unit1, unit2 in izip(units[:-1], units[1:]):
if value/unit2['factor'] == 0:
return '%i %s' % (value, unit1['name'])
else:
value /= unit2['factor']
return '%i %s' % (value, unit2['name'])