#!/usr/bin/python

# tzlib.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       July 22, 2003           bar
#       October 7, 2003         bar     ambiguous_file_list
#       January 31, 2004        bar     find_in_array
#       September 28, 2004      bar     define False and True
#       October 25, 2004        bar     hash sort routines (built in already?)
#       January 12, 2005        bar     array_find
#       January 13, 2005        bar     file_name_able and ascii and crc32 routines moved to here
#       January 17, 2005        bar     add <>+'" to the characters changed by file_name_able
#                                       fix __ALL__'s file_name_able name
#       January 30, 2005        bar     _ascii() call -> ascii() - and make it right and import re
#       February 9, 2005        bar     tz_vector_cosine
#       February 17, 2005       bar     html character entities
#       February 25, 2005       bar     safe_html
#       March 1, 2005           bar     option to translate &nbsp; to space or whatever
#       March 19, 2005          bar     typo in ascii - StringType's
#       June 27, 2005           bar     collapse array_find in to find_in_array
#                                       without_dupes
#       July 12, 2005           bar     check crc
#       August 24, 2005         bar     more fooling with the new python warnings about 32 bit ints in crc logic
#       September 5, 2005       bar     look up any of an array of strings in find_in_array / array_find
#       March 25, 2006          bar     try to avoid a latin1 to ascii fuss
#       April 26, 2006          bar     strrev
#       June 14, 2006           bar     string_pairs and flat_positional_strings
#       June 16, 2006           bar     de_html_str
#       June 21, 2006           bar     pull scripts out in de_html_str
#       May 5, 2007             bar     linear_regression
#       May 16, 2007            bar     zlib crc
#       May 17, 2007            bar     printable
#       June 22, 2007           bar     comment
#       July 1, 2007            bar     longitudinal parity (xor sum)
#       October 12, 2007        bar     distance / direction routines (here so I don't need to look 'em up again)
#       November 18, 2007       bar     turn on doxygen
#       November 20, 2007       bar     move lf_only() and no_blank_lines() from strip_files.py
#                                       lf_only() fixes \r\r\n to be one \n
#                                       as does safe_html() make \r\r\n one <BR>
#                                       c_string()
#                                       read_whole_text_file() (finally)
#                                       read_whole_binary_file() (ditto)
#       November 27, 2007       bar     lf_only_with_no_trailing_white_space()
#       November 27, 2007       bar     insert boilerplate copyright
#       December 1, 2007        bar     elapsed_time()
#       December 14, 2007       bar     write the file in write_whole_...
#       December 15, 2007       bar     multiline_strip
#       December 21, 2007       bar     maybe_wrap_with_cdata
#       January 1, 2008         bar     s_except_1()
#       January 18, 2008        bar     unicode_byte_string()
#                                       and auto-convert unicode to utf-16 or utf-8 when writing whole files
#       January 20, 2008        bar     same_object() (for doc purposes)
#       January 21, 2008        bar     s_except_1 takes lists and dictionarys
#       January 29, 2008        bar     sys_err_file_line()
#       February 8, 2008        bar     finally, a binary_search() i can remember
#                                       fix blkcrc32() under python 2.2 (zlib's crc isn't right, apparently)
#                                       add start/end indices to find_in_array and array_find
#       March 9, 2008           bar     temp_file_name
#       March 12, 2008          bar     allow arrays to de_html_str()
#       March 13, 2008          bar     comment
#       May 17, 2008            bar     email adr
#       August 13, 2008         bar     make_dictionary works for strings
#       August 18, 2008         bar     use basestring
#       August 29, 2008         bar     basestring instead of StringType because of unicode strings and others
#       October 28, 2008        bar     float_regx_str
#       November 6, 2008        bar     bool_to_0_or_1()
#       November 15, 2008       bar     egad! I've been making default params as [] and {}
#       November 28, 2008       bar     decode_html_entities latin1/unicode fixes
#                                       print_exception()
#       December 20, 2008       bar     INITIAL_CRC32_VALUE
#       December 28, 2008       bar     ooops, it should be zero, not -1
#       January 7, 2009         bar     excel_column_name
#       January 9, 2009         bar     list_lstrip()
#       February 20, 2009       bar     golden_smaller()
#       March 27, 2009          bar     python 2.6
#       April 1, 2009           bar     printable_str()
#       April 11, 2009          bar     run test ok under python 2.5
#       April 15, 2009          bar     max_index() and min_index()
#       June 2, 2009            bar     find_upper_dir()
#       September 2, 2009       bar     fix find_upper_dir for when the dir is not found (unix needs testing)
#       September 9, 2009       bar     radian_angle_difference()
#       September 16, 2009      bar     find_upper_file_or_dir
#       November 13, 2009       bar     crc16
#       November 28, 2009       bar     file_signature()
#       January 24, 2010        bar     force 16 bit crc to be 16 bits
#       February 10, 2010       bar     get_tid() for linux
#       February 11, 2010       bar     value_array_for_key()
#                                       replace_value_array_for_key()
#       February 20, 2010       bar     windows version of get_tid
#       March 14, 2010          bar     pickle file by file name routines
#       July 22, 2010           bar     keep replace_file inside the only place it's used (and shouldn't be here, anyway)
#       September 23, 2010      bar     make_index_dictionary() and update_all_case_keys()
#       September 26, 2010      bar     print_stack()
#       September 28, 2010      bar     c_ctrl_esc()
#       October 1, 2010         bar     C, C++ comment remover
#       October 2, 2010         bar     safe whole file read/write
#                                       safe_relpath()
#       October 10, 2010        bar     base 36
#       October 19, 2010        bar     best_ascii()
#       October 24, 2010        bar     de_dupe_str()
#       October 27, 2010        bar     find_arg()
#       November 2, 2010        bar     allow update_all_case_keys() to accept int and other keys
#       November 5, 2010        bar     flatten_array()
#       November 7, 2010        bar     expand user and vars in ambiguous file name finder
#       November 9, 2010        bar     c_string changes \ to \134 rather than \\ so that there are no doubled slashes
#       November 16, 2010       bar     same_file()
#                                       reroute_stdout_err()
#       December 5, 2010        bar     multiline_flush_left()
#                                       invert_dictionary()
#       December 27, 2010       bar     safer pickling
#                                       allow printable to zap bad chars
#       February 8, 2011        bar     make unpickle safer ('module' object has no attribute '---class---')
#       March 21, 2011          bar     line intersections
#       March 27, 2011          bar     allow smart proto to pickle_file
#       April 14, 2011          bar     under non-win32, put the .cfg file in get_ini_or_cfg_file_name() in an app directory, not at the user's home dir
#       June 15, 2011           bar     faster max_index and min_index
#       August 9, 2011          bar     let those faster _index routines work with numpy arrays
#       November 5, 2011        bar     can_run_program()
#       November 6, 2011        bar     whack_file()
#       November 13, 2011       bar     q_get()
#       November 29, 2011       bar     pyflake cleanup
#                                       allow make_dictionary to take a dictionary (it's shallow duped, effectively)
#       November 30, 2011       bar     uh. test it
#       December 14, 2011       bar     kalman filter
#                                       get rid of ALL
#       December 23, 2011       bar     cmp_str_with_ints()
#       January 18, 2012        bar     left_valley(), rite_valley()
#       February 4, 2012        bar     run_program() and in_screen_saver()
#       February 7, 2012        bar     add stderr to the output string in run_program
#       February 11, 2012       bar     wdhms_str()
#       March 8, 2012           bar     buples_to_dictionary()
#       March 11, 2012          bar     weighted_choice()
#       March 15, 2012          bar     make ambiguous_file_list() safer
#       May 1, 2012             bar     file_name_able() uses best_ascio to turn latin 1 characters in to ascii (but really should leave them for modern OS's)
#       May 25, 2012            bar     fix a comment
#       May 27, 2012            bar     doxygen namespace
#       July 8, 2012            bar     same_time_ish()
#       August 19, 2012         bar     do line segments intersect using a trick
#       September 1, 2012       bar     ext_ambiguous_file_list()
#                                       kmeans clustering
#       September 19, 2012      bar     restricted_eval()
#       October 28, 2012        bar     regression rtn can handle missing data
#       November 14, 2012       bar     get rid of too-generic command line args for re-routing std out/err
#       December 2, 2012        bar     a_pickleable_pil_image
#       December 19, 2012       bar     "Doing gravity right"
#       January 26, 2013        bar     get_full_user_name()
#       May 19, 2013            bar     rotate 2d array
#       May 21, 2013            bar     catch more (all) errors in the unpickle routine
#       July 8, 2013            bar     stdin input parameter to run_program()
#       July 25, 2013           bar     pil_tostring() and pil_fromstring() to avoid pillow's deprecation warnings in most code
#                                       best_w_h_fit_scale()
#       September 6, 2013       bar     way for caller to get to the proc in run_program
#       September 17, 2013      bar     comment
#       September 23, 2013      bar     correct namespace
#                                       triangle rtns
#       September 30, 2013      bar     make_q_empty()
#                                       handle more exceptions in q_get()
#       October 1, 2013         bar     fix cmp_str_with_ints
#       October 9, 2013         bar     find_argi()
#       October 17, 2013        bar     make reroute_stdout_err more robust
#       October 20, 2013        bar     best_line_fit()
#       October 22, 2013        bar     area_of_irregular_polygon()
#       November 24, 2013       bar     raise value error for vertical linear regression lines
#       November 27, 2013       bar     better angle difference code
#                                       radian_angle_from_horizontal()
#       November 29, 2013       bar     a_min_max()
#                                       return value from whack_file()
#       December 3, 2013        bar     golden_rectangle() and ilk
#       February 24, 2014       bar     make a_min_max robust in the face of no file name and None being attempted to be counted
#       February 25, 2014       bar     make_thread_listing()
#       March 1, 2014           bar     move the Windows .ini file dir to .app in C:\users\USER_NAME or, under XP to C:\Documents and Settings\USER_NAME
#                                       that is, move it to where it can be found, but put a dot in front of the name
#                                       change the Linux .ini file name to .ini from .cfg
#       April 13, 2014          bar     dink with close_fds in run_program()
#                                       cache the full user name
#                                       run_shell_program()
#                                       by default, in the run_program routines, route stderr to stdout
#       April 25, 2014          bar     allow caller to pass allowed things to the restricted eval and exec routines
#                                       allow enumerate() and sum() in restricted eval and exec
#       May 11, 2014            bar     create_*_id rtns
#       May 18, 2014            bar     get_system_wide_lock()
#       May 28, 2014            bar     show tid (htop's PID) in thread listing
#       May 29, 2014            bar     can_run_program can now handle None or ""
#       June 13, 2014           bar     decode latin1 in de_html_str
#       June 14, 2014           bar     undotted_file_name_able()
#                                       allow z to distance_from_x_y
#                                       put the short-form thread listing at the bottom of the printout, not the top
#       June 26, 2014           bar     release_system_wide_lock()
#       July 1, 2014            bar     whack_dir() and whack_full_dir()
#       July 2, 2014            bar     average angle and modulo - but they don't work
#       July 3, 2014            bar     file_name_able won't allow file names starting with dashes
#                                       satisfy myself about the average angle logic - I still don't like it when the angles are "local" to each other. In a small area on Earth, the land appears flat and average location is different on flatland than on curve-land
#                                       expand_user_vars(), finally
#       July 16, 2014           bar     drives and sizes
#       July 17, 2014           bar     get_mount_point()
#       July 29, 2014           bar     sha_directory() and sha_dir_compare()
#       July 30, 2014           bar     str_base()
#       August 19, 2014         bar     reverse tick turned to underline in file_name_able()
#       August 28, 2014         bar     safe_file_datetime
#       September 2, 2014       bar     option to not strip blank lines from run_program()
#                                       don't allow pipe | in file_name_able()
#       September 3, 2014       bar     convert_to_unicode()
#                                       sort_numerically() and its ilk
#       September 18, 2014      bar     whack_dir_contents()
#       October 3, 2014         bar     rename a dir to be whacked. and try to get rid of as much of it as can be gotten rid of.
#       October 14, 2014        bar     put the whack full dir tmp name in a variable so it can be changed by outsiders
#       October 21, 2014        bar     not_in()
#       October 28, 2014        bar     best_ascii() comment
#       November 13, 2014       bar     pop_slice()
#       January 18, 2015        bar     disappear_window()
#       January 20, 2015        bar     protect get_disk_space() against bad Windows drives.
#       January 27, 2015        bar     read_whole rtns take optional how_many param
#       April 10, 2015          bar     point_in_polygon(), but not finished
#       April 11, 2015          bar     go with calling the right/bottom edges outside the polygon (with an exception or two when they are angled)
#                                       relatively untested clip_polygon()
#                                       rotate_point()
#       May 2, 2015             bar     compass_angle()
#       May 4, 2015             bar     sort_kmeans_clusters and fix a bug for None values in kmeans logic
#       May 10, 2015            bar     torus_distance()
#       May 16, 2015            bar     square hilbert curver rtns
#                                       bit routines
#                                       make_color_gradient and make_color_wheel
#       May 17, 2015            bar     gray_code and make un_gray_code more powerful
#                                       balanced_8_bit_gray_code8
#       May 24, 2015            bar     median()
#                                       min_array() and max_array() return -1 for empty arrays rather than zero and use argmin/max for numpy arrays
#                                       as_integer_ratio()
#       May 25, 2015            bar     table lookup bit_count
#       June 1, 2015            bar     couple of special cases in median weren't handled right
#                                       flatten arrays for median
#                                       flatten numpy arrays in flatten_array()
#       June 21, 2015           bar     cumsum_sum() and cumsum_average()
#                                       cleaner handling of array copy in median
#       June 23, 2015           bar     sense numpy arrays in cumsum()
#       July 28, 2015           bar     comment
#       July 29, 2015           bar     fix a special case in cumsum()
#       August 4, 2015          bar     make median work for numpy arrays
#                                       is_pil_image() and is_numpy_array()
#       August 6, 2015          bar     find_argi_and_del()
#       August 7, 2015          bar     de_bruijn()
#       October 15, 2015        bar     in_screen_saver() handles xscreensaver
#       October 23, 2015        bar     unicode_strftime()
#       November 6, 2015        bar     use Window's GetTickCount for elapsed_time
#       November 10, 2015       bar     safe_file_size()
#       November 21, 2015       bar     use ShowWindow in disappear_window() if possible because it seems to work on Win7, 64-bit program
#       November 28, 2015       bar     safe_makedirs()
#       December 15, 2015       bar     underscore square brackets in file_name_able()
#       December 16, 2015       bar     inside_daytime_window()
#       December 29, 2015       bar     inside_daytime_window() allow 11pm to 2am types of periods
#                                       inside_daytime_window() allow out of day-time times - to never match, for instance
#       January 4, 2016         bar     svn_info() and svn_simple()
#       January 23, 2016        bar     add_to_file() and add_print_to_file()
#                                       safer safe_ rtns
#       January 26, 2016        bar     whack_bak_files() and whack_bak_tmp_files()
#       January 31, 2016        bar     create_valid_id doesn't create 1 too high
#       February 2, 2016        bar     fix that valid id fix so it won't break old regression tests
#       February 23, 2016       bar     be careful in run_program with the proc array pop
#       March 28, 2016          bar     make_numbered_file()
#       March 29, 2016          bar     fix bug in make_dictionary() - if given an array of length-2 strings, for instance, as upate can take a [ [ k, w ], [ k, w ], ... ] arg
#       April 9, 2016           bar     whack_files()
#                                       split_path()
#                                       common_path()
#       April 22, 2016          bar     KILOMETERS_TO_MILES
#       May 11, 2016            bar     don't create a uid with a leading zero
#       June 14, 2016           bar     put in a doc string some things to search for s_except_1()
#       August 23, 2016         bar     better rotate() for a_point()
#       August 24, 2016         bar     make_2D_starburst()
#       August 27, 2016         bar     append_to_file_name()
#       December 24, 2016       bar     add a comment in get_full_user_name()
#       January 12, 2017        bar     any-dimension numpy cumsum (1 and 2 d tested)
#                                       numpy cumsum converts to doubles before doing it so there are not overflows
#       January 15, 2017        bar     comments
#       January 18, 2017        bar     comment
#       January 20, 2017        bar     array_replaced() and array_replace()
#       January 21, 2017        bar     get_ini_dir() and comment changes
#       October 2, 2017         bar     a_min_max_median
#       October 3, 2017         bar     a_peak_finder
#       October 8, 2017         bar     an_object
#       November 7, 2017        bar     fix a dict update while looping over it problem
#       November 7, 2017        bar     maxint->maxsize
#       November 27, 2017       bar     a_fifo_histogram()
#       April 8, 2018           bar     in median(), use numpy percentile for numpy arrays
#       May 21, 2018            bar     find_3d_line_to_plane_intersection() and epsilon
#                                       rectangle_intersection() and rectangle_union_area()
#       June 4, 2018            bar     backdoor import numpy and bail anywhere we must if numpy doesn't import
#                                       white_balance() and do_like_gimps_auto_adjust_color_levels()
#       June 16, 2019           bar     s_if_1
#       July 8, 2019            bar     ladder_score()
#       July 10, 2019           bar     max_w_h_scale()
#       July 12, 2019           bar     alias argmax and argmin to our routines
#       July 13, 2019           bar     typo
#       July 16, 2019           bar     sigmoid() and sigmoid_delta() and tanh() and tanh_delta()
#       July 24, 2019           bar     find_representative_points_on_a_rubber_line()
#       July 26, 2019           bar     scope out a bad idea to slightly improve find_representative_points_on_a_rubber_line()
#       August 13, 2019         bar     angle_between_two_vectors()
#                                       clean up all == None and != None
#       August 26, 2019         bar     bit_count() can handle numpy scalars
#       August 27, 2019         bar     in cmp_str_with_ints(), assume the numbers are decimal rather than letting python convert zero-NNN numbers to octal (etc)
#                                       rework sort_numerically()
#                                       cmp_lower and cmp_upper, properly written, finally, whew
#       October 14, 2019        bar     allow callers of pickle_file() to pass a proto of -1 to get the highest protocol, which is what they want to do without fuss
#       November 5, 2019        bar     comments and make a subroutine for clarity
#       December 16, 2019       bar     start on a_peak_finder_regex
#       December 17, 2019       bar     get it working with mults
#       December 19, 2019       bar     do a bail-out print in print_exception() because traceback doesn't print exceptions in some threading situations
#       January 11, 2020        bar     is_numpy_array() also has attribute, dot
#       January 12, 2020        bar     ellipse_circumference()
#                                       get_signal_name()
#       January 29, 2020        bar     run_config_module()
#       February 9, 2020        bar     remove_trailing_dupes() - make_dictionary() should have is_numpy_array() and flatten() !!!!
#                                       cmd_line_files()
#       April 29, 2020          bar     key modification routine for remove_trailing_dupes()
#                                       remove_names_of_duplicate_files()
#       May 3, 2020             bar     comma_and_join()
#       May 22, 2020            bar     utf8()
#       May 24, 2020            bar     find_first_not_in_array()
#       May 25, 2020            bar     case-insensitive versions of find_argi() and find_argi_and_del() (for /H and --Help and their ilk)
#       May 31, 2020            bar     index()
#       June 26, 2020           bar     string_to_number()
#                                       find_usb_gvfs_drive_dirs()
#       June 27, 2020           bar     str_diff_distance()
#       July 6, 2020            bar     set_terminal_title()
#       July 10, 2020           bar     someone on the internet's f("string {variable}") for old python doing v 3.6+ f"string {variable}"
#       July 12, 2020           bar     torus_goto_distance()
#       July 26, 2020           bar     allow named params to an_object
#       --eodstamps--
##      \file
#       \namespace              tzpython.tzlib
#
#
#       Buncha things.
#
#

import  cPickle

import  copy
import  difflib
import  getpass
import  glob
import  htmlentitydefs
import  math
import  os
import  Queue
import  random
import  re
import  shutil
import  signal
import  string
import  subprocess
import  sys
import  threading
import  time
import  traceback
import  unicodedata
import  urlparse
import  zlib
from    types                   import ListType, TupleType, UnicodeType, DictionaryType

try :
    import  hashlib
except      ImportError       :
    import  md5
    import  sha
    class   a_hashlib(object) :
        def md5(me, s  = "")  :
            return(md5.new(s))
        def sha1(me, s = "")  :
            return(sha.new(s))
        pass
    hashlib = a_hashlib()

try :
    import  ctypes
except      ImportError :
    cypes   = None

try     :
    import  win32api
except  ImportError :
    win32api    = None

try     :
    import  win32gui
except  ImportError :
    win32gui    = None

try     :
    import  pywintypes
except      ImportError :
    class   pywintypes_type(object) :
        pass
    pywintypes          = pywintypes_type()
    pywintypes.error    = Exception                                     # this keeps python from crashing to hide an exception we don't handle, sort of


try         :
    numpy   = __import__('numpy')                                       # pull in numpy without letting modulefinder.py know about it
except      ImportError :
    numpy   = None                                                      # we just have to deal with numpy not being on this system



##  Run under older Pythons
try:
    True, False
except NameError:
    True    = 1
    False   = 0



float_regx_str      = r"(?:[\+\-]?(?:\d+(?:\.\d*)?|\.\d+))"             # but not scientifc e numbers because for no real reason, probably
FLOAT_regx_str      = r"(?:[\+\-]?(?:\.\d+|\d+\.\d*))"                  # number with a decimal point, for sure


def an_object(name = None, **kwargs) :
    """ Trick to, with a 1-liner, make an object that attributes can be hung on, etc. """
    me  = type(name or 'an_object', (), {})                     # thanks, StackOverflow
    for k, v in kwargs.items() :
        setattr(me, k, v)
    return(me)


def print_exception() :
    e       = sys.exc_info()
    traceback.print_exception(e[0], e[1], e[2])
    print   "Exception:", e[1].message, 'in "' + re.sub(r'.*?, file "', '', str(e[2].tb_frame.f_code)).replace('>', ','), "at line number:", e[2].tb_lineno
    sys.stderr.flush()
    sys.stdout.flush()


def print_stack() :
    traceback.print_stack()


def make_thread_listing(force_show_frames = False) :
    """ Return a string with an informative thread listing. """
    tnms    = [ "Thread: " + th.name + " " + str(getattr(th, 'tid', 'NoTID')) for th in threading.enumerate() ]
    tnms.sort(cmp_lower)
    s       = ""
    for th in threading.enumerate() :
        if  force_show_frames or th.name.startswith('Thread-') :
            fr      = sys._current_frames().get(th.ident, None)
            if  fr  :
                s  += "\n"
                s  += "------ %s %s ------\n" % ( th.name, str(getattr(th, 'tid', 'NoTID')), )
                s  += "".join(traceback.format_stack(fr))                   # traceback.print_stack() by another name
            pass
        pass
    if  s   :
        s  += "\n"
    s      += "\n".join(tnms)
    if  s and (s[-1] != '\n') :
        s  += '\n'

    return(s)






def get_tid() :
    tid             = -2
    if win32api     :
        tid         = win32api.GetCurrentThreadId()
    elif sys.platform.find('linux') >= 0 :                                      # we could try to find the syscall.h or unistd.d file with the SYS_gettid in it
        if  ctypes  :
            tid         = int(str(ctypes.CDLL('libc.so.6').syscall(224)))       # does not work on 64-bit OS (that's probably the reason it returns -1 on spring)
            if  tid < 0 :                                                       # note: "pstree -p -H " + str(os.getpid) is ambiguous at best, and would need a system lock around it, too, if more that 1 thread were using it for this purpose
                tid     = int(str(ctypes.CDLL('libc.so.6').syscall(186)))       # found with " #include <syscall.h> printf("%u\n", SYS_gettid) "
            pass
        pass

    return(tid)



BIT_BUCKET  = [ None ]  # I hate "global", but this trick isn't much better :)
def send_stdouterr_to_the_bit_bucket() :
    """ Let's not see any more from sys.stdout or sys.stderr. """
    if  BIT_BUCKET[0] is None :
        BIT_BUCKET[0]   = open(os.devnull, 'w')
    sys.stdout          = BIT_BUCKET[0]
    sys.stderr          = BIT_BUCKET[0]


def disappear_window(hide) :
    """ If told to, disappear our window. Or, at least, send stdout and stderr to the bit bucket. """
    if  hide :
        send_stdouterr_to_the_bit_bucket()
        if  (sys.platform  == 'win32') and ctypes and hasattr(ctypes, 'windll') :
            hwnd            = ctypes.windll.kernel32.GetConsoleWindow()
            if  hwnd and win32gui :
                win32gui.ShowWindow(hwnd, 0)                    # this leaves the window in the taskbar, but 64-bit Win7 doesn't do FreeConsole, it appears.
            ctypes.windll.kernel32.FreeConsole()                # this worked fine for 32-bit program

            return(True)

        pass

    return(False)



def run_shell_program(cmd, stdin = None, stdout = None, stderr = None) :
    """
        Run the given command line program (full path needed for the executable's name).
        Return the process.
    """
    close_fds   = not not (stdin or stdout or stderr)           # note: I could not repro the reason this was always True before April 13, 2014, but this is an attempt to stop an "Err:[Errno 12] Cannot allocate memory on line NNNN" when failing to fork inside subprocess here
    stdin       = stdin  or subprocess.PIPE
    stdout      = stdout or subprocess.PIPE
    stderr      = stderr or subprocess.STDOUT

    if  sys.platform == 'win32' :
        p       = subprocess.Popen(cmd, shell = True, stdin = stdin, stdout = stdout, stderr = stderr)
    else        :
        p       = subprocess.Popen(cmd, shell = True, stdin = stdin, stdout = stdout, stderr = stderr, close_fds = close_fds)
    return(p)


def run_program(cmd, stdin = None, stdout = None, stderr = None, input = None, proc_a = None, strip_blank_lines = True) :
    """
        Run the given command line program (full path needed for the executable's name).
        Return the exit code int and the stdout/stderr output stripped and CRLFCRLF...LFLF... -> LF converted.
        This is a blocking function and does not return until the program finishes. So you can't monitor the output as it runs.

    """
    if  proc_a     is None :
        proc_a      = []
    try             :
        p           = run_shell_program(cmd, stdin = stdin, stdout = stdout, stderr = stderr)
        proc_a.append(p)
        ( rs, se )  = p.communicate(input = input or None)          # note: this input won't work as input to sudo for the password
        try         :
            proc_a.pop()
        except IndexError :
            pass
        r           = p.returncode
        rs          = (rs or "").strip() + (se or "").strip()
        rs          = re.sub(r"(?:\r?\n)" + ((strip_blank_lines and "+") or ''), "\n", rs).strip() + "\n"
    except          :
        raise

    return(r, rs)



def in_screen_saver() :
    """ Is the screen saver running? (Only works under Gnome !!!! ) """

    if  sys.platform.find('linux') < 0 :
        return(False)

    ( r, rs )   = run_program("gnome-screensaver-command -q")
    if  (not r) and (rs.find(" active") >= 0) :
        return(True)

    ( r, rs )   = run_program("xscreensaver-command -time")
    if  (not r) and (rs.find(" non-blanked") < 0) :
        return(True)

    return(False)



def set_terminal_title(title = None) :
    """ Set the terminal's title or, if not given, set it to the current directory. """
    if  sys.platform.find('linux') >= 0 :
        if  not title   :
            cwd, title  = os.path.split(os.getcwd())
            while cwd   :
                ocwd    = cwd
                cwd, bn = os.path.split(cwd)
                if  (len(title) + len(bn) >= 16) or (cwd == ocwd) :
                    break
                if  len(title) + len(cwd) <  16 :
                    bn  = cwd
                    cwd = ''
                title   = os.path.join(bn, title)
            pass
        sys.stderr.write("\033]0;%s\a" % title)        # set our bash terminal window's title
        sys.stderr.flush()
    pass



def can_run_program(program_file_name) :
    """ Return True if it appears that this program is runnable. """
    return(program_file_name and os.path.exists(program_file_name) and os.path.isfile(program_file_name) and os.access(program_file_name, os.R_OK) and os.access(program_file_name, os.X_OK))



def get_signal_name(signum) :
    """ Return the name of the given integer signal number, if known. Return None otherwise. """
    signum  = int(signum)           # allow floats, but round down
    signum  = abs(signum)           # allow negative values because that's what multiprocessing comes back with
    for nm in dir(signal) :
        if  re.match(r'^SIG[^_]', nm) :
            sv  = getattr(signal, nm)
            if  type(sv) is type(1) :
                if  signum is sv :
                    return(nm)
                pass
            pass
        pass
    return(None)


def expand_user_vars(fn) :
    """ Expand ~/ and ${HOME}/, etc. """
    return(os.path.expanduser(os.path.expandvars(fn)))




def ambiguous_file_list(ambiguous_name, do_sub_dirs = False) :
    """
        Return an array with the names of the files that match the given ambiguous file name.
    """

    abn     = expand_user_vars(ambiguous_name)
    files   = glob.glob(abn)

    if  do_sub_dirs :
        (dir_name, amb_name) = os.path.split(abn)
        if  not len(dir_name) :
            dir_name         = "./"

        try         :
            abn     = os.path.join(os.path.normpath(dir_name), os.path.normpath(amb_name))          # note: not sure why this is here. it's not what we want for listdir, but would work for glob, though they both have weirdnesses

            for fn in os.listdir(dir_name) :
                ffn = os.path.join(os.path.normpath(dir_name), fn)
                if  os.path.isdir(ffn) :
                    fls = ambiguous_file_list(os.path.join(ffn, amb_name), do_sub_dirs)
                    for fln in fls :
                        files.append(fln)
                    pass
                pass
            pass
        except ( OSError, IOError, ValueError ) :
            pass
        pass

    return(files)


def ext_ambiguous_file_list(fn, ext, do_sub_dirs = None) :
    """ Return an array with the names of the files that match the given ambiguous file name - looking for those that have the given extension when needed. """

    ext     = ext or ""
    if  ext and (ext[0] != '.') :
        ext = '.' + ext

    if  os.path.isdir(fn) :
        fn  = os.path.join(fn, '*' + ext)
    else    :
        fns = ambiguous_file_list(fn, do_sub_dirs = do_sub_dirs)
        if  not len(fns) :                                          # if there are no ambiguous files to find,
            fn += '*' + ext                                         #    then add the extension in case it's not there.
        pass
    fns     = ambiguous_file_list(fn, do_sub_dirs = do_sub_dirs)    # find the files

    return(fns)



def is_numpy_array(a) :
    """ Return if the given object is a numpy array. """
    return(hasattr(a, 'flatten') and hasattr(a, 'shape') and hasattr(a, 'dot'))


def is_pil_image(image) :
    """ Return if the given object is a PIL image. """
    return(hasattr(image, 'convert') and hasattr(image, 'getdata') and hasattr(image, 'getpixel') and hasattr(image, 'crop'))






class   a_min_max(object) :
    """ Track a minimum, maximum, average, etc. """

    def __init__(me) :
        """ Make a new object. """
        me.mn   =   sys.maxsize
        me.mnfn = ""
        me.mx   = -(sys.maxsize - 1)
        me.mxfn = ""
        me.sm   = 0.0
        me.cnt  = 0

    def mean(me) :
        """ Return the average value so far. (0.0 if none) """
        return(me.sm / float(me.cnt or 1))

    def min(me) :
        """ Return the minimum value so far. """
        return(me.mn)

    def min_info(me) :
        """ Return the minimum value's info so far. """
        return(me.mnfn)

    def max(me) :
        """ Return the maximum value so far. """
        return(me.mx)

    def max_info(me) :
        """ Return the maximum value's info so far. """
        return(me.mxfn)

    def len(me) :
        """ Return the number of values known so far. """
        return(me.cnt)

    def sum(me) :
        """ Return the number of values known so far. """
        return(me.sm)

    def append(me, v, info = None) :
        """ Check out this new value, dude. """
        if  v is not None   :
            if  me.mn   > v :
                me.mn   = v
                me.mnfn = info or ""
            if  me.mx   < v :
                me.mx   = v
                me.mxfn = info or ""
            me.sm      += v
            me.cnt     += 1
        pass

    #   a_min_max


class   a_min_max_median(a_min_max) :
    """ Track a median, minimum, maximum, average, etc. """

    def __init__(me, *args, **kwargs) :
        """ Make a new object. """
        super(a_min_max_median, me).__init__(*args, **kwargs)
        me.values   = []

    def append(me, v, info = None) :
        """ Track a new value. """
        if  v is not None :
            me.values.append(v)
            super(a_min_max_median, me).append(v, info = info)
        pass

    def median(me, middle = None) :
        """
            Return the float median or some kinda of median-esque value.

            'middle' can be from 0.0 to 1.0

            Returns the result of a call to median().
        """
        return(median(me.values, middle = middle))

    #   a_min_max_median


def binary_search(a, item, cmp_rtn = None, si = None, ei = None, cmp_obj = None) :
    """
        Binary search the sorted array, 'a' looking for 'item' or the first index in the array that has a value greater than or equal to 'item'.
        Use the given 'cmp_rtn', which looks like the default, direct_cmp(), below.
        Return the found index (or len(a) if all the array's items are below 'item').
    """


    def direct_cmp(a, i, item, cmp_obj) :
        return(cmp(a[i], item))


    si      = si or 0
    if  ei is None  :
        ei  = len(a)

    if  cmp_rtn is None :
        cmp_rtn = direct_cmp

    lo      = si
    hi      = ei
    mid     = (lo + hi) / 2
    while lo < hi :
        mid = (lo + hi) / 2

        if  cmp_rtn(a, mid, item, cmp_obj) < 0 :        # note: it appears that this could be <= and we'd find the firt index with a value greater than 'item'
            mid    += 1
            lo      = mid
        else :
            hi      = mid
        pass

    # let the caller do mid=max(0, min(len(a), mid)) if he wants

    return(mid)





def find_in_array(a, s, bi = None, ei = None) :
    """
        Find an item in an array, 'a' - or the first of an array of items, 's', in the array..
        Return -1 if not found.
        Otherwise return the found array index.
    """

    if  not isinstance(a, ListType) and not isinstance(a, TupleType) :
        a = [ a ]

    if  not isinstance(s, ListType) and not isinstance(s, TupleType) :
        s = [ s ]

    bi  = bi or 0
    if  ei is None :
        ei  = len(a)

    for ss in s :
        try :
            i = a.index(ss, bi, ei)
            return(i)

        except TypeError :                  # catch python 2.2 or whatever
            try :
                i = a[bi:ei].index(ss)
                return(i)

            except IndexError :
                pass
            except ValueError :
                pass
            pass
        except IndexError :
            pass
        except ValueError :
            pass
        pass

    return(-1)


def index(a, item, bi = None, ei = None) :
    """ Find the item in an array. Not any value in the item if the 'item' is an array. Return the index or -1. """
    return(find_in_array(a, [ item ], bi = bi, ei = ei))


def find_first_not_in_array(a, item, bi = None, ei = None) :
    """
        Find a non-equal-to 'item' in an array.
        Return -1 if not found.
        Otherwise return the found array index.
    """

    if  not isinstance(a, ListType) and not isinstance(a, TupleType) :
        a = [ a ]

    bi  = bi or 0
    if  ei is None :
        ei  = len(a)
    for i,  v in enumerate(a[bi:ei]) :
        if  v != item :
            return(i)
        pass
    return(-1)


def array_replaced(a, frv, tov) :
    """ Return an array with all instances of 'frv' in the given array changed to 'tov'. """
    return([ tov if v == frv else v for v in a ])       # this is probably slower than in-place, but is less bug-enhancing.

def array_replace(a, frv, tov) :
    """ Replace all instances of 'frv' in the given array to 'tov'. In-place. Do not return the array. """
    for i,  v in enumerate(a) :
        if  v == frv :
            a[i]    = tov
        pass
    pass                                            # to emphasize the original is modified, be like sort() and don't return anything


#
#   http://stackoverflow.com/questions/42519/how-do-you-rotate-a-two-dimensional-array
#

def rotate_2d_array_clockwise(a) :
    """ Rotate a 2D array clockwise. """
    return(zip(*a[::-1]))

def rotate_2d_array_counter_clockwise(a) :
    """ Rotate a 2D array counter clockwise. """
    return(zip(*a)[::-1])



def max_index(a) :
    """
        Return the index in to the given array of the maximum value in the array.
    """

    if  len(a) == 0 :
        return(-1)

    if  hasattr(a, 'argmax') :
        return(a.argmax())          # numpy

    if  hasattr(a, 'index') :       # numpy arrays don't have this, apparently
        return(a.index(max(a)))     # benchmark says this is faster (than enumerate, too)

    bi  = 0
    bv  = a[0]
    for i in xrange(1, len(a)) :
        v       = a[i]
        if  bv  < v :
            bv  = v
            bi  = i
        pass

    return(bi)
argmax          = max_index
index_of_max    = max_index



def min_index(a) :
    """
        Return the index in to the given array of the minimum value in the array.
    """

    if  len(a) == 0 :
        return(-1)

    if  hasattr(a, 'argmin') :
        return(a.argmin())          # numpy

    if  hasattr(a, 'index') :
        return(a.index(min(a)))     # benchmark says this is faster (than enumerate, too)

    bi  = 0
    bv  = a[0]
    for i in xrange(1, len(a)) :
        v       = a[i]
        if  bv  > v :
            bv  = v
            bi  = i
        pass

    return(bi)
argmin          = min_index
index_of_min    = min_index


def as_integer_ratio(v, cutoff = 0.000000001) :
    """ Find a good fraction for the given real number. """
    cr  = 1.0 / cutoff
    x   = float(v)
    n   = 0
    d   = 1
    tn  = 1
    td  = 0
    while (tn < cr) and (td < cr) :
        f   = math.floor(x)
        n, tn   = tn, f * tn + n
        d, td   = td, f * td + d
        if  (f == x) or (abs(tn / td - v) < cutoff) :
            if  (tn < cr) and (td < cr) :
                n   = tn
                d   = td
            break
        x   = 1 / (x - f)

    return(n, d)



def median(a, middle = None) :
    """
        Return the float median or some kinda of median-esque value from the given array.

        middle can be from 0.0 to 1.0

        ????
            Should the 1/3rd-ian of [ 1, 2, 3 ] be 1? and the 2/3rd-ian be 3?

            That is, at the ends, shouldn't a sufficiently close to zero or 1 -ian value pick the end values in the array?

            If the floor and ceil operations are given tweaked values and the special li==hi return value is done for li>=hi, then this sort of thing might be handled in a way that makes a different sense.

            This is all leaving aside the issue of whether the proper model of a median should be a linear interpolation between two array values, and whether the model should effectively fill in
            the array value with many microscopic values between the array values, but not, say, filling in half way past the end values.

                In other words, should the model of [ 1, 2, 3 ] by [ 1, 1.1, 1.2.... 2.7, 2.8, 2.9, 3 ] or [ .5, .6, .7, .8, .9, 1.0, 1.1 ... 2.9, 3, 3.1 ... 3.5, ]?

                Current thinking

                    It makes sense that it would be the former, as the code does it, but not perfect sense.

                    An -ian value outside the array values? Ugh. And some particular normal median case doesn't work right if the boundaries are extended. I forget what it is.

    """
    if  a  is None :
        return(None)

    if  is_pil_image(a) :
        a   = a.getdata()

    if  numpy and is_numpy_array(a) :
        return(numpy.percentile(a, ((middle is None) and 50) or (middle * 100.0)))

    if  hasattr(a, 'flatten') :
        a   = a.flatten()               # numpy array
        a   = list(a)
    elif len(a) and isinstance(a[0], (list, tuple)) :
        a   = flatten_array(a)
    else    :
        a   = list(a)
    a.sort()

    if  not len(a) :
        return(None)
    if  len(a) == 1 :
        return(a[0])

    if  middle is None :
        middle  = 0.5

    if  not middle :
        return(a[0])
    if  middle == 1.0 :
        return(a[-1])

    li  = int(math.floor((len(a) - 1) * middle))
    hi  = int(math.ceil( (len(a) - 1) * middle))

    if  li == hi :
        return(a[li])

    lv      = a[li]
    hv      = a[hi]

    try         :
        n, d    = float(middle).as_integer_ratio()
    except      :
        n, d    = as_integer_ratio(middle)

    fr      = float(n) * ((len(a) - 1) % float(d)) / float(d)
    fr      = fr - math.floor(fr)

    return(lv + ((hv - lv) * fr))




def left_valley(a, i, bump = 0) :
    """ Return the lowest spot in the given array to the left/west of the given index. """

    bump    = bump or 0
    while i > 0 :
        if  a[i - 1] > a[i] + bump :        # find the spot to the left/west of a flat valley
            break
        i -= 1

    return(i)
west_valley     = left_valley


def rite_valley(a, i, bump = 0) :
    """ Return the lowest spot in the given array to the rite/east of the given index. """

    bump    = bump or 0
    while i < len(a) - 1 :
        if  a[i + 1] > a[i] + bump :        # find the spot to the rite/east of a flat valley
            break
        i  += 1

    return(i)
east_valley     = rite_valley
right_valley    = rite_valley


#
#   Snagged from: http://rightfootin.blogspot.com/2006/09/more-on-python-flatten.html
#
def flatten_array(l, ltypes = (list, tuple)) :
    """
        Flatten an array (of arrays).
    """

    if  hasattr(l, 'flatten') :
        return(l.flatten())             # numpy array

    ltype   = type(l)
    l       = list(l)
    i       = 0
    while i < len(l) :
        while isinstance(l[i], ltypes) :
            if  not l[i] :
                l.pop(i)
                i  -= 1
                break
            else :
                l[i:i + 1]  = l[i]
            pass
        i  += 1

    return(ltype(l))



def cumsum(a) :
    """
        Return a 2D float array that's a cumulative sum of the given array.

    """
    if  hasattr(a, 'cumsum') and hasattr(a, 'ndim') :
        a       = a.astype('double')
        for d in xrange(a.ndim) :
            a   = a.cumsum(axis = d)
        return(a)

    a   = copy.deepcopy(a)

    try :
        for y in xrange(len(a)) :
            for x in xrange(1, len(a[0])) :
                a[y][x]    += a[y][x - 1]
            pass
        for x in xrange(len(a[0])) :                            # note: requires each row be the same length
            for y in xrange(1, len(a)) :
                a[y][x]    += a[y - 1][x]
            pass
        pass
    except AttributeError :
        pass

    return(a)


def cumsum_sum(a, x, y, w, h) :
    """
        Return the sum of the original array values in a given rectangle from the cumulative sum array of the original array.

        The cumulative sum array can be computed using numpy like this:
            a   = numpy.cumsum(numpy.cumsum(original_a, axis = 0), axis = 1)

    """
    w   = max(0, min(w, len(a[0]) - x))
    h   = max(0, min(h, len(a   ) - y))
    if  w and h :
        return(                a[y + h - 1][x + w - 1]
               - ((      y and a[y     - 1][x + w - 1]) or 0)
               - ((x and       a[y + h - 1][x     - 1]) or 0)
               + ((x and y and a[y     - 1][x     - 1]) or 0)
              )
    return(0)


def cumsum_average(a, x, y, w, h) :
    """ Return the average original array float value of the given rectangle's values from the cumulative sum array of the original array. """
    w   = max(0, min(w, len(a[0]) - x))
    h   = max(0, min(h, len(a   ) - y))
    if  w and h :
        s   = cumsum_sum(a, x, y, w, h)
        return(float(s) / (w * h))
    return(None)






def pop_slice(a, frm = None, to = None) :
    """ pop() the given slice of the array. """
    r   = a[frm:to]
    del(  a[frm:to])
    return(r)




def invert_dictionary(d, dupable_values = {}) :
    """
        Invert the given dictionary.
        The result is updated with force_dict.
    """

    dupable_values  = dupable_values or {}
    rd              = dict((v, k) for k, v in d.iteritems())
    if  len(rd)    != len(d) :
        rrd         = make_dictionary(rd.values())
        va          = [ { k : v } for k, v in d.iteritems() if (k not in rrd) and (v not in dupable_values) ]
        if  va      :
            raise IndexError("invert_dictionary(): %d duped value%s %s" % ( len(d) - len(rd), s_except_1(va), repr(va) ) )
        rd.update(dupable_values)

    return(rd)


def make_dictionary(a, val = True) :
    """
        Make a dictionary from a list/tuple.

        If python is 2.3+, then this is fromkeys().
    """

    retval  = {}

    #   try     :
    #       retval.update(zip(a, [ val ] * len(a)))             # slower than the simple way
    #       return(retval)
    #
    #   except ( ValueError, AttributeError, TypeError ) :
    #       pass

    if  a is not None :

        if  not isinstance(a, ListType) and not isinstance(a, TupleType) and not isinstance(a, basestring) :
            a = [ a ]

        for k in a :
            retval[k] = val
        pass

    return(retval)


def buples_to_dictionary(a) :
    """
        Given an array of [ key, value ] items return a dictionary. (dict() function does this.)
    """

    d   = {}
    for i in a :
        d[i[0]] = i[1]

    return(d)



def make_index_dictionary(a) :
    """
        Make a dictionary from a list/tuple where the keys are the list/tuple's values and the values are the list/tuple indices.

    """

    retval = {}

    if  a is not None :

        if  not isinstance(a, ListType) and not isinstance(a, TupleType) and not isinstance(a, basestring) :
            a = [ a ]

        for i, k in enumerate(a) :
            retval[k] = i
        pass

    return(retval)



def sorted_by_count(a) :
    """ Return an array of the values in the given array, sorted by how many of each value there are in the array, least common values first. """
    d   = {}
    for v in a :
        d[v]    = d.get(v, 0) + 1
    ka  = d.keys()
    ka.sort(lambda a, b : cmp(d[a], d[b]) or cmp(a, b))
    return(ka)



def update_all_case_keys(d) :
    """
        Update a dictionary, adding key/values for all upper() or lower() versions of existing keys where there are none now.
    """

    for k in d.keys() :
        try :
            nk  = k.lower()
            if  nk not in d :
                d[nk]   = d[k]
            nk  = k.upper()
            if  nk not in d :
                d[nk]   = d[k]
            pass
        except AttributeError :
            pass                    # the key is and int or something
        pass
    pass



def list_lstrip(a, va) :
    """
        Return an array subjected to the logical equivalent of string.lstrip().
    """

    if  not isinstance(va, (ListType, TupleType)) :
        va  = [ va ]
    va  = make_dictionary(va)

    for i in xrange(len(a)) :
        if  a[i] not in va :

            return(a[i:])

        pass

    return([])







def fromkeys(a, val = True) :
    """
        Synonym for make_dictionary.
    """

    return(make_dictionary(a, val))



def without_dupes(a) :
    """
        Return a copy of the given array with dupes removed.
        The item order will probably be changed.
    """

    return(make_dictionary(a).keys())


def remove_trailing_dupes(a, key_rtn = None) :
    """
        Return a copy of the given array with dupes removed.
        The item order will not be changed. Dupes later in the list will be removed.
        If a key routine is given, use it to modify the items in the list before comparing them for equality.
    """
    key_rtn     = key_rtn or (lambda k : k)
    retval      = []
    if  a is  not None :
        if  not isinstance(a, ListType) and not isinstance(a, TupleType) and not isinstance(a, basestring) and not is_numpy_array(a) :
            a   = [ a ]
        d       = {}
        for k in flatten_array(a) :
            rk  = key_rtn(k)
            if  rk not in d :
                d[rk]   = True
                retval.append(k)
            pass
        pass

    return(retval)


def remove_names_of_duplicate_files(fns) :
    """ Remove from the list file names that refer to other file names in the list. Remove the dupe names further down the list. """
    return(remove_trailing_dupes(fns, key_rtn = lambda fn : os.path.abspath(fn)))



def de_dupe_str(s) :
    """
        Return the given string without duplicate characters (after the 1st instance of each unique character).
    """

    cnts    = {}
    so      = ""
    if  type(s) == type(u"") :
        so  = u""
    for c in s :
        cnts[c] = cnts.get(c, 0) + 1
        if  cnts[c] < 2 :
            so += c
        pass

    return(so)



def not_in(d, a) :
    """ Return an array of the keys in dictionary, d (or values in array, d) that are not in array a (or dictinonary a's keys). """
    if  hasattr(d, 'keys') :
        d   = make_dictionary(d.keys())         # make copy of 'd's keys
    else    :
        d   = make_dictionary(d)                # or make a dictionary of 'a'
    if  hasattr(a, 'keys') :
        a   = a.keys()                          # make an array of values we'll look for
    for v in a :
        if  v in d :
            del(d[v])                           # get rid of values that are in 'a'
        pass
    return(d.keys())                            # leaving only the keys that aren't in 'a'




def keys_sorted_by_values_keys(hash_dict) :
    """
        Return an array of the keys from a dictionary, sorted by value/key.
    """

    def _vkcmp(k1, k2) :
        c      = cmp(hash_dict[k1], hash_dict[k2])
        if  c != 0 :
            return(c)
        return(cmp(k1, k2))

    v = hash_dict.keys()

    v.sort(_vkcmp)

    return(v)


def values_sorted_by_values_keys(hash_dict) :
    """
        Return an array of the values from a dictionary, sorted by value/key.
    """

    kys = keys_sorted_by_values_keys(hash_dict)

    return(map(lambda k : hash_dict[k], kys))



def keys_sorted_by_keys_values(hash_dict) :
    """
        Return an array of the keys from a dictionary, sorted by key/value.
    """

    def _vkcmp(k1, k2) :
        c      = cmp(k1, k2)
        if  c != 0 :
            return(c)
        return(cmp(hash_dict[k1], hash_dict[k2]))

    v = hash_dict.keys()

    v.sort(_vkcmp)

    return(v)


def values_sorted_by_keys_values(hash_dict) :
    """
        Return an array of the values from a dictionary, sorted by key/value.
    """

    kys = keys_sorted_by_keys_values(hash_dict)

    return(map(lambda k : hash_dict[k], kys))


def value_array_for_key(array_of_dicts, k, dflt = None) :
    """
        Return an array of values for the given key in each dict in an array of dicts.

        If dflt is None :
            Skip any dict that doesn't have the key, returning an array shorter than the input array.
        else            :
            Return dflt for any missing value.
    """

    if  dflt is None :
        return([ d[k]           for d in array_of_dicts if k in d ])

    return(    [ d.get(k, dflt) for d in array_of_dicts           ])



def replace_value_array_for_key(array_of_dicts, k, a) :
    """
        Put the given array of values in each dict in an array of dicts - each value under the given key.

        Raise an IndexError exception if the length of 'a' is not the same as 'array_of_dicts'.
    """

    if  len(array_of_dicts) != len(a) :
        raise IndexError("Wrong length %u in to %u" % ( len(a), len(array_of_dicts) ))

    for di in xrange(len(array_of_dicts)) :
        array_of_dicts[di][k]   = a[di]
    pass



def numerically_sortable(s) :
    """
        Return a string converted to one appropriate for sorting numerically. That is, "a9" sorts lower than "a10".

        Notice that negative numbers aren't handled, as the integer parts of them are a problem.

        Also, notice that dots make floats. That may not be what you want.

        There are better ways to do this - creating a list of strings and numbers for each item in the list and sorting on those lists, for instance.
        But, it happened, I wanted this routine, not the actual sort routines. And dashes before the numbers are as often dashes, not minus signs, that, ... well ...

        Anyway, see the cmp_str_with_ints() routine below for something a little better to handle file names with.

    """

    #
    #   import  natsort     is a heavy-weight solution for this kind of thing
    #

    def _float_sub(g) :
        """ Replace ints. """
        f   = float(g.group(1))
        return("%065.32f" % f)

    def _int_sub(g) :
        """ Replace ints. """
        s   = g.group(1)
        if  s.startswith('.') :
            return(s)
        return("%032i" % int(s, 10))

    s   = re.sub(r'(\.\d+|\d+\.\d*)',   _float_sub, s)         # note: the digits after the dot can be of various lengths. numericizing them makes .11 the same as .110 and makes .11 less than .111
    s   = re.sub(r'(\.?\d+)',           _int_sub,   s)

    return(s)


def cmp_lower(a, b) :
    return(cmp(a.lower(), b.lower()) or cmp(a, b))
def cmp_upper(a, b) :
    return(cmp(a.upper(), b.upper()) or cmp(a, b))
def cmp_same(a, b) :
    return(0)

def sort_numerically(a, cmp_rtn = None) :
    """ Sort in place the given list of strings numerically. That is, "9" comes before "10". """

    def _cmp_rtn(a, b) :
        na  = numerically_sortable(a)
        nb  = numerically_sortable(b)
        if  cmp_rtn :
            d   = cmp_rtn(na, nb)
            if  d :
                return(d)
            pass
        d   = cmp(na, nb)
        if  d :
            return(d)
        if  cmp_rtn :
            d   = cmp_rtn(a, b)
            if  d :
                return(d)
            pass
        d   = cmp(a, b)
        return(d)

    a.sort(_cmp_rtn)

def sorted_numerically(a, cmp_rtn = None) :
    """ Return a new list of strings sorted numerically. That is, "9" comes before "10". """
    a   = list(a)
    sort_numerically(a, cmp_rtn = cmp_rtn)
    return(a)



def cmp_str_with_ints(a, b) :
    """
        For use in numbered file name sorting, compare two strings with integer values in them.
        E.g. [ 'fn_1_1.txt', 'fn_1_9.txt', 'fn_1_10.txt', 'fn_1_100.txt', 'fn_1_20.txt', ].sort(tzlib.cmp_str_with_ints).
    """
    def _strint(g) :
        return("%0*u" % ( max(len(g.group(1)), 32), int(g.group(1), 10), ) )

    a   = re.sub(r'(\d+)', _strint, a)
    b   = re.sub(r'(\d+)', _strint, b)
    return(cmp(a, b))

def cmp_lower_str_with_ints(a, b) :
    """
        For use in numbered file name sorting, compare two strings with integer values in them.
        E.g. [ 'fn_1_1.txt', 'fn_1_9.txt', 'fn_1_10.txt', 'fn_1_100.txt', 'fn_1_20.txt', ].sort(tzlib.cmp_str_with_ints).
        Lower case version.
    """

    a   = a.lower()
    b   = b.lower()
    return(cmp_str_with_ints(a, b) or cmp(a, b))



def str_diff_distance(wanted_s, s) :
    """ Return a distance the given fuzzy string, 'wanted_s', is to a known string, 's'. """
    wanted_s    = wanted_s or ""
    s           = s        or ""
    d   = difflib.SequenceMatcher(None, wanted_s, s)
    return(   len(d.get_opcodes())                                          # positive because we don't want lots of opcodes
            - len(d.get_matching_blocks())                                  # negative because we want lots of matching blocks
            - sum([ n[2] ** 2 for n in d.get_matching_blocks() ])           # negative sum of squares of the lengths of matching blocks
          )
    pass


def array_find(a, s, si = None, ei = None) :
    """
        Return the index of the item in an array or negative 1 if it's not there.
    """

    return(find_in_array(a, s, si, ei))



def find_arg(args, a) :
    """
        Return the index in to args of 'a' (or any string in 'a') or -1 if not found.
        Underscores are ignored and/or match dashes.

        DO NOT USE THIS! Use the next one.
    """

    if  not isinstance(args, ListType) and not isinstance(args, TupleType) :
        args    = [ args ]
    adn         = make_index_dictionary([ s.replace('_', '' ) for s in args ])
    add         = make_index_dictionary([ s.replace('_', '-') for s in args ])

    if  not isinstance(a, ListType) and not isinstance(a, TupleType) :
        a       = [ a ]

    for s in a  :
        i       = adn.get(s.replace('_', '' ), -1)
        if  i  >= 0 :
            return(i)
        i       = add.get(s.replace('_', '-'), -1)
        if  i  >= 0 :
            return(i)
        pass

    return(-1)


def find_argi(sys_args, to_find_a) :
    """
        Return the index in to 'sys_args' of 'to_find_a' (or any string in 'to_find_a') or -1 if not found.
        If 'to_find_a' has args with underscores or non-leading dashes, they are converted to being equivalent to each other and to nothing.
    """

    if  not isinstance(sys_args,  ListType) and not isinstance(sys_args,  TupleType) :
        sys_args    = [ sys_args  ]
    if  not isinstance(to_find_a, ListType) and not isinstance(to_find_a, TupleType) :
        to_find_a   = [ to_find_a ]

    to_find_a       = make_dictionary(to_find_a)
    for k in list(to_find_a.keys()) :
        to_find_a[k.replace('_', '-')]  = True                                                      # sense args with dashes between the words in case
        to_find_a[k.replace('_', '' )]  = True                                                      # sense args with runonwords
        to_find_a[re.sub(r'(^-+).*', r'\1', k) + re.sub(r'^-+', '', k).replace('-', '_')] = True    # sense args with dashes as having underscored words
        to_find_a[re.sub(r'(^-+).*', r'\1', k) + re.sub(r'^-+', '', k).replace('-', '' )] = True    # sense args with dashes as begin runonwords

    for ai, a in enumerate(sys_args) :
        if  a in to_find_a :
            return(ai)
        pass

    return(-1)


def find_argi_and_del(sys_args, to_find_a) :
    """ Call find_argi() and delete the argument from 'sys_args' if it's found, returning -1 or the index of the arg that was deleted. """
    ai  = find_argi(sys_args, to_find_a)
    if  ai >= 0 :
        del(sys_args[ai])
    return(ai)




def find_argi_any_case(sys_args, to_find_a) :
    """
        Return the index in to 'sys_args' of 'to_find_a' (or any string in 'to_find_a') or -1 if not found.
        If 'to_find_a' has args with underscores or non-leading dashes, they are converted to being equivalent to each other and to nothing.

        This is the case-insensitive version of find_argi().
    """

    if  not isinstance(sys_args,  ListType) and not isinstance(sys_args,  TupleType) :
        sys_args    = [ sys_args  ]
    if  not isinstance(to_find_a, ListType) and not isinstance(to_find_a, TupleType) :
        to_find_a   = [ to_find_a ]
    to_find_a       = [ a.upper() for a in to_find_a ] + [ a.lower() for a in to_find_a ]           # belt and suspenders with the caller

    to_find_a       = make_dictionary(to_find_a)
    for k in list(to_find_a.keys()) :
        to_find_a[k.replace('_', '-')]  = True                                                      # sense args with dashes between the words in case
        to_find_a[k.replace('_', '' )]  = True                                                      # sense args with runonwords
        to_find_a[re.sub(r'(^-+).*', r'\1', k) + re.sub(r'^-+', '', k).replace('-', '_')] = True    # sense args with dashes as having underscored words
        to_find_a[re.sub(r'(^-+).*', r'\1', k) + re.sub(r'^-+', '', k).replace('-', '' )] = True    # sense args with dashes as begin runonwords

    for ai, a in enumerate(sys_args) :
        if  (a.upper() in to_find_a) or (a.lower() in to_find_a) :
            return(ai)
        pass

    return(-1)


def find_argi_and_del_any_case(sys_args, to_find_a) :
    """
        Call find_argi_any_case() and delete the argument from 'sys_args' if it's found, returning -1 or the index of the arg that was deleted.

        This is the case-insensitive version of find_argi_and_del().
    """
    ai  = find_argi_any_case(sys_args, to_find_a)
    if  ai >= 0 :
        del(sys_args[ai])
    return(ai)




def comma_and_join(a, comma = None, ands = None) :
    """
        Join a list of strings with commas and the last item pair with "and" or ", and" if there are more than two list items.
    """
    comma   = comma or ", "
    ands    = ands  or " and "
    cand    = comma.rstrip() + " " + ands.lstrip()
    rs      = zip([ '' ] + ([ comma ] * max(0, len(a) - 2)) + [ (((len(a) == 2) and ands) or cand) ], a)
    rs      = flatten_array(rs)
    return("".join(rs))





def strrev(s) :
    """
        Reverse a string.

        Or post or 2.2 (2.3?) return(s[::-1])
    """

    a = list(s)
    a.reverse()
    return(string.join(a, ""))


def strip_c_comments(text) :
    """
        Strip C and C++ comments from a string.
        From:   http://stackoverflow.com/questions/241327/python-snippet-to-remove-c-and-c-comments
    """

    def replacer(match) :
        s   = match.group(0)
        if  s.startswith('/') :
            return("")
        return(s)

    pattern = re.compile(r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE)

    return(re.sub(pattern, replacer, text))



def c_string(s) :
    """
        Escape a string back to C string form.
        Or, anyway, get the normal escaped characters back to their C string form.

        This *must* be a built in function somewhere! (repr(), sort of)
    """

    s   =   s.replace("\\", r"\134")                # this way there are no doubled slashes
    s   =   s.replace("\'", r"\'")
    s   =   s.replace("\"", r"\"")
    s   =   s.replace("\a", r"\a")
    s   =   s.replace("\b", r"\b")
    s   =   s.replace("\f", r"\f")
    s   =   s.replace("\n", r"\n")
    s   =   s.replace("\r", r"\r")
    s   =   s.replace("\t", r"\t")
    s   =   s.replace("\v", r"\v")

    return(s)


def c_ctrl_esc(s) :
    """
        Escape a string back to C string form with all control characters and Perl smarties escaped.
    """

    def _esc(s) :
        return(r"\%03o" % ord(s.group(0)))

    s   = c_string(s)
    s   = re.sub(r"[\0-\x1f$@%]", _esc, s)              # escape control characters and Perl smarties

    return(s)



def unicode_byte_string(s) :
    """
        Return a Unicode string as a byte string.

        Note: encode puts 2 bytes (converted codecs.BOM_LE), at the start of the string (under Windows or maybe x86).
    """

    return(s.encode('utf-16'))



def ascii(s, ac = '_') :
    """
        Return the given string after converting all characters over 127 to _ or the given character.
    """

    if  ac is None :
        ac  = '_'

    astr    = ""

    if  not isinstance(s, basestring) :
        s = str(s)

    for i in range(0, len(s)) :
        c = s[i:i+1]
        if  ord(c) >= 128 :
            c = ac

        astr += c

    return(astr)


def convert_to_unicode(s) :
    """ Try to convert the given string to unicode as best we can guess. """
    if  not isinstance(s, unicode) :
        try :
            s   = unicode(s.decode('utf8'))     # this blows up on most latin1 chars - so we hope such is the case if the string is latin 1
        except UnicodeDecodeError :
            s   = unicode(s.decode('latin1'))   # bail out
        pass
    return(s)


def utf8(s) :
    """ Try to convert the given probably-unicode string to utf8 as best we can. If it's not unicode, assume it's already utf8. """
    if  isinstance(s, unicode) :
        s   = s.encode('utf8')
    else    :
        try :
            s.decode('utf8')            # check if it's utf8 already. Or close enough.
        except UnicodeDecodeError :
            s   = unicode('latin1')     # look, in my book, it's either utf8 or latin1. If it's not, you gotta deal with it, yourself.
            s.encode('utf8')            # so let's get the latin1 stuff in to utf8. Screw the other 11-teen encodings.
        pass

    return(s)



def best_ascii(unicode_s) :
    """
        Return the best guess as to ASCII characters that could be used for the given string.
    """

    unicode_s   = convert_to_unicode(unicode_s)

    #
    #   NFKD doesn't work for a lot of characters \u0189, for instance, should be a D
    #   http://www.unicode.org/reports/tr36/confusables.txt is a tiny attempt at look-alikes.
    #   Best to do something to actually render characters in various fonts and compare them to ASCII chars in the same font.
    #   And this logic could be used to compare fonts and put them in family/groups.
    #
    #   Another way of dealing with this is to get the descriptions of the characters, unicodedata.name(c), from http://www.unicode.org/Public/UNIDATA/UnicodeData.txt, and do a least-differencs string thing against the ascii characters' names
    #   Some of the non-Roman names could be table-translated going in to such a thing.
    #
    #   Another thing to do would be to convert Chinese characters to Pingyen. And do similar to other appropriate languages.
    #
    s   = "".join([ unicodedata.normalize("NFKD", c)[0] for c in unicode_s ])

    return(s)





def file_name_able(fn) :
    """
        Return the given file name with the illegal file name characters stripped from it.
    """

    if  isinstance(fn, UnicodeType) :
        fn  = best_ascii(fn).encode('ascii', 'replace')

    fn      = ascii(fn)

    fn      = re.sub(r"[\"]",                           "",  fn)
    fn      = re.sub(r"[\\\/\:\&\^\*\?\<\>\|\"\'\`\+]", "_", fn)
    fn      = re.sub(r"[\[\]]",                         "_", fn)            # protect glob from not finding the file by partial or ambiguous name
    fn      = re.sub(r"^-",                             "_", fn)            # don't allow leading dashes - so they can't be confused with Unix command line arguments

    return(fn.strip())


def undotted_file_name_able(base_name_no_ext) :
    """
        Return the given file name with the illegal file name characters stripped from it, including dots.
    """
    return(file_name_able(base_name_no_ext).replace('.', '_'))


def append_to_file_name(fn, *sa) :
    """ Add strings in the arguments to the file name before the extension. """
    fn, ext = os.path.splitext(fn)
    return(fn + "".join([ str(s) for s in sa if s ]) + ext)



printable_re    =   re.compile(r"[^" + re.escape(string.printable) + r"]")
def printable(s, tochr = "_") :
    """
        Return a printable string with non-printable characters converted to underscores or whatever.
    """

    tochr   = "" if tochr == "" else (tochr or "_")

    s   = printable_re.sub(tochr, s)

    return(s)



def printable_str(s) :
    """ Return a string that's printable. """

    try :
        rs  = unicode(s).encode('unicode_escape')
    except UnicodeDecodeError :
        try :
            rs  = unicode(s.decode('utf8')).encode('unicode_escape')
        except UnicodeDecodeError :
            try :
                rs  = unicode(s.decode('latin1')).encode('unicode_escape')
            except UnicodeDecodeError :
                rs  = repr(s)
            pass
        pass

    return(rs)


def string_to_number(s) :
    """ Convert a byte or string or unicode string to a long number. """
    s       = convert_to_unicode(s)
    if  sys.version_info[0] < 3 :
        s   = bytes(s.encode('utf8'))
        n   =   0
        for c  in s :
            n   = (n * 256) + ord(c)
        pass
    else    :
        s   = bytes(s, 'utf8')
        n   =   0
        for c  in s :
            n   = (n * 256) + c
        pass
    return(n)


def unicode_strftime(s) :
    """ Return the strfime'd string without fussing about unicode characters. """
    s   = convert_to_unicode(s)
    s   = s.encode('utf8')
    s   = time.strftime(s)
    s   = unicode(s, 'utf8')                                # convert back to what we really are
    return(s)




def lf_only(fs) :
    """ Convert all variants of line breaks to '\n' in a string with multiple text lines. """

    fs  = re.sub(r"\r+\n", "\n", fs)
    fs  = re.sub(r"\r",    "\n", fs)

    return(fs)




##  Get rid of leading line feeds (amounts to s.strip("\n"))
strip_first_lines_re            =   re.compile(r"^\n+",                 re.DOTALL)

##  Get rid of white-space that is at the ends of text lines inside a string.
strip_eol_spaces_re             =   re.compile(r"\s+$",                 re.MULTILINE)


def lf_only_with_no_trailing_white_space(fs) :
    """ Run a string through lf_only() and strip trailing white space from the lines, too. Insure last lines ends with LF. """

    fs  = lf_only(fs)
    fs  = strip_eol_spaces_re.sub("", fs)
    fs += "\n"

    return(fs)



def no_blank_lines(fs) :
    """
        Get rid of any blank or white-space-only lines in a string containing multiple text lines.

        Gets rid of white space at the ends of all text lines as a side effect.
        The last line is forced to end with an LF.
    """

    fs  = lf_only(fs)
    fs  = strip_eol_spaces_re.sub("", fs)
    fs += "\n"
    fs  = re.sub("\n(\s*\n)+", "\n", fs)
    fs  = strip_first_lines_re.sub("", fs)

    return(fs)



def multiline_strip(fs, chrs = None) :
    """
        Do a strip on a string with multiple text lines.
        If no \n is at the end of 'fs', there will be none at the end of the return value.
        CRLF, LF and CR all are considered to be an EOL.
    """

    fs  = "\n".join( [ s.strip(chrs) for s in re.split(r"\r?\n|\r", fs) ] )
    return(fs)
    if  chrs :
        rs  = re.escape(chrs)
    else :
        rs  = r"\s"

    fs  = lf_only(fs)

    fs  = re.sub(r"(?m)^[" + rs + "]+",  "", fs)
    fs  = re.sub(r"(?m)["  + rs + "]+$", "", fs)
    fs  = re.sub(r"^[" + rs + "]+",  "", fs)
    fs  = re.sub(r"["  + rs + "]+$", "", fs)

    return(fs)


def multiline_flush_left(s) :
    """
        Whack left-side spaces shared for all non-blank lines in the given multiline string.
        Also whacks right-side white-space.
        Also, converts to \n separated lines. No CRs.
    """

    if  isinstance(s, basestring) :
        s   = re.split(r"\r*\n", s)

    la      = [ len(ln) - len(ln.lstrip(' ')) for ln in s if ln.strip() ]
    if  la  :
        lc  = min(la)
        s   = [ ln[lc:] if ln.strip() else ln for ln in s ]
        s   = [ ln.rstrip() for ln in s ]
    return("\n".join(s))



_crc32_table = (
        0x4dbdf21c,
        0x500ae278,
        0x76d3d2d4,
        0x6b64c2b0,
        0x3b61b38c,
        0x26d6a3e8,
        0x000f9344,
        0x1db88320,
        -1610256068,        # 0xa005713c,
        -1112383144,        # 0xbdb26158,
        -1687465484,        # 0x9b6b51f4,
        -2032385648,        # 0x86dc4190,
         -690409300,        # 0xd6d930ac,
         -881975096,        # 0xcb6e20c8,
         -306769820,        # 0xedb71064,
         -268435456,        # 0xf0000000
        )

_TEST_CRC_VALUE     = -1737075662           # 0x98765432
_CRC32_MASK         = 0xFFFFffffl
try :
    _CRC32_MASK     = int(_CRC32_MASK)
except OverflowError :
    _CRC32_MASK     = -1




INITIAL_CRC32_VALUE = 0     # 0xFFFFffffl



#
#       Compute a 32-bit (PKZIP) crc of a string or array.
#
#       REMEMBER! The value could easily be construed to be a signed int. Odd things might happen in future versions of python.
#       To be "correct" about it, the value should be massaged like "crc = long(crc) & 0xFFFFffffL", yeilding a long value.
#
#


def crc32(current_crc, c) :
    c            = int(c)
    current_crc  = (((current_crc >> 4) & 0x0FFFffff) ^ _crc32_table[(current_crc ^  c      ) & 0xf]) & _CRC32_MASK
    current_crc  = (((current_crc >> 4) & 0x0FFFffff) ^ _crc32_table[(current_crc ^ (c >> 4)) & 0xf]) & _CRC32_MASK

    return(current_crc)


def pure_python_crc32(current_crc, xmem) :
    # print "crclen", len(xmem)
    for i in range(0, len(xmem)) :
        cv = ord(xmem[i:i+1])
        current_crc = crc32(current_crc, cv)
        # print "%u %08x %02x %s\n" % ( i, current_crc, cv, chr(cv) )
    return(current_crc)



def blkcrc32(current_crc, xmem) :

    if  sys.version < "2.4" :
        return(pure_python_crc32(current_crc, xmem))                # maybe 2.3 is ok. I don't know. But 2.4 is ok and 2.2 is not.

    current_crc = long(current_crc)
    if  current_crc & 0x80000000L :
        current_crc = (~current_crc + 1) & 0xFFFFffffL
        current_crc = -current_crc

    return(long(zlib.crc32(xmem, int(current_crc))) & 0xFFFFffffL)




xmcrctab    = (
        0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
        0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
        0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
        0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
        0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
        0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
        0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
        0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
        0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
        0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
        0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
        0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
        0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
        0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
        0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
        0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
        0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
        0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
        0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
        0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
        0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
        0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
        0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
        0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
        0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
        0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
        0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
        0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
        0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
        0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
        0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
        0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
        )



def crc16(current_crc, c) :
    c            = int(c)
    #                               Note: Not Xmodem CRC. Xmodem has post processing. We do the Palm CRC: "dcrc /i -1 /Wp"
    return((((current_crc) << 8) ^ xmcrctab[(((current_crc) >> 8) ^ c) & 0xff]) & 0xffff)


def pure_python_crc16(current_crc, xmem) :
    # print "crclen", len(xmem)
    for i in range(0, len(xmem)) :
        cv          = ord(xmem[i:i+1])
        current_crc = crc16(current_crc, cv)
        # print "%u %08x %02x %s\n" % ( i, current_crc, cv, chr(cv) )
    return(current_crc & 0xffff)



def blkcrc16(current_crc, xmem) :
    return(pure_python_crc16(current_crc, xmem))






def xorsum(s) :
    """
        Compute the longitudinal parity (the XOR sum) of a string.
    """

    cs      = 0
    for c in s :
        cs ^= ord(c)

    return(cs)







def str_base(n, base = 10) :
    """ Convert a number to a lower case string like str(), but with a given base from 2 to 36. """
    if  n < 0 :
        return('-' + str_base(-n, base))

    (d, m)  = divmod(n, base)

    return(((d and str_base(d, base)) or "") + chr((((m < 10) and 48) or 87) + m))


#
#
#   Find the "cosine" of the angle between two vectors.
#
#       If either vector is None, then the return value is None.
#       If all elements of either of the arrays are zero (or None), then the return value is None.
#
#       Undef'd values in the arrays are ignored - that "dimension" is ignored, that is.
#
#       cosine = tz_vector_cosine( [ 1, 2, 3 ], [ 4, -5, 6 ])
#
#       If the vectors are the same, then the return value is 1.
#       If the vectors are exactly opposite in direction and magnitude, then the return value is -1.
#       If the vectors are exactly at "right angles", then the return value is 0.
#
#
def tz_vector_cosine(v1, v2) :
    """
        Return the hyperspace cosine of two vectors.
    """

    if  (v1 is None) or (v2 is None) :    return(None)

    if  (len(v1) == len(v2)) and numpy and is_numpy_array(v1) and is_numpy_array(v2) and v1.sum() and v2.sum() and numpy.all(numpy.isfinite(v1)) and numpy.all(numpy.isfinite(v2)) :
        #
        #   This numpy method from: https://stackoverflow.com/questions/2827393/angles-between-two-n-dimensional-vectors-in-python/13849249#13849249
        #
        def unit_vector(vector):
            """ Returns the unit vector of the vector.  """
            return(vector / numpy.linalg.norm(vector))

        v1_u = unit_vector(v1)
        v2_u = unit_vector(v2)
        return(numpy.clip(numpy.dot(v1_u, v2_u), -1.0, 1.0))

    sum      = 0.0
    ss1      = 0.0
    ss2      = 0.0

    for i in range(0, min(len(v1), len(v2))) :

        i1   = v1[i]
        if  i1 is None :    continue

        i2   = v2[i]
        if  i2 is None :    continue

        i1   = float(i1)
        i2   = float(i2)

        sum += (i1 * i2)
        ss1 += (i1 * i1)
        ss2 += (i2 * i2)

    if  (ss1 == 0.0) or (ss2 == 0.0) :
        return(None)

    return(sum / math.sqrt(ss1 * ss2))


def angle_between_two_vectors(v1, v2) :
    """ Return the angle between 2 N dimensional vectors. Return None if either is None or if either is a point. Ignore None values in particular dimensions. """
    cos = tz_vector_cosine(v1, v2)
    if  cos is None :
        return(None)
    return(math.acos(cos))



#
#
#       Decode HTML character entities in a string.
#
#
html_entity_re  = re.compile(r"\&[^;\r\n\s]+;", re.DOTALL)

def decode_html_entities(s, nbsp_chr = None) :
    """
        Decode any HTML character entities in the string to their character counterparts.

        The string given to this routine should be unicode.
        If it isn't then unicode (> Latin 1) characters will
        cause python fusses of

        "TypeError: function takes exactly 5 arguments (1 given)"

        or other bad things to happen.

    """

    if  nbsp_chr is None :
        nbsp_chr  = htmlentitydefs.entitydefs['nbsp']

    def _entity_2_chr(g) :
        try :
            w = g.group(0)[1:-1]

            if htmlentitydefs.entitydefs.has_key(w) :
                if  w == 'nbsp' :
                    w = nbsp_chr
                else :
                    w = htmlentitydefs.entitydefs[w]
                pass

            if  (w[0:2] == '&#') and (w[-1:] == ';') :          # handles ints from htmlentitydefs
                w   = w[2:-1]
                try :
                    w = chr(int(w))
                except ValueError :
                    w = unichr(int(w))
                pass
            elif w[0:1] == '#' :                                # handles normal int values from the passed string
                w   = w[1:]
                try :
                    w = chr(int(w))
                except ValueError :
                    w = unichr(int(w))
                pass

            pass

        except ValueError :
            w = g.group(0)

        if  len(w) == 1 :
            if  128  <= ord(w) < 256 :
                w   = w.decode('latin1')
            elif 256 <= ord(w) :
                pass
            pass

        return(w)


    return(html_entity_re.sub(_entity_2_chr, s))




def safe_html(txt) :
    """
        Convert the given string in to text that can be put out in an HTML page without worry of it being interpreted as anything but text.
    """

    def _decimalfy(s) :
        return("&#" + str(ord(s.group(0))) + ";")

    txt = txt.replace("&", "&amp;")
    txt = txt.replace("<", "&lt;")
    txt = txt.replace(">", "&gt;")
    txt = lf_only(txt)
    txt = re.sub(r"[\r\n]", "<BR>", txt)
    txt = re.sub(r"-{6,}", "<HR>", txt)
    txt = re.sub(r"[^ -\176]", _decimalfy, txt)             # escape all characters outside of space-to-tilde

    return(txt)



cdata_string_re = re.compile(r"^<\!\[CDATA\[.*\]\]>$")

def maybe_wrap_with_cdata(s) :
    """ If needed, and if it's not already wrapped with a CDATA tag, wrap this string with <[!CDATA[...]]> """

    if  not cdata_string_re.match(s) :
        ns  = safe_html(s)
        if  ns != s :
            s   = "<![CDATA[%s]]>"  % ( s.replace("]]>", "&#93;&#93;&gt;") )
        pass

    return(s)





de_br_re        = re.compile(r"</?br\b[^>]{0,200}>",                    re.DOTALL + re.IGNORECASE)
de_p_re         = re.compile(r"</?p\b[^>]{0,200}>",                     re.DOTALL + re.IGNORECASE)
de_hr_re        = re.compile(r"<hr\b[^>]{0,200}>",                      re.DOTALL + re.IGNORECASE)
de_lfs_re       = re.compile(r"<(/?(?:table|tr|dir|li|ol|ul|dt|dl))\b", re.DOTALL + re.IGNORECASE)

de_tab_re       = re.compile(r"\t",                                     re.DOTALL + re.IGNORECASE)
de_space_re     = re.compile(r" +",                                     re.DOTALL + re.IGNORECASE)

de_lf_re        = re.compile(r"\n *\n *(?:\n *)+",                      re.DOTALL + re.IGNORECASE)
de_lfsp_re      = re.compile(r"\n +",                                   re.DOTALL + re.IGNORECASE)
de_splf_re      = re.compile(r" +\n",                                   re.MULTILINE)
de_multlf_re    = re.compile(r"\n+",                                    re.DOTALL + re.IGNORECASE)

de_script_re    = re.compile(r"<script\b[^>]*>.*?</script\b[^>]*>",     re.DOTALL + re.IGNORECASE)

de_html_re      = re.compile(r"<[^>]+>",                                re.DOTALL + re.IGNORECASE)


def de_html_str(s) :
    """
        Do cheaply what something like lynx could do from the command line:
          Convert a string that contains HTML in to a text string with no HTML markup, but
          with the text looking kind of like the HTML would look if rendered as text.
    """
    if  isinstance(s, ListType) or isinstance(s, TupleType) :
        s       = "\n".join(s)

    s = de_tab_re.sub(" ", s)
    s = de_space_re.sub(" ", s)

    s = s.replace("\r\n", "\n")
    s = s.replace("\r",   "\n")
    s = de_lf_re.sub("\n\n", s)
    s = de_lfsp_re.sub("\n", s)
    s = de_splf_re.sub("\n", s)
    s = de_multlf_re.sub("\n", s)

    s = de_br_re.sub("\n", s)
    s = de_p_re.sub("\n\n", s)
    s = de_hr_re.sub("\n----------------------------------------\n", s)
    s = de_lfs_re.sub(r"\n<\1", s)

    s = de_script_re.sub("", s)

    s = de_html_re.sub("", s)                                   # remove all HTML tags

    s = decode_html_entities(s, nbsp_chr = ' ')

    s = s.replace(unicode("\x93", 'latin1'), '"')               # left  double quote
    s = s.replace(unicode("\x91", 'latin1'), '`')               # left  single quote
    s = s.replace(unicode("\x94", 'latin1'), '"')               # right double quote
    s = s.replace(unicode("\x92", 'latin1'), "'")               # right single quote
    s = s.replace(unicode("\x9c", 'latin1'), "oe")
    s = s.replace(unicode("\x96", 'latin1'), "-")               # n-dash

    s = s.replace(unicode("\x8b", 'latin1'), "<")
    s = s.replace(unicode("\x9b", 'latin1'), ">")
    s = s.replace(unicode("\x8c", 'latin1'), "OE")


    s = s.replace(unicode("\xa0", 'latin1'), ' ')               # non-break space
    s = s.replace(        "\x7f",            ' ')               # rubout

    s = s.replace(unicode("\xad", 'latin1'), "-")               # soft hyphen
    s = s.replace(unicode("\xaf", 'latin1'), "-")               # macron mark
    s = s.replace(unicode("\xab", 'latin1'), "<<")
    s = s.replace(unicode("\xbb", 'latin1'), ">>")

    s = de_tab_re.sub(" ", s)
    s = de_space_re.sub(" ", s)

    s = de_lf_re.sub("\n\n", s)
    s = de_lfsp_re.sub("\n", s)
    s = de_splf_re.sub("\n", s)

    return(s)




def string_pairs(a, skip = 1, connector_str = "\x80_Pr_") :
    """
        Return an array of strings composed of each sequential pair in the given array of strings.
    """
    if  False :

        t2  = [ 0 ] * (len(a) - skip)

        for i in xrange(len(t2)) :
            t2[i]   =   a[i] + connector_str + a[i + skip]
        pass

    else :
        t2          = [ a[i] + connector_str + a[i + skip] for i in xrange(len(a) - skip) ]

    return(t2)




def flat_positional_strings(strings, buckets = 3, ident_str = "\x80_Pf%u_%s") :
    """
        Given an array of strings, return an array of the strings with their positions in the array concatenated with them.
        For each string, there are two positions so that equal strings in similar positions in two arrays will yield at least one equal position string.
        The 'buckets' refers to how many sections of the array are used. Each identical string inside a particular bucket/section will yield at least one positional string that's the same.
            The first positional  string will identify the bucket the string is in.
            The second positional string will identify which pair of buckets the string is in and nearest.
        The 'ident_str' must contain a %u and a %s in that order.
            The %u will be filled in with the bucket or half-bucket number.
            The %s will be filled in with the string.
    """

    if  len(strings) == 0 :
        return( [] )


    pstrs   = [ 0 ] * (len(strings) * 2)


    def _fill(pi, ni, ne, n) :
        # print pi, ni, ne, n
        for i in xrange(ni, min(ne, len(strings))) :
            pstrs[pi]   = ident_str % ( n, strings[i] )
            pi         += 1

        return( ( pi, ne ) )

    d   = min((len(strings) + buckets - 1) / buckets, 2)
    d2  = (d + 1) / 2
    i   = pi = 0
    ni  = 0
    while ni < len(strings) :
        (pi, ni)    = _fill(pi, ni, ni + d2,  i)
        d2          = d
        i          += 1

    ni  = 0
    while ni < len(strings) :
        (pi, ni)    = _fill(pi, ni, ni + d2,  i)
        i          += 1

    return(pstrs)



def sigmoid(x) :
    """
        Return a sigmoid of the given value. That is, return an
        S-curve number ranging from 0.0 to +1.0 given numbers
        ranging from -any_number to +any_number.

        Do the special riff for negative numbers so there won't be a math range error.

    """
    if  x >= 0 :
        return(1.0 / (1.0 + math.exp(-x)))
    z   = math.exp(x)
    return(z / (1.0 + z))

def delta_of_sigmoid(s) :
    """ Return the 1st derivative of the given sigmoid (0.0...1.0) value. """
    return(s * (1.0 - s))

def sigmoid_delta(x) :
    """
        Return the 1st derivative of the sigmoid of the given value.

        Note that in NN stuff, the S=sigmoid(x) value is already computed,
        so you'll just want to use delta_of_segmoid(sigmoid_of_x)
        directly.

    """
    return(sigmoid(x) * (1.0 - sigmoid(x)))

sigmoid_derivative  = sigmoid_delta


def tanh(t) :
    """
        Return a tanh of the given value. That is, return an
        S-curve number ranging from -1.0 to +1.0 given numbers
        ranging from -any_number to +any_number.

    """
    return(math.tanh(t))

def delta_of_tanh(t) :
    """ Return the 1st derivative of the given tanh (-1.0...1.0) value. """
    return(1.0 - (t ** 2))

def tanh_delta(x) :
    """ Return the 1st derivative of the tanh or the given value. """
    return(delta_of_tanh(tanh(x)))

tanh_derivative = tanh_delta



phi = 1.618033988749894848204586834

def golden_smaller(v) :
    return(v * (1.0 / phi))


def golden_portrait_rectangle(w, h) :
    """ Given a width and hite, return the width and hite of a possibly-larger, golden-ratio-sized, portrait-aspect-ratio rectangle that encompasses the given one. """
    if  h   > w * phi :
        w   = h / phi
    else    :
        h   = w * phi
    return(w, h)


def golden_landscape_rectangle(w, h) :
    """ Given a width and hite, return the width and hite of a possibly-larger, golden-ratio-sized, landscape-aspect-ratio rectangle that encompasses the given one. """
    if  w   > h * phi :
        h   = w / phi
    else    :
        w   = h * phi
    return(w, h)


def golden_rectangle(w, h) :
    """ Given a width and hite, return the width and hite of a possibly-larger, golden-ratio-sized rectangle that encompasses the given one. """
    if  w < h   :
        return(golden_portrait_rectangle(w, h))
    return(golden_landscape_rectangle(w, h))



def ellipse_perimeter(side_a, side_b) :
    """ Return an approximation of the perimeter/circumference of an ellipse. """
    #   From: https://www.mathsisfun.com/geometry/ellipse-perimeter.html
    #   Approx 2:
    # c2  = math.pi * abs(3.0 * (side_a + side_b) - math.sqrt(((3.0 * side_a) + side_b) * (side_a + (3.0 * side_b))))
    #   Approx 3:
    h   = ((side_a - side_b) ** 2.0) / ((side_a + side_b) ** 2.0)
    c3  = math.pi * (side_a + side_b) * ( 1.0 + ( (3.0 * h) / (10.0 + math.sqrt(4.0 - (3.0 * h))) ) )
    # print c2, c3, "%12.10f" % abs(c2 - c3), math.pi * side_a * 2, math.pi * side_b * 2
    return(c3)



def log_positional_strings(strings, log_mult = 2.0, ident_str = "\x80_Pl_%u_%s") :
    """
        Given an array of strings, return an array of the strings with their positions in the array concatenated with them.
        For each string, create a string concatenated with the log of the position of the string in 'strings' multiplied by 'log_mult'.
        The 'ident_str' must contain a %u and a %s in that order.
            The %u will be filled in with the string's position in 'strings'
            The %s will be filled in with the string.
    """

    sa  = [ ident_str % ( int(math.log(i + 1) * log_mult), strings[i] ) for i in xrange(len(strings)) ]

    return(sa)



def lower_left_bit(n) :
    """ Make 0 the left-most 1 bit of the given number. E.g. 0b0100->0b0000 or 0b0101->0b0100 or 0b0111->0b0110 or 0b0110->0b0100. """
    return(n & (n - 1))


def raise_left_zero_bit(n) :
    """ Make 1 the left-most 0 bit of the given number. E.g. 0b0100->0b0101 or 0b0101->0b0111 or 0b0111->0b1111. """
    return(n | (n + 1))


def only_right_zero_bit(n) :
    """ Return a number with only the given number's right most 0 bit a 1. E.g. 0b1001->0b0010 or 0b0111->0b1000. """
    return(~n & (n + 1))

def raise_all_right_xero_bits(n) :
    """ Return the given number with all far right-side 0 bits set to 1.   E.g. 0b0100->0b0111   or 0x0101->0b0101. """
    return(n | (n - 1))


def bit_length(n) :
    """
        Return the bit number of the left-most bit in the given number.

        In the case of 0, return 0.

        Compatible with 2.7 and 3.1+ int.bit_length().

        Another approach is int(math.log(abs(n),2))+1
        Rounding might be a problem with really big numbers.
        No timing test done with either the log or a simple loop or this cute method.

    """
    if  not n:
        return(0)
    return(len(bin(abs(n))) - 2)


#
#   http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable
#
bit_count_table = [0] * 0x10000
for i in range(len(bit_count_table)) :
    bit_count_table[i]  = (i & 1) + bit_count_table[i >> 1]
del(i)

def _bit_count(v) :
    if  numpy and numpy.isscalar(v) :
        v   = int(v)
    c       = 0
    while v :
        c  += (
                  bit_count_table[ v        & 0xffff]
                + bit_count_table[(v >> 16) & 0xffff]
                + bit_count_table[(v >> 32) & 0xffff]
                + bit_count_table[(v >> 48) & 0xffff]
              )
        v >>= 64
    return(c)

def _loop_bit_count(n) :
    """ Return the number of 1 bits in the given number. """
    c   = 0
    while n :
        n  &= (n - 1)
        c  += 1
    return(c)

bit_count   = _bit_count            # table lookup is twice as fast with lots of bits, can be a hair slower with values in 0..15



def un_gray_code(g) :
    """ Convert from grey code (gray code) back to binary """
    g ^= (g >>  1)
    g ^= (g >>  2)
    g ^= (g >>  4)
    g ^= (g >>  8)
    g ^= (g >> 16)
    g ^= (g >> 32)
    if  g   > 0x0fffFFFF :
        g  ^= (g >> 64)
        g  ^= (g >> 128)
        g  ^= (g >> 256)
        b   = 512
        while g >= (1 << b) :
            g  ^= (g >> b)
            b <<= 1
        pass
    return(g)
un_grey_code    = un_gray_code


def gray_code(n) :
    """ Convert a binary number to gray code. """
    return(n ^ (n >> 1))
grey_code       = gray_code


_balanced_8_bit_gray_code_table = [
                                    0x00, 0x01, 0x03, 0x02, 0x06, 0x0E, 0x0A, 0x0B, 0x09, 0x0D, 0x0F, 0x07, 0x05, 0x04, 0x0C, 0x08,
                                    0x18, 0x1C, 0x14, 0x15, 0x17, 0x1F, 0x3F, 0x37, 0x35, 0x34, 0x3C, 0x38, 0x28, 0x2C, 0x24, 0x25,
                                    0x27, 0x2F, 0x2D, 0x29, 0x39, 0x3D, 0x1D, 0x19, 0x1B, 0x3B, 0x2B, 0x2A, 0x3A, 0x1A, 0x1E, 0x16,
                                    0x36, 0x3E, 0x2E, 0x26, 0x22, 0x32, 0x12, 0x13, 0x33, 0x23, 0x21, 0x31, 0x11, 0x10, 0x30, 0x20,
                                    0x60, 0x70, 0x50, 0x51, 0x71, 0x61, 0x63, 0x73, 0x53, 0x52, 0x72, 0x62, 0x66, 0x6E, 0x7E, 0x76,
                                    0x56, 0x5E, 0x5A, 0x7A, 0x6A, 0x6B, 0xEB, 0xEA, 0xFA, 0xDA, 0xDE, 0xD6, 0xF6, 0xFE, 0xEE, 0xE6,
                                    0xE2, 0xF2, 0xD2, 0xD3, 0xF3, 0xE3, 0xE1, 0xF1, 0xD1, 0xD0, 0xF0, 0xE0, 0xA0, 0xB0, 0x90, 0x91,
                                    0xB1, 0xA1, 0xA3, 0xB3, 0x93, 0x92, 0xB2, 0xA2, 0xA6, 0xAE, 0xBE, 0xB6, 0x96, 0x9E, 0x9A, 0xBA,
                                    0xAA, 0xAB, 0xBB, 0x9B, 0x99, 0x9D, 0xDD, 0xD9, 0xDB, 0xFB, 0x7B, 0x5B, 0x59, 0x5D, 0x7D, 0x79,
                                    0xF9, 0xFD, 0xBD, 0xB9, 0xA9, 0xE9, 0x69, 0x6D, 0x6F, 0x67, 0x65, 0x64, 0xE4, 0xE5, 0xE7, 0xEF,
                                    0xED, 0xAD, 0xAF, 0xA7, 0xA5, 0xA4, 0xAC, 0xEC, 0x6C, 0x68, 0xE8, 0xA8, 0xB8, 0xF8, 0x78, 0x7C,
                                    0xFC, 0xBC, 0xB4, 0xB5, 0xB7, 0xF7, 0xF5, 0xF4, 0x74, 0x75, 0x77, 0x7F, 0xFF, 0xBF, 0x9F, 0xDF,
                                    0x5F, 0x57, 0x55, 0x54, 0xD4, 0xD5, 0xD7, 0x97, 0x95, 0x94, 0x9C, 0xDC, 0x5C, 0x58, 0xD8, 0x98,
                                    0x88, 0xC8, 0x48, 0x4C, 0xCC, 0x8C, 0x84, 0xC4, 0x44, 0x45, 0xC5, 0x85, 0x87, 0xC7, 0x47, 0x4F,
                                    0xCF, 0x8F, 0x8D, 0xCD, 0x4D, 0x49, 0xC9, 0x89, 0x8B, 0xCB, 0x4B, 0x4A, 0xCA, 0x8A, 0x8E, 0xCE,
                                    0x4E, 0x46, 0xC6, 0x86, 0x82, 0xC2, 0x42, 0x43, 0xC3, 0x83, 0x81, 0xC1, 0x41, 0x40, 0xC0, 0x80,
                                  ]

def balanced_8_bit_gray_code(n) :
    """ Return a balanced gray code for the given number 0..255. """
    return(_balanced_8_bit_gray_code_table[n])


_balanced_8_bit_un_gray_code_table  = [ 0 ] * 256
for i in xrange(len(_balanced_8_bit_un_gray_code_table)) :
    _balanced_8_bit_un_gray_code_table[_balanced_8_bit_gray_code_table[i]]  = i
del(i)

def balanced_8_bit_un_gray_code(g) :
    return(_balanced_8_bit_un_gray_code_table[g])





def de_bruijn(alphabet_or_number_of_symbols, n):
    """
        Get a De Bruijn sequence for subsequences of length 'n' taken from 'alphabet'.

        The returned value contains all possible unique sequences of length 'n'
        of values from 'alphabet'.

        Rememeber that to get all sequences, you must wrap around such that the last sequence
        will start with the last item in the returned value.

        Return a string if given 'alphabet' as a string. Otherwise, return a list.

        Code is modified from https://en.wikipedia.org/wiki/De_Bruijn_sequence

    """

    try:
        alphabet    = range(int(alphabet_or_number_of_symbols))     # make a list if given a symbol count
    except ( ValueError, TypeError, ) :
        alphabet    = alphabet_or_number_of_symbols                 # otherwise

    k           = len(alphabet)
    a           = [0] * k * n
    sequence    = []


    def db(t, p) :
        if t > n :
            if n % p == 0 :
                sequence.extend(a[1:p + 1])
            pass
        else            :
            a[t]        = a[t - p]
            db(t + 1, p)
            for j in xrange(a[t - p] + 1, k) :
                a[t]    = j
                db(t + 1, t)
            pass
        pass


    db(1, 1)
    if  isinstance(alphabet_or_number_of_symbols, basestring) :
        sequence    = "".join(alphabet[i] for i in sequence)

    return(sequence)




p2  = math.pi * 2.0


#
#
#   From Wikipedia      http://en.wikipedia.org/wiki/Hilbert_curve
#
#

def _sqhilbert_rot(n, x, y, rx, ry) :
    """ Rotate the situation. """
    if  not ry :
        if  rx == 1 :
            x = n - 1 - x
            y = n - 1 - y
        x, y    = y, x
    return(x, y)


def square_hilbert_xy_to_i(n, x, y) :
    """
        Convert a 2D location in a square to the appropriate location on a Hilbert curve.

        n   is the width/hite - must be a power of 2.
        x   is the current X location.
        y   is the current Y location.

        The curve will go from upper left to upper right, assuming X increases to the right and Y increases going down.

        Returns a new curve index [0 ... (max(x) + 1) * (max(y) + 1)).
    """
    i   = 0
    s   = n / 2
    while s > 0 :
        rx      = ((x & s) > 0)
        ry      = ((y & s) > 0)
        i      += s * s * ((3 * rx) ^ ry)
        x, y    = _sqhilbert_rot(s, x, y, rx, ry)
        s      /= 2
    return(i)


def square_hilbert_i_to_xy(n, i)  :
    """
        Convert a location on a Hilbert curve to a 2D location on a square.

        n   is the width/hite - must be a power of 2.
        i   is the index/location on the Hilbert curve.  [0 ... n * n)

        The curve will go from upper left to upper right, assuming X increases to the right and Y increases going down.

        Returns the X and Y location.

    """
    x   = 0
    y   = 0
    s   = 1
    while s < n :
        rx      = 1 & (i/2)
        ry      = 1 & (i ^ rx)
        x, y    = _sqhilbert_rot(s, x, y, rx, ry)
        x      += s * rx
        y      += s * ry
        i      /= 4
        s      *= 2
    return(x, y)



def make_2D_starburst(rings = None, spokes = None, starting_radians = None, ring_step_radians = None) :
    """
        Return an array of [ x, y ] values for points around zero.

        Default to outputting 8 points, 4 at 1.0 away from 0,0 in e/n/w/s directions, and 4 at 2.0 away from 0,0 at ne/nw/sw/se.

    """
    rings               = int(rings         or 2)   # default to 2 rings
    spokes              = int(spokes        or 4)   # default to orthoganal points
    angle               = starting_radians  or 0.0  # default to the first point being due east
    step_angle          = p2 / spokes
    ring_step_radians   = ((ring_step_radians is None) and (step_angle / 2.0)) or ring_step_radians
    retval              = []
    for r in xrange(1, rings + 1) :
        for s in xrange(spokes) :
            retval.append([ math.cos(angle) * r, math.sin(angle) * r ])
            angle      += step_angle
        angle          += ring_step_radians
    return(retval)



def linear_regression(ax = None, ay = None):
    """
        Given an array of ax and ay values,
        return the values that make a linear regression line,
        (m * ax[i]) + a = ay[i]
    """

    ay  = ay or []

    if  not ax :
        ax  = range(len(ay))

    if  (not isinstance(ax, ListType)) and (not isinstance(ax, TupleType)) :
        ax  = [ x + ax for x in xrange(len(ay)) ]

    if  len(ax) != len(ay) :
        raise ValueError('Different length of ax and ay array')

    sx      = sy    =   sxx =   syy =   sxy =   0.0
    c       = 0
    for i   in xrange(len(ax)) :
        x   = ax[i]
        y   = ay[i]
        if  (x is not None) and (y is not None) :
            c  += 1
            sx  = sx + x
            sy  = sy + y

            sxx = sxx + (x * x)
            syy = syy + (y * y)
            sxy = sxy + (x * y)
        pass

    d       =  (sxx *  c) - (sx * sx)

    if  not d :
        raise ValueError('Vertical line')
        m   = sum(ay + [ 0.0 ]) / (len(ay) or 1.0)      # special case a vertical line for no real reason other than to stop the crash
        a   = 0.0
    else    :
        m   = ((sxy *  c) - (sy * sx )) / d
        a   = ((sxx * sy) - (sx * sxy)) / d

    return( ( m, a ) )



KILOMETERS_TO_MILES = 0.621371192                       #: multiply klicks by this to get miles. Divide to get kilometers from miles.
MILES_PER_KILOMETER = KILOMETERS_TO_MILES               #: ditto


def torus_goto_distance(frm, to, w) :
    """
        How far and in what direction is the nearest way from a to b on a torus (a loop, really) of width w?

        Returns a value from [-w / 2 to w / 2].

    """
    w2  = w / 2
    return((((to - frm) + w2) % w) - w2)


def torus_distance(a, b, w) :
    """
        How far apart are a and b on a torus (a loop, really) of width w?

        Returns a value from [0 to w / 2).

    """
    return(abs(torus_goto_distance(b, a, w)))




def y_from_distance_direction(dist, direction) :
    """
        Get the Y value from a distance and a direction (direction is -pi..pi, where both pi's are at y<0:x=0 and 0 direction is y=0:x>=0)
    """

    return(dist * math.sin(direction))


def x_from_distance_direction(dist, direction) :
    """
        Get the X value from a distance and a direction (direction is -pi..pi, where both pi's are at y<0:x=0 and 0 direction is y=0:x>=0)
    """

    return(dist * math.cos(direction))


def distance_from_x_y(x, y, z = None) :
    """
        Get the distance from an X and Y value.
        This routine is here for documentation purposes, folks.
    """
    if  z is not None :
        return(math.sqrt((x * x) + (y * y) + (z * z)))
    return(math.hypot(y, x))


def direction_from_x_y(x, y) :
    """
        Get the direction from an X and Y value.
        This routine is here for documentation purposes, folks.
    """

    return(math.atan2(y, x))


def compass_angle(radians) :
    """ Return the compass angle of the given angle. 0..360. And 0 is north. """
    return((math.degrees(-(radians - (math.pi / 2.0))) + 720.0) % 360.0)



def radian_angle_difference(a, b) :
    """
        What's the angle between these two angles (in radians. that is, += 0..pi - to get degrees, math.degrees(a))?
    """
    return((((a - b) + math.pi) % p2) - math.pi)        # 12% quicker, ballpark, than the original code

    #
    #   Original code: closer than 0.00000000000001 to the above.
    #
    #   Slower is:  math.atan2(math.sin(a - b), math.cos(a - b))
    #
    a       = (a - b) % p2

    if  a   >  math.pi :
        a  -=  p2

    if  a   < -math.pi :
        a  +=  p2

    return(a)


def radian_angle_from_horizontal(a) :
    """ Return the absolute angle the given angle is from horizontal. """
    a  %= math.pi
    return(min(abs(a), abs(math.pi - a)))



def angle_from_triangle_sides(side1, side2, opp_side) :
    """ Given 3 side lengths of a triangle, return the angle between the first two sides. """
    side1       = float(side1)
    side2       = float(side2)
    opp_side    = float(opp_side)
    return(math.acos((side1*side1 + side2*side2 - opp_side*opp_side) / (2 * side1 * side2)))


def rite_triangle_hite_from_angle_and_hypotenuse(a, hypot) :
    """ Return one of the sides of a right triangle, given an angle and hypotenuse. """
    return(hypot * math.sin(a))                             # watch out for extreme angles - loss of precision situation


def area_of_triangle_given_sides(a, b, c) :
    """ Return the area of a triangle given the lengths of the 3 sides. """
    p   = (a + b + c) / 2.0
    return(math.sqrt(p * (p - a) * (p - b) * (p -c)))       # Heron's formula


def average_angle(radian_angles) :
    """
        Return the average angles of the given radian angles. Return value is [0..2*pi).

        Note: This routine won't necessarily return the simple
        average of some angles even though they are all within
        180 degrees of each other. The idea is that the space
        angles reside in isn't really linear with respect to the
        angle, itself.

    """
    if  radian_angles is None :
        return(None)
    if  not len(radian_angles) :
        return(0.0)
    y   = sum([ math.sin(a) for a in radian_angles ]) / float(len(radian_angles))
    x   = sum([ math.cos(a) for a in radian_angles ]) / float(len(radian_angles))
    if  abs(y) < 0.00000000001 :
        y   = 0.0                                                       # this keeps us from figuring the angle of a very, very, very short vector, instead of calling it 0 degrees
    if  abs(x) < 0.00000000001 :
        x   = 0.0
    return(math.atan2(y, x) % p2)


def average_modulo(numbers, modulo) :
    """
        Return the average of the given numbers modulo the modulo.

        See the note in average_angle() for why the average of these numbers isn't the straight average of the numbers.
        Modulo space implies a world that's not linear.

    """
    if  (numbers is None) or (modulo is None) :
        return(None)

    modulo  = abs(float(modulo))
    numbers = [ (p2 * ((n % modulo) / modulo)) for n in numbers ]
    a       = average_angle(numbers)
    return(modulo * a / p2)



def rectangle_intersection(r1, r2) :
    """ Given two x,y,w,h rectangles, return a rectangle that's the intersection of the two. If they don't overlap the returned rectangle will have a zero width and hite. """
    r       = copy.deepcopy(r1)
    r[0]    = max(r1[0], r2[0])
    r[2]    = max(0, min(r1[0] + r1[2], r2[0] + r2[2]) - r[0])
    r[1]    = max(r1[1], r2[1])
    r[3]    = max(0, min(r1[1] + r1[3], r2[1] + r2[3]) - r[1])
    return(r)


def rectangle_union_area(r1, r2) :
    """ Given two x,y,w,h rectangles, return the area of the union of the two. """
    i       = rectangle_intersection(r1, r2)
    return((r1[2] * r1[3]) + (r2[2] * r2[3]) - (i[2] * i[3]))





#
#
#   Notes from:
#       http://www.wikihow.com/Calculate-the-Area-of-a-Polygon
#
#       Going clockwise, the area is negated, which can be used to "identify a cyclic path or sequence of a set of points forming a polygon".
#       "If you use it on a shape where two of the lines cross like a figure eight, you will get the area surrounded counterclockwise minus the area surrounded clockwise."
#
#
def area_of_irregular_polygon(counter_clockwise_vertices) :
    """ Give a list of corners [ [ x, y ] ... ] in counter clockwise order, return the area of the polygon they describe. """
    if  len(counter_clockwise_vertices) <= 1 :
        return(0.0)

    va  = list(counter_clockwise_vertices)
    va.append(counter_clockwise_vertices[0])

    psm = sum([ va[vi][0] * va[vi + 1][1] for vi in xrange(len(va) - 1) ])
    nsm = sum([ va[vi][1] * va[vi + 1][0] for vi in xrange(len(va) - 1) ])
    return((psm - nsm) / 2.0)




def poly_from_xy_list(xyl) :
    """ Given a list list of XY values in X, Y, X, Y, ... form, return a list of [ X, Y, ], [ X, Y, ] ... """
    return([ [ xyl[i], xyl[i + 1], ] for i in xrange(0, len(xyl), 2) ])


#
#
#       Notes:
#           Look for Weiler-Atherton clipping algorithm.
#
#           Also:
#               http://math.stackexchange.com/questions/141798/two-quadrilaterals-intersection-area-special-case
#               http://www.cplusplus.com/forum/lounge/30271/
#               http://sourceforge.net/projects/polyclipping/
#
#
def clip_polygon(subjectPolygon, clipPolygon) :
    """
        Finds the intersection of two polygons using Sutherland-Hodgman algorithm.

        The polygons are in [ X, Y, ], [ X, Y, ] ... form. Untested whether both clockwise and counter-clockwise winding work.

        From    http://rosettacode.org/wiki/Sutherland-Hodgman_polygon_clipping

        Modified to return the list in the winding order of subjectPolygon

        Clip rectangle seems to need to be a rectangle - maybe even orthogonal. Jury is out on the latter, but sensing clip vertices inside subject using point_in_polygon() finds overlap where there was none from this routine..

    """

    if  (area_of_irregular_polygon(subjectPolygon) < 0) != (area_of_irregular_polygon(clipPolygon) < 0) :       # if the winding orders are different, reverse the clipPolygon.  http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
        clipPolygon = list(clipPolygon)
        clipPolygon.reverse()

    def inside(p) :
        return((cp2[0]-cp1[0])*(p[1]-cp1[1]) > (cp2[1]-cp1[1])*(p[0]-cp1[0]))

    def computeIntersection() :
        dc = [ cp1[0] - cp2[0],  cp1[1] - cp2[1] ]
        dp = [   s[0] -   e[0],    s[1] -   e[1] ]
        n1 =   cp1[0] * cp2[1] - cp1[1] * cp2[0]
        n2 =     s[0] *   e[1] -   s[1] *   e[0]
        n3 =   1.0 / ((dc[0] * dp[1]) - (dc[1] * dp[0]))
        return([(n1*dp[0] - n2*dc[0]) * n3, (n1*dp[1] - n2*dc[1]) * n3])

    outputList  = subjectPolygon
    cp1         = clipPolygon[-1]

    for clipVertex in clipPolygon :
        if  not len(outputList) :
            break

        cp2         = clipVertex
        inputList   = outputList
        outputList  = []
        s           = inputList[-1]

        for subjectVertex in inputList :
            e   = subjectVertex
            if  inside(e) :
                if not inside(s) :
                    outputList.append(computeIntersection())
                outputList.append(e)
            elif inside(s) :
                outputList.append(computeIntersection())
            s   = e
        cp1     = cp2

    return(outputList)



ninety_degrees_counter_clockwise    = math.pi / 2.0

class   a_point(object) :
    def __init__(me, x, y) :
        me.x        = x
        me.y        = y
    def rotate(me, a = ninety_degrees_counter_clockwise) :
        """ Rotate the point 90 degrees counter-clockwise around 0,0. (Note the unfortunate historical angle default. The angle is in radians, counter-clockwise. Zero angle is 3 o-clock, for None purposes.) """
        if  a is None   :
            a           = math.atan2(me.y, me.x)
        if  a          == ninety_degrees_counter_clockwise :
            me.x, me.y  = -me.y, me.x           # go fast
        else            :
            s           = math.sin(a)
            c           = math.cos(a)
            me.x, me.y  = (me.x * c) - (me.y * s), (me.x * s) + (me.y * c)
        pass
    def __str__(me) :
        return("%f:%f" % ( me.x, me.y, ))
    #   a_point


#
#   Some thought should be given to the code at http://totologic.blogspot.fr/2014/01/accurate-point-in-triangle-test.html
#
def point_in_polygon(poly, p) :
    """
        Given a list of a_point() vertices of a polygon, and an a_point() 'p' , return True if the point is inside the polygon.

        Points on left/top are inside. Points on right/bottom edges are outside.
        But when the edges are at angles, return values can be not what one might expect - or want.

    """
    mnx     = poly[0].x
    mxx     = poly[0].x
    mny     = poly[0].y
    mxy     = poly[0].y
    for v  in poly[1:] :
        mnx = min(v.x, mnx)
        mxx = max(v.x, mxx)
        mny = min(v.y, mny)
        mxy = max(v.y, mxy)

    if  not ((mnx <= p.x < mxx) and (mny <= p.y < mxy)) :         # note: allow high on the line to be inside
        return(False)

    inside  = False
    j       = len(poly) - 1
    i       = 0
    while i < len(poly) :
        # if  (poly[i].x == p.x) and (poly[i].y == p.y) :
        #     return(True)                # on a vertex
        if  poly[i].y == poly[j].y :
            return((poly[i].x <= p.x < poly[j].x) or (poly[j].x < p.x <= poly[i].x))  # on a horizontal line
        if  (poly[i].x == p.x) and (poly[j].x == p.x) and ((poly[i].y <= p.y < poly[j].y) or (poly[j].y <= p.y < poly[i].y)) :
            return(True)                # on a vertical line
        if  (
                ((poly[i].y > p.y ) != (poly[j].y > p.y))
             and
                (p.x < poly[i].x + ((p.y - poly[i].y) * ((poly[j].x - poly[i].x) / float(poly[j].y - poly[i].y))))
            ) :
            inside  = not inside
        j   = i
        i  += 1

    return(inside)



def point_on_line(xy, xyxy, fudge = None) :
    """ Is the given (x, y) on infinite ( (x1,y1), (x2,y2) ) or finite line if 'fudge' is non-None? """

    x   = xy[0]
    y   = xy[1]

    x1  = xyxy[0][0]
    y1  = xyxy[0][1]
    x2  = xyxy[1][0]
    y2  = xyxy[1][1]

    if  fudge is not None :
        if  (    False
             or (not (min(x1, x2) - fudge <= x <= max(x1, x2) + fudge))
             or (not (min(y1, y2) - fudge <= y <= max(y1, y2) + fudge))
            ) :

            return(False)

        pass

    if  x2 == x1 :                                              # special case vertical line
        return(abs(x - x1) <= (fudge or 0.0))

    sl  = float(y2 - y1) / float(x2 - x1)
    zy  = y1 - (sl * x1)

    return(abs((sl * x) + zy - y) <= (fudge or 0.0))

#
#   From:       http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2
#   See also:   http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
#   And:        http://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm
#   And         http://www.pygame.org/wiki/IntersectingLineDetection
#
def convert_two_points_to_ABC(x1, y1, x2, y2) :
    """ Find the values of ABC in "A*x1 + B*y1 = C" given two points. """

    A   = float(y2 - y1)
    B   = float(x1 - x2)
    C   = float((A * x1) + (B * y1))

    return(A, B, C)


def get_line_intersection(xy1, xy2, fudge = None) :
    """
        Find the intersection point between two lines. Return (None,None) if lines are parallel.

        If 'fudge' is non-None, then do strict comparison (subject to 'fudge' slop).
           That is, the intersection must be between the points
           on the lines that go from point to point, not-infinite length.
           And, if the lines are parallel find an intersection point
                if the lines overlap, 'fudge' logic accounted for.
    """

    ( A1, B1, C1 )  = convert_two_points_to_ABC(xy1[0][0], xy1[0][1], xy1[1][0], xy1[1][1])
    ( A2, B2, C2 )  = convert_two_points_to_ABC(xy2[0][0], xy2[0][1], xy2[1][0], xy2[1][1])
    det             = (A1 * B2) - (A2 * B1)
    if  not det     :
        if  point_on_line(xy1[0], xy2, fudge) : return(float(xy1[0][0]), float(xy1[0][1]))
        if  point_on_line(xy1[1], xy2, fudge) : return(float(xy1[1][0]), float(xy1[1][1]))
        if  point_on_line(xy2[0], xy1, fudge) : return(float(xy2[0][0]), float(xy2[0][1]))
        if  point_on_line(xy2[1], xy1, fudge) : return(float(xy2[1][0]), float(xy2[1][1]))

        return(None, None)

    x               = ((B2 * C1) - (B1 * C2)) / det
    y               = ((A1 * C2) - (A2 * C1)) / det

    if  fudge is not None :
        if  (    False
             or (not (min(xy1[0][0], xy1[1][0]) - fudge <= x <= max(xy1[0][0], xy1[1][0]) + fudge))
             or (not (min(xy1[0][1], xy1[1][1]) - fudge <= y <= max(xy1[0][1], xy1[1][1]) + fudge))
             or (not (min(xy2[0][0], xy2[1][0]) - fudge <= x <= max(xy2[0][0], xy2[1][0]) + fudge))
             or (not (min(xy2[0][1], xy2[1][1]) - fudge <= y <= max(xy2[0][1], xy2[1][1]) + fudge))
            ) :

            return(None, None)

        pass

    return(x, y)



#
#   From http://www.bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
#
def _ccw(A,B,C):
    return((C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0]))

def do_line_segments_intersect(xy1, xy2):
    """ Return True if these line segments intersect. """
    return((_ccw(xy1[0], xy2[0], xy2[1]) != _ccw(xy1[1], xy2[0], xy2[1])) and (_ccw(xy1[0], xy1[1], xy2[0]) != _ccw(xy1[0], xy1[1], xy2[1])))



epsilon = math.ldexp(1.0, -53)      # from https://stackoverflow.com/questions/6063755/increment-a-python-floating-point-value-by-the-smallest-possible-amount

def find_3d_line_to_plane_intersection(line_p1, line_p2, plane = None) :
    """
        Find where a line in 3-space intersects with a plane.

        The plane can be defined by either 3 points        ( [ p1x, p1y, p1z ], [ p2x, p2y, p2z ], [ p3x, p3y, p3z ] ] ),
        or a single point and a vector from that point normal to the plane ( [ [ px, py, pz ], [ vx, vy, vz ] ] ).

        Raise a value error with a string to explain things if:

        The plane is defined by 3 points, but they aren't different.

        The line is on the plane.

        The line is otherwise parallel with the plane.

        Return an [ x, y, z ] point on the plane if all is well.

        This routine requires numpy. If numpy isn't available, it returns None.

    """
    if  not numpy :
        return(None)

    line_p1 = numpy.array(line_p1)
    line_p2 = numpy.array(line_p2)
    plane   = numpy.array(plane)

    if  len(plane) == 3 :
        u   = plane[1] - plane[0]
        v   = plane[2] - plane[0]
        n   = numpy.cross(u, v)
        if  not numpy.abs(n).sum() :
            raise ValueError("Bad plane - points aren't distinct?")
        pass
    else    :
        n   = plane[1]

    while True :
        d       = line_p2 - line_p1
        w       = line_p1 - plane[0]
        a       = -n.dot(w)
        b       =  n.dot(d)
        # print d, w, a, b
        if  abs(b) < epsilon :
            if  not a :
                raise ValueError("Line on plane")
            raise ValueError("Line parallel to plane")

        r       = a / b
        if  r  >= 0 :
            break
        line_p1, line_p2    = line_p2, line_p1  # this is what happens when you copy code (from http://geomalgorithms.com/a06-_intersect-2.html) rather than thinking things thru, folks - you cheapo out - the line goes in the wrong direction for the copied code, so we'll just reverse it and try again.

    ipt     = line_p1 + (r * d)

    return(ipt)



def apply_gravity(position = 0.0, speed = 0.0, gravity = 1.0, delta_time = 1.0) :
    """ Move an accelerated position by applying 'gravity' to its position and speed. From: http://www.niksula.hut.fi/~hkankaan/Homepages/gravity.html """
    d           = (gravity  * delta_time / 2.0)
    speed       = speed     + d
    position    = position  + (speed * delta_time)
    speed       = speed     + d

    return(position, speed)



def make_color_gradient(freq1, freq2, freq3, phase1, phase2, phase3, center = 128, width = 127, count = 256) :
    """ Return an array of colors that go around the wheel so that all neighboring colors are close to each other, as are the two end colors. """
    colors  = []
    for i in xrange(count) :
        red = int(math.sin(freq1 * i + phase1) * width + center)
        grn = int(math.sin(freq2 * i + phase2) * width + center)
        blu = int(math.sin(freq3 * i + phase3) * width + center)
        colors.append(( red, grn, blu ))
    return(colors)
make_color_gradiant = make_color_gradient


def make_color_wheel(count = 256) :
    """ Make a color wheel with the given number of colors. """
    count   = int(count)
    freq    = 2 * math.pi / count
    return(make_color_gradient(freq, freq, freq, 0.0, 2 * math.pi / 3, 4 * math.pi / 3, count = count))




def safe_relpath(fn) :
    """
        os.path.relpath(), returning the given file name if there is a problem with it (if it's "" or None).
    """

    try :
        fn  = os.path.relpath(fn)
    except ValueError :                                 # happens if the file name is "" or None
        pass
    return(fn)



def same_file(f1, f2) :
    """
        Are these the same files?
    """

    try :
        return(os.path.samefile(f1, f2))

    except ( AttributeError, OSError, IOError ) :               # attrib? samefile() does not exist under Unix. OS/IO? File doesn't exist.
        f1  = os.path.abspath(f1)
        f2  = os.path.abspath(f2)
        if  f1 == f2 :
            return(True)
        pass

    return(False)




def _read_whole_file(fname, how = "t", how_many = None) :
    """ Read a whole file. """

    fi      = open(fname, "r" + how)
    if  how_many is None :
        fs  = fi.read()
    else    :
        fs  = fi.read(how_many)
    fi.close()

    return(fs)


def read_whole_text_file(fname, how_many = None) :
    """ Read a whole text file. """

    return(_read_whole_file(fname, how = "t", how_many = how_many))


def read_whole_binary_file(fname, how_many = None) :
    """ Read a whole binary file. """

    return(_read_whole_file(fname, how = "b", how_many = how_many))


def _safe_read_whole_file(fn, rtn) :
    try :
        return(rtn(fn))
    except ( OSError, IOError ) :
        pass
    return(None)

def safe_read_whole_text_file(fn)   :
    return(_safe_read_whole_file(fn, read_whole_text_file))

def safe_read_whole_binary_file(fn) :
    return(_safe_read_whole_file(fn, read_whole_binary_file))



def _write_whole_file(fname, how, s) :
    """ Write a whole file. """

    if  isinstance(s, UnicodeType) :
        s   = unicode_byte_string(s)

    fi  = open(fname, "w" + how)
    fi.write(s)
    fi.close()

    del(fi)



def write_whole_text_file(fname, s) :
    """ Write a whole text file. """

    if  isinstance(s, UnicodeType) :
        s   = s.encode('utf-8')

    return(_write_whole_file(fname, "t", s))


def write_whole_binary_file(fname, s) :
    """ Write a whole binary file. """

    return(_write_whole_file(fname, "b", s))



def _safe_write_whole_file(fn, fd, rtn) :
    try :
        import  replace_file
    except ImportError  :
        replace_file    = None

    tfn = fn + ".tmp"
    try :
        rtn(tfn, fd)
    except ( OSError, IOError ) :
        return(False)

    bfn = fn + ".bak"
    if  replace_file  and None :
        try :
            replace_file.replace_file(fn, tfn, bfn)
        except ( OSError, IOError ) :
            return(False)
        pass
    else    :
        try :
            if  os.path.exists(bfn) :
                os.unlink(bfn)
            if  os.path.exists(fn)   :
                os.rename(fn, bfn)
            os.rename(tfn, fn)
        except ( OSError, IOError ) :
            return(False)
        pass

    return(True)

def safe_write_whole_text_file(fn, fd) :
    return(_safe_write_whole_file(fn, fd, write_whole_text_file))

def safe_write_whole_binary_file(fn, fd) :
    return(_safe_write_whole_file(fn, fd, write_whole_binary_file))



def safe_file_datetime(fn) :
    """ Get the given file's date/time or None. """
    try :
        t   = os.path.getmtime(fn)
    except ( OSError, IOError, ValueError, TypeError, ) :
        t   = None
    return(t)


def safe_file_size(fn) :
    """ Get the file's size or None. """
    try :
        return(os.path.getsize(fn))
    except ( OSError, IOError, TypeError, ) :
        pass
    return(None)



def safe_makedirs(dn) :
    """ Safely make a directory, returning the directory name if all went well, or None if it did not. """
    try :
        if  not os.path.isdir(dn) :
            os.makedirs(dn)
        return(dn)
    except ( OSError, IOError ) :
        pass
    return(None)




def whack_file(fn) :
    """ Safely make sure the given file is no longer with us. Return True if file existed and was successfully unlinked. """
    try :
        os.unlink(fn)               # note: unlink, not remove, so symlinks get whacked instead of their target
        return(True)
    except ( OSError, IOError ) :
        pass
    return(False)


def whack_files(fn) :
    """ Safely make sure files matching the given ambiguous file name are no longer with us. Return True if a file existed and was successfully unlinked. """
    retval      = False
    for fn     in glob.glob(fn) :
        retval |= whack_file(fn)
    return(retval)


def whack_dir(dn) :
    """ Safely make sure the given empty directory is no longer with us. Return True if dir existed and was successfully removed. """

    if  whack_file(dn) :
        return(True)

    try :
        os.rmdir(dn)
        return(True)
    except ( OSError, IOError ) :
        pass
    return(False)


WHACK_DIR_TMP_DIR_NAME  = "tmpd2del.tmp"                        #: temporary name to rename a directory we want to whack to


def whack_full_dir(dn, tmp_dir_base_name = None) :
    """ Safely fully make sure the given symlink or directory is no longer with us. Return True if dir existed and was successfully removed. """

    if  (not dn) or dn.endswith(os.path.normpath("/")) :
        return(False)                                           # don't wipe the poor machine when there's a bug if we can help it

    if  whack_dir(dn) :
        return(True)

    tdn = os.path.join(os.path.dirname(os.path.abspath(dn)), os.path.basename(tmp_dir_base_name or WHACK_DIR_TMP_DIR_NAME))
    if  tdn != dn :
        whack_full_dir(tdn)                                     # get rid of any lagacy staging directory we'll rename to before whacking the dir we want to whack
        try :
            os.rename(dn, tdn)
            dn  = tdn
        except ( OSError, IOError ) :
            pass
        pass

    if  not os.path.exists(dn) :
        return(False)                                           # note: I really wonder whether this thing with returning False if the dir (and file in whack_file) did not exist is the right thing to do.

    shutil.rmtree(dn, ignore_errors = True)                     # get rid of whatever is get-riddable

    if  os.path.exists(dn) :
        try :
            shutil.rmtree(dn)                                   # otherwise, try again, but this time we'll excaption out and return False
        except ( OSError, IOError ) :
            return(False)
        pass

    return(True)


def whack_dir_contents(dn) :
    """ Safely make sure the contents of a given directory is no longer with us. Return True if dir existed and was successfully removed. """
    if  whack_file(dn) :            # in case it's just a file or a symlink
        return(True)

    retval      = os.path.isdir(dn)
    for fn     in glob.glob(os.path.join(dn, '*')) :
        retval |= whack_full_dir(fn)                    # make this thing go away
    return(retval)


def whack_bak_files(dn, exts = None, regexes = None) :
    """
        Safely get rid of any *.bak or *~ files in the given directory.

        'exts', including .bak,  are case-insensitive.

        'regexes' whack matching glob.glob() file names.

        Returns the number of files whacked.

    """
    exts    = (exts or []) + [ '.bak', ]
    cnt     = 0
    for fn in glob.glob(os.path.join(dn, '*')) :
        if  fn.endswith('~') :
            cnt    += 1
            whack_file(fn)
        elif os.path.splitext(fn)[1].lower() in exts :
            cnt    += 1
            whack_file(fn)
        else :
            for r in (regexes or []) :
                if  r.search(fn) :
                    whack_file(fn)
                    cnt    += 1
                    break
                pass
            pass
        pass
    return(cnt)

def whack_bak_tmp_files(dn, exts = None, regexes = None) :
    """
        Safely get rid of any *.bak *.tmp or *~ files in the given directory.

        'exts', including .bak and .tmp,  are case-insensitive.

        'regexes' whack matching glob.glob() file names.

        Returns the number of files whacked.

    """
    exts    = (exts or []) + [ '.tmp', ]
    return(whack_bak_files(dn, exts, regexes))



def elapsed_time() :
    """ Return a running clock value based on some arbitrary "zero". """

    if  sys.platform == 'win32' :
        if  win32api :
            if  sys.maxsize > 0x7fffFFFF :
                return(win32api.GetTickCount64() / 1000.0)
            return(    win32api.GetTickCount()   / 1000.0)    # under Win7, this seems to work, but time.clock() does not. The latter is way-slow.
        return(time.clock())

    #       Mac could use MacOS.GetTicks() / 60.0, I suppose.

    return(os.times()[4])           # under windows this value stays at zero


def wdhms_str(t, weeks = False) :
    """ Return a string that represents the shortest weeks:days:hours:minutes:seconds.fraction for the given time. """

    def _nxt(s, t, d, fm = '%02u:') :
        tt  = t / d
        if  tt or s :
            s  += (fm % tt)
        return(s, t - (int(tt) * d))

    t               = float(t)
    s               = ""
    if  weeks       :
        ( s, t )    = _nxt(s, t, 60 * 60 * 24 * 7, fm = "%u:"   )
    ( s, t )        = _nxt(s, t, 60 * 60 * 24    , fm = "%1u:"  )
    ( s, t )        = _nxt(s, t, 60 * 60                        )
    ( s, t )        = _nxt(s, t, 60                             )
    if  t          == int(t)  :
        ( s, t )    = _nxt(s, t, 1               , fm = "%02u"  )
    else            :
        ( s, t )    = _nxt(s, t, 1               , fm = "%09.6f")

    s               = re.sub(r"^[0\:]+", "", s)
    s               = re.sub(r"[0\.]+$", "", s)
    if  s.endswith(':') :
        s          += "00"
    if  re.search(r":\d$", s) :
        s          += "0"

    return(s)




def same_time_ish(now, then, yday = None, same_day_hours = 14, diff_day_hours = 8) :
    """ Is Unix time 'then' likely to be the same "day" - session as 'now' in localtime? """

    yday    = yday or time.localtime(now).tm_yday
    d       = ((time.localtime(then).tm_yday == yday) and same_day_hours) or diff_day_hours             # same local day of year, allow the times to be 14 hours away from each other. Different days: must be within 8 hours of each other.
    d      *= 60 * 60                                                                                   # convert to seconds

    return(abs(now - then) <= d)



def inside_daytime_window(wins, now = None) :
    """
        Given a list of [ from, to ] seconds, is the current time-of-day inside any of them?

        Allow from-before-to values to invert the logic for periods like 11pm to 1am.

    """
    if  len(wins or []) :
        if  now is not None :
            t   = now               # don't use gmtime because that would limit it to legal time-of-days
        else        :
            tm  = time.localtime(time.time())
            t   = (tm.tm_hour * 3600) + (tm.tm_min * 60) + tm.tm_sec
        for ft, tt in wins :
            if  ft <= tt :
                if  ft <= t < tt :
                    return(True)
                pass
            else    :
                if  not (tt <= t < ft) :
                    return(True)
                pass
            pass
        pass

    return(False)




def get_ini_or_cfg_file_name(base_name, app_name = None, make_dir = True) :
    """ Return a file name of config_directory/base_name.ini or .cfg, appropriate to the OS. And make the config directory for the app. """

    base_name       = base_name or "tzlibpy"
    app_name        = "." + os.path.splitext(os.path.basename(app_name or base_name))[0]

    if  sys.platform   == 'win32' :
        app_path        = os.path.join("C:\\", app_name)
        if  os.path.expandvars("${homedrive}") and os.path.expandvars("${homepath}") :
            app_path    = os.path.normpath(os.path.join(os.path.expandvars("${homedrive}${homepath}"), app_name))   # on modern machines this is C: and \Users\USER_NAME - on XP it's C: and \Documents and Settings\USER_NAME
        if  os.path.expandvars("${userprofile}") :
            app_path    = os.path.normpath(os.path.join(os.path.expandvars("${userprofile}"),          app_name))   # same thing by a different, single name
        if  not os.path.isdir(app_path) :
            os.makedirs(app_path)
        return(os.path.join(app_path, base_name + ".ini"))

    app_path    = os.path.join(os.path.expanduser("~"), app_name)
    if  make_dir and (not os.path.isdir(app_path)) :
        os.makedirs(app_path)

    return(os.path.join(app_path, base_name + ".ini"))


def get_ini_dir(app_name, make_dir = True) :
    """ Return the .ini/config directory name for this app. """
    fn  = get_ini_or_cfg_file_name(app_name, app_name = app_name, make_dir = make_dir)
    return(os.path.dirname(fn))


def run_config_module(program_name, under_under_name_under_under) :
    """ Import ~/.PROGRAM_LOWER_NAME/PROGRAM_LOWER_NAME_config.py so arbitrary Python code can be run from a frozen .EXE file or whatever. """
    if  under_under_name_under_under   == 'tzlib' :
        under_under_name_under_under    = ''
    if  program_name   == '-c' :
        program_name    = ''
    src_fn          = os.path.splitext(os.path.basename(program_name or under_under_name_under_under))[0].lower()
    if  src_fn      :
        sys.path.insert(0, get_ini_dir(src_fn, make_dir = False))
        try         :
            cmnm    = src_fn + "_config"
            __import__(cmnm, globals(), locals(), [])
            getattr(sys.modules[cmnm], 'config', lambda a : False)(sys.modules[under_under_name_under_under])
        except ImportError :
            pass
        pass
    pass


def cmd_line_files(program_name, under_under_name_under_under) :
    """ Return an array of file names, each preceded by an at sign (@). These files should contain command line options. """
    ra          = []
    if  under_under_name_under_under   == '__main__' :
        under_under_name_under_under    = ''
    if  under_under_name_under_under   == 'tzlib' :
        under_under_name_under_under    = ''
    if  program_name   == '-c' :
        program_name    = ''
    src_fn      = os.path.splitext(os.path.basename(program_name or under_under_name_under_under))[0].lower()
    if  src_fn  :
        for dn in [ get_ini_dir(src_fn, make_dir = False), os.path.dirname(program_name), '.', ] :
            for ext in [ ".cmd", ".cfg", ] :
                fn  = os.path.join(dn, src_fn + ext)
                if  os.path.isfile(fn) :            # is an @ file in the current directory?
                    ra.append("@" + os.path.abspath(fn))
                pass
            pass
        pass
    return(remove_trailing_dupes(ra))



def temp_file_name(base_name = None, app_name = None, ext = None) :
    ext = ext or ".tmp"

    if  (not app_name) or (not base_name) :
        td  =       os.environ.get("TEMP", None)
        td  = td or os.environ.get("TMP", None)
        td  = td or "."
        fn  = os.path.join(td, "tmp_tzlibpy" + ext)
    else :
        fn  = get_ini_or_cfg_file_name(base_name, app_name)

    while True :
        rs  = "_%08x" % ( random.randint(0, 2000000000) )
        rfn = os.path.splitext(fn)[0] + rs + ext
        if  not os.path.isfile(rfn) :
            break
        pass

    return(rfn)




def s_except_1(v) :
    """ Return an "s" if the 'v' is not 1. Find this rtn under plural _if_ single _one_ _s() 1_if not_one """

    if  isinstance(v, (TupleType, ListType, DictionaryType ) ) :
        v   = len(v)

    if  v != 1 :
        return("s")
    return("")
s_if_1  = s_except_1            # make it easier to find this routine whose name I always forget



def f(string) :
    """
        Poor man's f-string for python versions older than 3.6.

        Best to put "from tzlib import f" at the top of your program.

    """
    import inspect

    frame = inspect.currentframe().f_back

    v = dict(**frame.f_globals)
    v.update(**frame.f_locals)

    return string.format(string, **v)


def same_object(o1, o2) :
    """ Return whether these two things are the same, exact thing (in memory). """

    return(id(o1) == id(o2))



def sys_err_file_line(sys_exc_info_2 = None) :
    """ Return a simple, one-line string with the bottom line about where the latest except failure has been. """

    sys_exc_info_2  = sys_exc_info_2 or sys.exc_info()[2]

    lns = traceback.format_tb(sys_exc_info_2)[-1].strip()
    lns = re.sub(r"[\r\n]+", " ---- ", lns)

    return(lns)



def bool_to_0_or_1(b) :
    """ Return 0 or 1 depending upon the given bool's value. """

    return((b or 0) and 1)



def excel_column_name(c) :
    if  c < 0 :
        raise ValueError("%d<0"   % c)
    if  c >= 26 * 27 :
        raise ValueError("%d>max" % c)

    cc  = chr(ord('a') + c % 26)
    if  c >= 26 :
        cc  = chr(ord('a') + (c / 26) - 1) + cc

    return(cc)



def split_path(fn) :
    """ Split the given file name in to disk, directory, ..., file_name array. """
    if  not fn :
        return([])

    drv, fn     = os.path.splitdrive(fn)
    retval      = []
    while True  :
        odn     = fn
        dn, fn  = os.path.split(fn)
        if  (not dn) or (dn == odn) :
            retval.append(dn or fn)
            break
        retval.append(fn)
        fn      = dn
    retval.append(drv)
    retval.reverse()
    retval      = [ n for n in retval if n ]
    return(retval)


def common_path(fns) :
    """ Return what part of the path is the same for the given file names. """
    if  not fns :
        return("")

    fna = [ split_path(fn) for fn in fns ]
    bi  = len(fna[0])
    for pa in fna :
        for i in xrange(bi) :
            if  pa[i] != fna[0][i] :
                bi  = i
                break
            pass
        pass
    cp  = os.path.join(*fna[0][:bi])
    cpl = len(cp)
    if  (cpl < len(fns[0])) and (fns[0][cpl] in [ '/', '\\', ':', ]) :
        cp += fns[0][cpl]

    return(cp)


def find_upper_dir(to_find_dir, our_dir = None) :
    our_dir = our_dir or "."
    our_dir = os.path.abspath(our_dir)

    sd      = our_dir
    while sd and (not sd.endswith(os.path.normpath("/"))) :
        ( p, dn )   = os.path.split(sd)
        if  dn     == to_find_dir :
            return(sd)

        if  not dn  :
            sd      = ""
            break
        sd          = p

    sd      = our_dir
    while sd and (not sd.endswith(os.path.normpath("/"))) :
        p           = os.path.join(sd, to_find_dir)
        if  os.path.isdir(p) :
            return(p)

        sd          = os.path.split(sd)[0]

    return("")


def find_upper_file_or_dir(to_find, our_dir = None) :
    our_dir         = our_dir or "."
    our_dir         = os.path.abspath(our_dir)
    to_find         = os.path.normpath(to_find)

    sd              = our_dir
    while sd        :
        p           = os.path.join(sd, to_find)
        if  os.path.exists(p) :
            return(p)

        if  sd.endswith(os.path.normpath("/")) :
            break

        sd          = os.path.split(sd)[0]

    return("")




def file_signature(file_name)    :
    """ Return the CRC of the file's name, size and date/time. """

    return(blkcrc32(INITIAL_CRC32_VALUE, file_name + str(safe_file_size(file_name)) + str(safe_file_datetime(file_name))))




def unpickle_file(fn) :
    fd  = safe_read_whole_binary_file(fn)
    if  fd is not None :
        try :

            return(cPickle.loads(fd))

        except ( cPickle.PickleError, TypeError, AttributeError, EOFError, ImportError, IndexError, ) :         # among other errors
            pass
        except :
            pass
        pass

    return(None)


def pickle_file(fn, o, proto = 0)  :            # note: proto = -1 means HIGHEST_PROTOCOL
    try :
        fd  = cPickle.dumps(o, protocol = min(cPickle.HIGHEST_PROTOCOL, max(-1, proto or 0)))
    except ( cPickle.PickleError, TypeError, AttributeError, ) :
        return(False)

    return(safe_write_whole_binary_file(fn, fd))





#
#
#   Or to make outside packages, like ReportLab, stop fussing (unless multiprocessing is the order of the day):
#
#       import warnings
#
#       with warnings.catch_warnings() :
#           warnings.filterwarnings("ignore", category = DeprecationWarning)
#           from    PIL import Image
#
#   Note: PIL->numpy->PIL conversions. Somewhat tested code cribbed from various places.
#       PIL version 1.1.6 and beyond:
#           numpy_image = numpy.asarray(pil_image)      # numpy_image is read only
#           numpy_image = numpy.array(pil_image)        # numpy_image is ok to change
#           pil_image   = Image.fromarray(numpy_image)  # back to PIL form
#       Old PIL:
#           numpy_image = numpy.fromstring(tzlib.pil_tostring(img), dtype = 'uint8')
#           numpy_image = numpy_image.reshape([ pil_image.size[1], pil_image.size[0], 3 ])      # if RGB
#           numpy_image = numpy_image.reshape([ pil_image.size[1], pil_image.size[0], 4 ])      # if RGBA
#           numpy_image = numpy_image.reshape([ pil_image.size[1], pil_image.size[0]    ])      # if L  grayscale
#           pil_image   = tzlib.pil_fromstring(Image)('L',    ( numpy_image.shape[1], numpy_image.shape[0] ), numpy_image)  # if L
#           pil_image   = tzlib.pil_fromstring(Image)('RGBA', ( numpy_image.shape[1], numpy_image.shape[0] ), numpy_image)  # if RGBA
#           pil_image   = tzlib.pil_fromstring(Image)('RDB',  ( numpy_image.shape[1], numpy_image.shape[0] ), numpy_image)  # if RGB
#
#
def pil_tostring(img) :
    """ Sigh. """
    return(getattr(img, 'tobytes',   getattr(img, 'tostring'))())
def pil_fromstring(img) :
    """ Sigh. """
    return(getattr(img, 'frombytes', getattr(img, 'fromstring')))


class   a_pickleable_pil_image(object) :
    def __init__(me, img) :
        me.mode = img.mode
        me.size = img.size
        me.s    = pil_tostring(img)
    #   a_pickleable_pil_image



#   White balance for every channel independently.
#   From buggy code at https://stackoverflow.com/questions/1175393/white-balance-algorithm
#   This, with prec==0.6) seems to be close to what Gimp does in Color|Levels|Auto
#   Speculation: Does Gimp only do the histogram inside [ 1, 254 ] and leave the 0 and 255's alone?
#   Also, from https://stackoverflow.com/questions/9744255/instagram-lux-effect/9761841#9761841
#       Done on the intensity channel (of LAB/HSV/YUV etc.) only:
#       a   = 255 / (ma - mi)
#       b   = -(a * mn)
#       channel = numpy.uint8(numpy.clip(((channel - mi) * a) + b, 0, 255))
#
def white_balance(channel, perc = 5):
    """
        White balance a channel of a (2D in numpy isn't here) image.

    """
    if  numpy and is_numpy_array(channel) :
        mi, ma  = numpy.percentile(channel, [ float(perc), 100.0 - perc ])
        # print mi, ma
        # print median(channel, perc / 100.0), median(channel, (100.0 - perc) / 100.0)
        if  (mi < ma) and (mi or (ma < 255)) :
            a       = 255.0 / (ma - mi)
            channel = numpy.uint8(numpy.clip((channel - mi) * a, 0, 255))
        pass
    else        :
        mi  = median(channel,          perc  / 100.0)
        ma  = median(channel, (100.0 - perc) / 100.0)
        if  (mi < ma) and (mi or (ma < 255)) :
            a       = 255.0 / (ma - mi)
            channel = [ [ int(max(0, min(255, (channel[y][x] - mi) * a))) for x in xrange(len(channel[0])) ] for y in xrange(len(channel)) ]
        pass
    return(channel)


def do_like_gimps_auto_adjust_color_levels(image) :
    perc    = .6                                       # 0.6 is a good simulation of Gimp's Adjust Color Levels - but it's a hair too low, though gives arguably better results - fine point
    if  numpy and is_numpy_array(image) :
        return(numpy.dstack([ white_balance(channel, perc) for channel in [ image[:,:,ci] for ci in ( 0, 1, 2 ) ] ] ))

    r   = [ [ image[y][x][0] for x in xrange(len(image[0])) ] for y in xrange(len(image)) ]
    g   = [ [ image[y][x][1] for x in xrange(len(image[0])) ] for y in xrange(len(image)) ]
    b   = [ [ image[y][x][2] for x in xrange(len(image[0])) ] for y in xrange(len(image)) ]
    r   = white_balance(r, perc = perc)
    g   = white_balance(g, perc = perc)
    b   = white_balance(b, perc = perc)
    img = [
            [ [ r[y][x], g[y][x], b[y][x] ] for x in xrange(len(image[0])) ]
                                            for y in xrange(len(image   ))
          ]
    return(img)




def best_w_h_fit_scale(to_w, to_h, from_w, from_h) :
    """ Return a scale factor that will make a "from_" image fit tightly in a "to_" image preserving the aspect ratio. """
    return(min(to_w / float(from_w), to_h / float(from_h)))


def max_w_h_scale(from_w, from_h, max_w_and_h = None, max_w = None, max_h = None) :
    """
        Return a scale factor that preserves an image's aspect
        ratio while making both the width and hite of an image no
        more than max_w_or_h, and the width no more than max_w,
        and hite no more than max_h if each of these parameters
        are not None.

    """
    sf  = 1.0
    if  max_w is not None :
        sf  = min(sf, float(min(from_w, max_w)) / from_w)
    if  max_h is not None :
        sf  = min(sf, float(min(from_h, max_h)) / from_h)
    if  max_w_and_h is not None :
        sf  = min(sf, float(min(from_w, max_w_and_h)) / from_w, float(min(from_h, max_w_and_h)) / from_h)
    return(sf)


def best_line_fit(x, w, mn, mx) :
    """ Return an integer X value that is greater than or equal to mn, w less than mx if possible, and is as close as possible to x - int(w)/2. So that a w long line from x fits inside mn and mx and includes x, if possible. """
    x   = int(x)
    w   = int(w)
    mn  = int(mn)
    mx  = int(mx)
    v   = max(mn, x - (w / 2))
    v   = min(v, min(mx, v + w) - w)
    v   = max(mn, v)                # if the line doesn't fit (or not mn <= x < mx), return mn.
    return(v)




def base_36_encode(number, alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') :
    '''
        Convert positive integer to a base36 string.
        Modified: http://en.wikipedia.org/wiki/Base_36
    '''


    if  not isinstance(number, (int, long)) :
        number  = int(number)                       # let the chips fall where they may in the case of floats and others

    if  number == 0 :
        return('0')

    base36      = ''

    sign        = ''
    if  number  < 0:
        sign    = '-'
        number  = -number

    while number   != 0 :
        number, i   = divmod(number, len(alphabet))
        base36      = alphabet[i] + base36

    return(sign + base36)

def base_36_decode(number) :
    return(int(number,36))




def q_get(q, timeout = None) :
    """
        Get from a Queue.Queue(), but with a single parameter.
            timeout is None     wait forever
            timeout ==  0       non-blocking
            otherwise           wait for timeout time.
        Return None if timeout or nothing in queue.
    """

    try         :
        if  timeout is None :
            r   = q.get()
        elif not timeout :
            r   = q.get(False)
        else    :
            r   = q.get(True, timeout)
        pass
    except ( Queue.Empty, IOError, OSError, EOFError, ) :
        r       = None
    return(r)


def make_q_empty(q) :
    """ Suck a queue dry, returning None and allowing for bad things to happen as a program shuts down. """
    while q :
        try     :
            q.get_nowait()
        except ( Queue.Empty, IOError, OSError, EOFError, ) :
            q   = None
        pass
    return(q)




def _open_w_a(fn, bt_mode = "") :
    """ Open a file for writing, or, if that fails, try appending. Return the file object or None. """
    bt_mode = bt_mode or ""         # 'b' or 't' or ''
    f       = None
    try     :
        f   = open(fn, "w" + bt_mode)
    except ( OSError, IOError ) :
        try :
            f   = open(fn, "a" + bt_mode)
        except ( OSError, IOError ) :
            f   = None
        pass
    return(f)


def reroute_stdout_err(argv) :
    """
        According to command line parameters, reroute stdout and stderr (for use at the top of .exe files made with py2exe).
    """

    try :
        import  replace_file
    except ImportError  :
        replace_file    = None

    stdout_name = None
    stderr_name = None
    ai          = 0
    while ai    < len(argv) :
        arg = argv[ai]
        if  arg  in [ "--std_out", "--std-out", "--stdout", ] :
            del(argv[ai])
            stdout_name = argv.pop(ai)
        elif arg in [ "--std_err", "--std-err", "--stderr", ] :
            del(argv[ai])
            stderr_name = argv.pop(ai)
        elif arg in [ "--std_out_err", "--std-out-err", "--stdouterr", "--std_err_out", "--std_err_out", "--stderrout", ] :
            del(argv[ai])
            stderr_name = argv.pop(ai)
            stdout_name = stderr_name
        else :
            ai             += 1
        pass

    f_out                   = None
    f_err                   = None
    if  stdout_name and stderr_name :
        if  replace_file    :
            replace_file.safe_replace_file(stdout_name, None, stdout_name + ".bak")
        f_out               = _open_w_a(stdout_name)
        if  same_file(stdout_name, stderr_name) :
            f_err           = f_out
        else                :
            if  replace_file :
                replace_file.safe_replace_file(stderr_name, None, stderr_name + ".bak")
            f_err           = _open_w_a(stderr_name)
        pass
    elif stdout_name        :
        if  replace_file    :
            replace_file.safe_replace_file(stdout_name, None, stdout_name + ".bak")
        f_out               = _open_w_a(stdout_name)
    elif stderr_name        :
        if  replace_file    :
            replace_file.safe_replace_file(stderr_name, None, stderr_name + ".bak")
        f_err               = _open_w_a(stderr_name)

    if  f_out               :  sys.stdout  = f_out
    if  f_err               :  sys.stderr  = f_err

    return(f_out, f_err)


def close_rerouted_stdout_err(f_out, f_err) :
    """
        Close the files (or None's) returned by reroute_stdout_err().
    """

    if  f_out                                       : f_out.close()
    if  f_err and (not same_object(f_out, f_err))   : f_err.close()




class   a_kalman_filter(object) :

    def __init__(me, process_variance = 0.01, measurement_variance = 0.04, initial_value = 0.0) :
        me.pv       = process_variance      or  0.01
        me.mv       = measurement_variance  or  0.04
        me.guess    = initial_value         or  0.0         # the current best guess of the output value
        me.offset   = 1.0                                   # our tracker, so to speak. How much we juice the process_variance the next measurement.

    def next(me, measurement) :
        pm          = me.offset   + me.pv                   # make the current process variance, sort of
        mult        = pm    / (pm + me.mv)                  # make the current tracking multiplier
        me.guess   += (mult * (measurement - me.guess))     # track the measurement
        me.offset   = (1    -  mult) * pm                   # update our tracker (notice that if we think the measurement is perfect, we go back to the initial state)

        return(me.guess)

    #   a_kalman_filter



class   a_peak_finder(object) :             # note: used by oxi logic
    """ Given samples and a minimum amplitude low to high, output peaks and their sample index or time. """
    fudge       = 0.4                       # if auto amplitude is being used, push out peaks that are greater than this amount of the running average of the peaks found so far

    def __init__(me, amplitude = None) :
        """ Initialize the object. """
        me.fud  = me.fudge                  # how much slack in automatic amplitude adjuster
        me.amp  = float(amplitude or 0.0)   # how far the sample value must be greater than from the latest, prospective peak. Zero or None == automatic.
        me.aamp = me.amp * (1.0 / me.fud)   # (could be) adaptive amplitude we're really using, bumped up by the fudge factor so the amplitude change can be compared against this value multiplied by me.fud
        me.hv   = -sys.maxsize              # the latest high peak value
        me.hw   = 0                         # sample count or time when the latest high peak value was found
        me.lv   =  sys.maxsize              # the latest low  peak value
        me.lw   = 0                         # sample count or time when the latest low  peak value was found
        me.phv  = 0                         # previous high peak value
        me.plv  = 0                         # previous low  peak value
        me.cnt  = 0                         # how many samples we've seen
        me.llow = None                      # None==look for either a high or low peak. True==look for low peak. False==look for high peak

    def append(me, sample, when = None) :
        """
            Input another sample and optionally, when the sample was acquired.

            If 'when' is None, this sample's 'when' will be the sample count.

            Return an array, ( peak_value, peak_when_or_sample_count, opposite_peak_value_before_this_peak ) if a peak was found in some previous sample.

            Return ( None, None, None ) if no peak has been found before this sample.
        """
        if  when   is None  :
            when    = me.cnt
        if  not me.cnt :
            me.phv  = sample                # initialize the latest peak values
            me.plv  = sample
        retval      = ( None, None, None, )
        if  sample  > me.hv :
            me.hv   = sample
            me.hw   = when
        if  sample  < me.lv :
            me.lv   = sample
            me.lw   = when
        if  (me.llow != False) and (sample - me.lv > me.aamp * me.fud) :
            if  not me.amp  :
                me.aamp     = ((me.aamp * 7.0) + (me.phv - me.lv )) / 8.0   # auto-adjust           !!!! auto-adjust should be able to handle when the peak-to-peak values go down and stay down. fud should have a linear decrease so that we don't go too long without a peak
            retval  = ( me.lv, me.lw, me.phv, )
            me.plv  = me.lv
            me.hv   = sample
            me.hw   = when
            me.llow = False
        elif (me.llow != True) and (me.hv - sample > me.aamp * me.fud) :
            if  not me.amp  :
                me.aamp     = ((me.aamp * 7.0) + (me.hv  - me.plv)) / 8.0   # auto-adjust           note: IIR filter - can be fewer ops in floating point like this: "fs += ((1.0/IIR_LEN) * (s - fs))" with 1/8 pre-calculated it's only a sub/mult/add per sample. integer 2^ IIR is shift/sub/add/shift
            retval  = ( me.hv, me.hw, me.plv, )
            me.phv  = me.hv
            me.lv   = sample
            me.lw   = when
            me.llow = True

        me.cnt     += 1                                                     # count samples we've seen

        return(retval)

    #   a_peak_finder


class   a_peak_finder_regex(object) :       # note: used by oxi logic
    """
        The idea here is to find patterns in an array of peaks from a_peak_finder().

        Given a relatively easy-to-type kinda-regex, that we convert to a real regex,
        we find a pattern of an array of peaks from a_peak_finder().

    """
    TOO_FAR_BACK    = 36

    def __init__(me, kinda_regex) :
        ka          = [ re.sub(r'^\.$',   '.........',               s) for s in kinda_regex.split() ]
        ka          = [ re.sub(r'^\.\.$', '.........' + '.........', s) for s in ka ]
        rs          = "".join(ka)
        rs          = re.sub(r'\s+', '', rs)
        rs          = r'(%s)' % rs
        me.regx     = re.compile(rs, re.DOTALL)

    def convert_peaks_to_string(me, peaks, below_mult = None, shorter_mult = None) :
        """ Given an array of peaks as found by a_peak_finder() peaks, return a string we can do our regex on. """
        if  len(peaks) < 2 :
            return("")

        ps          = []
        for i, pk in enumerate(peaks) :
            psl         = len(ps)
            pv, pi, ppv = pk
            ps.append('P' if pv >= ppv else 'N')

            bj  = i - 2
            e   = max(-1, i - me.TOO_FAR_BACK * 2)
            tv  = pv * (below_mult or 1.0)
            while bj > e :
                if  tv <= peaks[bj][0] :
                    break                                           # the bj'th peak is the first peak, going back from the current one, that is absolutely higher than the current sample
                bj -= 2
            if  bj  < 0 :
                ps.append('..')
            else    :
                ps.append('B' + '0123456789abcdefghijklmnopqrstuvwxyz'[ min(35, (i - bj) // 2) ])

            ln  = pi - peaks[i - 2][1]                              # how long in sample counts since the previous peak
            ln  = ln * (shorter_mult or 1.0)
            sj  = i - 2
            e   = max(2, i - me.TOO_FAR_BACK * 2)
            while sj > e :
                if  ln <= peaks[sj][1] - peaks[sj - 2][1] :
                    break                                           # sj'th sample is longer from its previous peak than the current sample is from its previous peak
                sj -= 2
            if  sj  < 2 :
                ps.append('..')
            else    :
                ps.append('S' + '0123456789abcdefghijklmnopqrstuvwxyz'[ min(35, (i - sj) // 2) ])

            if  False and (i < 50) :
                print "@@@@ i bj sj pv pi ppv ps", i, bj, sj, pv, pi, ppv, "".join(ps[psl:])

            pass

        ps  = "".join(ps)

        return(ps)


    def find(me, peak_string) :
        """ Given a string returned from convert_peaks_to_string(), use our regex to find matching substrings. Return an array of [ peaks_index, found_string, ] for re.finditer() objects. """
        fa  = me.regx.finditer(peak_string)
        fa  = [ [ g.start(1) // 5, g.group(1), ] for g in fa ]
        return(fa)


    #   a_peak_finder_regex



class   a_fifo_histogram(object) :

    """
        Keep track of a FIFO of N items in a histogram.
        The items can have information associated with them.
        The information, by default, is the count of items already appended to the histogram.
        The histogram, itself, is a dictionary keyed by the lable/number/whatever of each histogram bin.

    """

    def __init__(me, max_size = None) :
        me._size        = max_size or sys.maxsize
        if  me._size    < 0 :
            me._size    = sys.maxsize
        me.cnt          = 0             # how many values we've appended all together
        me.nums         = []            # fifo of item values (nums) appended.
        me.per_num      = {}            # histogram - keyed by item num, valued by arrays of informations

    def delete_oldest_item(me) :
        n   = me.nums.pop(0)
        del(me.per_num[n][0])
        if  not len(me.per_num[n]) :
            del(me.per_num[n])
        pass

    def append(me, histogram_bin_key, info = None) :
        """ Add an item to us. The info, if None, will be the count of numbers already appended. """
        num         = histogram_bin_key
        if  num not in me.per_num :
            me.per_num[num] = []
        if  info   is None  :
            info    = me.cnt
        me.cnt     += 1
        me.per_num[num].append(info)
        me.nums.append(num)
        while len(me.nums) > me._size :
            me.delete_oldest_item()
        pass

    def total_item_count(me) :
        """ Return how many items we've stored in the histogram all together. """
        return(me.cnt)

    def count(me) :
        """ Return how many items are in the FIFO histogram. """
        return(len(me.nums))
    size    = count

    def is_full(me) :
        """ Return whether the histogram is full. """
        return(me.count() >= me._size)

    def range(me) :
        """ Return the minumum and maximum numbers currently in the FIFO histogram or [0,0]. """
        if  not len(me.per_num) :
            return(0, 0)
        kys = me.per_num.keys()
        return(min(kys), max(kys))

    def picture(me) :
        """ Return a copy of a histogram dictionary keyed by item values (nums), valued by arrays of info's (or number-of-items-we've appended when this item was appended), the arrays in append order. """
        return(copy.deepcopy(me.per_num))

    def nums(me) :
        """ Return a copy of an array of the current values known in append order. """
        return(copy.deepcopy(me.nums))

    #   a_fifo_histogram



def weighted_choice(wates) :
    """ Given a list of (positive) weights, yield a randomly chosen index (like random.choice()), likely to be of a higher weight. """

    wates       = [ max(0, w) for w in wates ]
    if  not max(wates) :
        wates   = [ 1 ] * len(wates)

    mxw         = max(wates) * 2.0
    ii          = int(random.random() * len(wates))
    w           = 0.0
    while True  :
        w      += (random.random() * mxw)
        while w > wates[ii]:
            w  -= wates[ii]
            ii  = (ii + 1) % len(wates)
        yield(ii)
    pass




KMEANS_PASS_COUNT       = 20
KMEANS_SMALL_MOVEMENT   = 0.0

def kmeans_cluster(a, k, distance_rtn = None, make_center_rtn = None, pass_count = KMEANS_PASS_COUNT, small_movement = KMEANS_SMALL_MOVEMENT) :
    """
        Return an 'k' length array of cluster numbers corresponding with the items in 'a'.

        Callback routine examples:

            class   a_ksample(object) :
                def __init__(me, d) :
                    me.d    = d
                #   a_ksample

            def _cmp_dist(p1, p2) :
                if  (p1 is None) and (p2 is None) :
                    return(0)
                if  p1 is None  :
                    return( sys.maxsize)
                if  p2 is None  :
                    return(-sys.maxsize)
                return(abs(p1.d - p2.d))

            def _mkctr(a) :
                if  not len(a) :
                    return(None)
                return(a_ksample(float(sum([ s.d for s in a ]) / len(a))))

    """

    def mkctr_rtn(a) :
        """ Given an array of things, return a thing that's in the center of the things in the array. """
        if  not len(a) :
            return(None)
        return(float(sum(a)) / len(a))

    def d_rtn(p1, p2) :
        """
            Given two things (or None for either or both) return a distance (that can be compared with the '>' operator).
            If both things are None, return 0.
            Or, if either thing is None, return a huge distance.
            Otherwise, return the distance between the things.
        """
        if  (p1 is None) and (p2 is None) :
            return(0)
        if  p1 is None  :
            return( sys.maxsize)
        if  p2 is None  :
            return(-sys.maxsize)
        return(abs(p1 - p2))


    class   a_cluster(object) :
        """ Keep an array of things that are in this cluster. Compute/create a center thing that can be distance_rtn-compared with array things. """
        def __init__(me,  a  = [], make_center_rtn = None) :
            me.a        = a or []
            me.mkctr    = make_center_rtn   or mkctr_rtn
            me.center()
        def append(me, p) :
            me.a.append(p)
        def clear(me)   :
            me.a        = []
        def center(me)  :
            me.ctr      = me.mkctr(me.a)
            return(me.ctr)
        #   a_cluster


    k       = int(k)
    if  (len(a) < k) or (k < 1):
        return([ 0 ] * len(a))              # make everybody cluster zero

    dist        = distance_rtn                          or d_rtn
    pass_count  = ((pass_count    > 0) and pass_count)  or KMEANS_PASS_COUNT
    smlmv       = small_movement
    if  (smlmv is None) or (smlmv < 0) :
        smlmv   = KMEANS_SMALL_MOVEMENT

    ctrs    = [ a_cluster([ a[i] ], make_center_rtn) for i in xrange(k) ]
    a2ctr   = [ -1 for p in a ]             # we think every thing is in cluster -1 - so all things will move clusters the first time around

    for ps in xrange(pass_count) :
        for c in ctrs :
            c.clear()                       # forget the things in the cluster
        chng    = False
        for pi, p in enumerate(a) :
            bd  = dist(p, ctrs[0].ctr)
            bi  = 0
            for i in xrange(1, len(ctrs)) :
                d   = dist(p, ctrs[i].ctr)
                if  bd  > d :               # find the closest cluster
                    bd  = d
                    bi  = i
                pass
            if  bi     != a2ctr[pi] :
                a2ctr[pi]   = bi            # remember what cluster the thing is in
                chng        = True          # not the same cluster as last time
            ctrs[bi].append(p)              # add this thing to the closest cluster

        if  not chng        :
            break                           # kick out if no thing changed clusters

        bd          = smlmv - 1
        for c in ctrs :
            if  not len(c.a) :
                c.append(random.choice(a))  # fake a bright, new day        !!!! no, split the group with the largest sum([ dist(i, ctr) ** 2 for i in group_items ])
            octr    = c.ctr                 # remember the previously made center thing
            nctr    = c.center()            # compute a new center for the cluster
            d       = dist(octr, nctr)
            if  bd  < d :
                bd  = d
            pass
        if  bd <= smlmv :
            break                           # kick out if the cluster centers didn't move
        pass

    return(a2ctr)


def sort_kmeans_clusters(ka, a, k = 0, cmp_rtn = None) :
    """
        Change the cluster numbers in ka (which was returned by kmeans_cluster()) so that the lower cluster numbers probably have values in 'a' that sort earlier.

        Note:   Not passing a 'k' value is more stable (no unassigned cluster numbers), but probably slower.

        Note:   This routine should really compare the centers of the clusters rather than an arbitrary instance from each cluster.
                And when not given 'k', we could simply look for the max value in 'ka' to be 'k' and do the rest of the logic from there.

    """

    cmp_rtn = cmp_rtn or cmp

    if  k  <= 0     :                                           # find the cluster numbers from the ka array?
        kns         = [ [ kn, a[v], ] for kn, v in make_index_dictionary(ka).items() ]
    else            :
        kns         = [ [ kn, None, ] for kn in range(k) ]
        for kv in kns :
            i       = array_find(ka, kv[0])
            kv[1]   = None  if i < 0    else    a[i]
        pass


    def _cr(a, b) :
        if  (a[1] is None) and (b[1] is None) :
            return(0)
        if  a[1] is None :
            return( sys.maxsize)
        if  b[1] is None :
            return(-sys.maxsize)
        return(cmp_rtn(a[1], b[1]))
    kns.sort(_cr)

    kns = make_index_dictionary([ kv[0] for kv in kns ])

    for i, kn in enumerate(ka) :
        ka[i]   = kns[kn]

    pass




def restricted_fac(x):
    if  (type(x) != int) or (x < 0) :
        raise ValueError

    if  x == 0:
        return(1)

    for n in xrange(2, x) :
        x  *= x

    return(x)



def restricted_sinc(x) :
    if  x == 0  :
        return(1)

    return(math.sin(x) / x)



restricted_list     = [ 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', ]

restricted_dict     = {}
for k in restricted_list    :
    if  hasattr(math, k) :
        restricted_dict[k]  = getattr(math, k)
    pass

restricted_dict['enumerate']    = enumerate

restricted_dict['abs']      = abs
restricted_dict['min']      = min
restricted_dict['max']      = max
restricted_dict['sum']      = sum
restricted_dict['int']      = int
restricted_dict['float']    = float
restricted_dict['round']    = round
restricted_dict['len']      = len

restricted_dict['fac']      = restricted_fac
restricted_dict['sinc']     = restricted_sinc

restricted_dict['False']    = False
restricted_dict['True']     = True
restricted_dict['FALSE']    = False
restricted_dict['TRUE']     = True
restricted_dict['false']    = False
restricted_dict['true']     = True

restricted_dict['None']     = None
restricted_dict['none']     = None
restricted_dict['null']     = None
restricted_dict['nill']     = None

def restricted_eval(s, allowed = None) :

    allowed = allowed or {}
    allowed.update(restricted_dict)
    try     :
        return(eval(s, { "__builtins__" : {} }, allowed))
    except  :
        pass
    raise ValueError


def restricted_exec(s, allowed = None) :
    allowed = allowed or {}
    try :
        g   = { "__builtins__" : {} }
        g.update(restricted_dict)
        g.update(allowed)
        vd  = {}
        exec(s, g, vd)

        return(vd)

    except  :
        pass
    raise ValueError


full_user_name  = None
def get_full_user_name() :
    """ Return the user's full name, if it's there. Otherwise, return the user login name. """
    global  full_user_name
    if      full_user_name is not None :
        return(full_user_name)

    n   = getpass.getuser()
    if  n       :                                       # note: re.sub(r',.*$', '', pwd.getpwnam(getpass.getuser()).pw_gecos)
        r, rs   = run_program('finger -p %s' % n)
        if  not r :
            g   = re.search(r"Login: " + re.escape(n) + r"\s+Name:\s+(.+?)[\r\n]", rs)
            if  g :
                nn  = g.group(1).strip()
                if  nn :
                    n   = nn
                pass
            pass
        pass
    full_user_name      = n or ""

    return(n)



def create_valid_id(hi) :
    """ Return a non-zero, etc. random number up to, but not including, the given 'hi' number. """
    while   True    :
        n           = random.randint(0x10001, hi)
        if  (not (0x19000101 <= n <= 0x20991231)) and (n != hi) :           # fix the original, ambiguous treatment of hi by getting another number so regression tests dependent on the modulo calculation in random won't fail
            break
        pass
    return(n)


def create_small_id() :
    """ Return a non-zero, 31-bit, random number. """
    return(create_valid_id(0x7fffFFeF))


def create_uid(length = 8) :
    """ Return a UID - an 8 or given-length character, random, base-36-number string. """
    length  = ((length <= 0) and 8) or length
    while True :
        n   = create_valid_id((36 ** length) - 1)
        uid = (("0" * length) + base_36_encode(n).lower())[-length:]
        if  not uid.startswith('0') :                                       # don't allow strings that could be confused with octal or hex numbers in C form
            break
        pass
    return(uid)



def get_system_wide_lock(process_name = None):
    """
        Return a lock for this program - actually a system-wide mutex/lock by the given name.

        So we can see if we are already running.

        Make sure the returned variable stays in scope or else it will be deleted by Python and won't work as you want it to.

    """
    process_name    = process_name or undotted_file_name_able(__file__) or "tzlib_py"
    if  win32api    :
        import  win32event, winerror

        lock    = win32event.CreateMutex(None, False, process_name)
        if  win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS :

            return(None)

        pass
    else        :
        import  socket

        lock    = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
        try     :
            lock.bind('\0' + process_name)
        except socket.error:

            return(None)

        pass

    return(lock)


def release_system_wide_lock(lock) :
    """
        Release the given system wide lock.

        It won't be used any more.

        Return the lock if something goes wrong. Otherwise, return None.

    """
    if  not lock :
        return(None)

    if  win32api :
        if  win32api.CloseHandle(lock) :

            return(None)

        pass

    else        :
        import  socket

        try     :
            lock.close()
            return(None)
        except socket.error:
            pass
        pass

    return(lock)                    # something went wrong



usb_gvfs_re = re.compile(r'/gvfs/mtp:host=%5Busb%3A(\d+)%2C(\d+)%5D', re.IGNORECASE)

def find_usb_gvfs_drive_dirs() :
    """ Return an array of the directory names of currently mounted USB gvfs "drives". """
    dns = []
    for dn in glob.glob(expand_user_vars('/run/user/%s/gvfs/*' % os.geteuid())) :
        if  os.path.isdir(dn) and usb_gvfs_re.search(dn) :
            dns.append(dn)
        pass
    return(dns)


def get_all_drive_paths() :
    """ Get a list of all the drives on the system. For now, this here to note how to do this under Windows. """
    drives          = []
    if  win32api    :
        try         :
            drives  = [ dn.rstrip('\\') for dn in win32api.GetLogicalDriveStrings().split('\0') if dn ]
        except      :
            pass
        pass
    if  not drives  :
        if  sys.platform == 'win32' :
            drives  = [ d + ':' for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' if os.path.exists(d + ':') ]
        else        :
            drives  = [ '/', ]              # doesn't work yet
        pass
    return(drives)


def get_mount_point(dn) :
    """ Return the path to this path's mount point. """
    dn      = os.path.abspath(dn)
    if  sys.platform == 'win32' :
        while True  :
            ndn = os.path.dirname(dn)
            if  ndn == dn :
                break
            dn  = ndn
        return(dn)

    while not os.path.ismount(dn) :
        ndn = os.path.dirname(dn)
        if  ndn == dn :
            break
        dn  = ndn
    return(dn)


def get_system_drive_path() :
    """ Get the system drive's path. """
    if  sys.platform == 'win32' :
        return(os.getenv('SystemDrive') or "C:")
    return('/')


def get_disk_space(drv = None) :
    """
        Get the disk space and space available on the given drive (e.g. C: or /).

        Return (None, None) if the drive isn't there.

    """
    drv     = drv or get_system_drive_path()
    if  win32api :
        try :
            ds  = win32api.GetDiskFreeSpaceEx(drv)
        except pywintypes.error :
            return(None, None)
        return(ds[1], ds[0])

    ds      = os.statvfs(drv)                                       # note: must be a path, not and /dev/sd??
    return(ds.f_blocks * ds.f_bsize, ds.f_bavail * ds.f_bsize)




def sha_directory(dn) :
    """ Return a dictionary keyed by SHA1 hex strings and valued by an array of file names - and keyed by fn_{base_file_name}, valued by SHA1 of file and keyed by 'FN_', valued by the absolute directory name. """
    dn      = os.path.abspath(dn)
    sha_dir = {}
    for fn in glob.glob(os.path.join(dn, "*")) :
        if  safe_file_size(fn) :
            hs      = hashlib.sha1(safe_read_whole_binary_file(fn)).hexdigest().lower()
            sha_dir[hs] = sha_dir.get(hs, [])
            sha_dir[hs].append(fn)
            sha_dir['fn_' + os.path.basename(fn)]   = hs
        pass
    sha_dir['FN_']  = dn
    return(sha_dir)


def sha_dir_compare(from_sha_dir, to_sha_dir) :
    """ Return a list of files in the from_dir not in the to_dir and an (overriding) list of files that collide between the two directories. """
    ufns    = []
    hashes  = [ hs for hs in from_sha_dir.keys() if not hs.lower().startswith('fn_') ]
    for hs in hashes :
        if  hs not in to_sha_dir :
            ufns   += from_sha_dir[hs]
        pass

    cfns            = []
    nfns            = []
    for fn in ufns  :
        if  ('fn_'  + os.path.basename(fn)) in to_sha_dir :
            cfns.append(fn)
        else        :
            nfns.append(fn)
        pass

    return(nfns, cfns)



def svn_info(dn = None) :
    """
        Return a dictionary with SVN information about the given directory in it.

        Return None if pysvn can't be imported.

        Return an empty dictionary for a non-svn directory.

    """
    try :
        import  pysvn
    except ImportError :
        return(None)

    dn  = expand_user_vars(dn or '.')
    try :
        return(pysvn.Client().info2(dn, recurse = False)[0][1])
    except :    # pysvn._pysvn_2_7.ClientError :
        pass
    return({})


def svn_simple(dn = None) :
    """
        Get the SVN "simple" information for the given directory in a dictionary.

        Return None if pysvn can't be imported.

        Return an empty dictionary for a non-svn directory.

        This "simple" information can include the user name, password, password type, and realmstring.

        It's from a file in ~/.subversion/simple/

    """
    d   = svn_info(dn)
    if  not d :
        return(d)

    url     = d.get('repos_root_URL', None)
    if  not url :
        return({})
    uuid    = d.get('repos_UUID',     None)

    url     = urlparse.urlparse(url)
    dd      = {
                'urlparse_tuple'    : url,
                'url_scheme'        : url.scheme,
                'url_netloc'        : url.netloc,
                'url_path'          : url.path,
                'url_params'        : url.params,
                'url_query'         : url.query,
                'url_fragment'      : url.fragment,
                'url_username'      : url.username,
                'url_password'      : url.password,
                'url_hostname'      : url.hostname,
                'url_port'          : url.port,
              }
    url     = list(url)
    url[2]  = ''        # .path
    url[3]  = ''        # .params
    url[4]  = ''        # .query
    url[5]  = ''        # .fragment
    url     = urlparse.urlunparse(url)

    d       = dd
    for fn in glob.glob(expand_user_vars('~/.subversion/auth/svn.simple/*')) :
        fd  = safe_read_whole_text_file(fn)
        fuu     = uuid and (fd.find(uuid) > 0)
        if  (fd.find(url) > 0) or fuu :
            if  fuu :
                d   = dd

            g   = re.search(r'K 8\npasstype\nV (\d+)\n(.*?)\n', fd)
            if  g   :
                d['passtype']   = g.group(2)
            g   = re.search(r'K 8\npassword\nV (\d+)\n(.*?)\n', fd)
            if  g   :
                d['password']   = g.group(2)
            g   = re.search(r'K 15\nsvn:realmstring\nV (\d+)\n(.*?)\n', fd)
            if  g   :
                d['realm']      = g.group(2)
            g   = re.search(r'K 8\nusername\nV (\d+)\n(.*?)\n', fd)
            if  g   :
                d['username']   = g.group(2)

            if  fuu :
                break

            pass
        pass

    return(d)




def add_to_file(fn, *args) :
    """ Append the given writeable things to the file. """
    try :
        fo  = open(fn, 'a')
        for a in args :
            fo.write(a)
        fo.close()
    except ( OSError, IOError ) :
        return(False)
    return(True)


def add_print_to_file(fn, *args) :
    """ Append the given text or whatever to the file in print form. """
    try :
        fo  = open(fn, 'a')
        sp  = ''
        for a in args :
            fo.write(sp + str(a))
            sp  = ' '
        fo.write('\n')
        fo.close()
    except ( OSError, IOError ) :
        return(False)
    return(True)


def make_numbered_file(fn, starting_i = None) :
    """
        Make a file name of a new file that doesn't exist.

        If the given name exists, then add a _# to the base name before the extension, where # is 1... or (starting_i or 1)...

        If 'starting_i' is not None, then return the file name and the # that's found.

        Otherwise, just return the file name.

    """
    i   = starting_i or 0
    if  os.path.exists(fn) :
        bfn, ext    = os.path.splitext(fn)
        i           = i or 1
        while True  :
            fn      = bfn + "_" + str(i) + ext
            if  not os.path.exists(fn) :
                break
            i  += 1                                                 # find the highest unused sub-number
        pass
    if  starting_i is not None :
        return(fn, i)
    return(fn)



def ladder_score(winner_current_ladder_score, loser_current_ladder_score) :
    """
        A method of scoring for a ladder of participants. Taken from https://roshambo.me/users/info/

        Return the new ladder scores for the winner and loser.

    """
    return(
            winner_current_ladder_score + min(100.0, max(10.0, (50.0 + (( loser_current_ladder_score - winner_current_ladder_score) / 4.0)))),
             loser_current_ladder_score - min( 50.0, max( 5.0, (25.0 - ((winner_current_ladder_score -  loser_current_ladder_score) / 8.0)))),
          )


def find_representative_points_on_a_rubber_line(numbers, how_many, distance_between) :
    """
        Given an ordered array of 'numbers', return an ordered array with 'how_many' of the numbers, not including the 1st and last number.

        The returned array will be chosen to minimize the following for each of the N returned numbers:

            abs(distance_between(numbers[N], numbers[N+1]) - distance_between(numbers[N], numbers[N-1]))
                /
               (distance_between(numbers[N], numbers[N+1]) + distance_between(numbers[N], numbers[N-1]) + 1.0)

        That is, the returned numbers want to be evenly spaced in terms of 'distance_between' over the whole numbers array.

        The N-1'th number for the 1st  returned number is numbers[ 0].

        The N+1'th number for the last returned number is numbers[-1].

        So if how_many is 2 and distance_from is

            lambda n1, n2 : abs(n1 - n2)

        and numbers is [ 1, 4, 5, 9, 11, 100 ],

        then this routine will return [ 5, 11 ].

    """
    #
    #   Notes: This thing should start by distributing the margin over the starting indices by moving them to the right by one.
    #          Sorta like a Bresenham's line drawing algorithm.
    #          But, since the indices move around anyway, meh.
    #
    numbers = sorted(numbers)
    if  how_many + 2 > len(numbers) :
        return(numbers[1:-1])
    stp     = (len(numbers) - 2) / how_many
    mrg     = (len(numbers) - 2) - (stp * (how_many - 1))
    retval  = [ 0 ] + [ i for i in xrange(1 + (mrg / 2), 1 + (mrg / 2) + (stp * how_many), stp) ] + [ len(numbers) - 1 ]

    # print "@@@@", len(numbers), how_many, stp, mrg, retval, [ retval[i] - retval[i - 1] for i in xrange(1, len(retval)) ]

    fnd     = sys.maxsize
    while fnd > how_many * 0.8 :
        fnd = 0
        for i in xrange(1, len(retval) - 1)     :
            dw      = distance_between(numbers[retval[i]], numbers[retval[i - 1]])
            de      = distance_between(numbers[retval[i]], numbers[retval[i + 1]])
            d       = abs(dw - de) / (dw + de + 1.0)
            if  retval[i - 1]  != retval[i] - 1  :
                odw = distance_between(numbers[retval[i] - 1], numbers[retval[i - 1]])
                ode = distance_between(numbers[retval[i] - 1], numbers[retval[i + 1]])
                od  = abs(odw - ode) / (odw + ode + 1.0)
                if  od < d      :
                    retval[i]  -= 1
                    fnd        += True
                    continue
                pass
            if  retval[i + 1]  != retval[i] + 1  :
                odw = distance_between(numbers[retval[i] + 1], numbers[retval[i - 1]])
                ode = distance_between(numbers[retval[i] + 1], numbers[retval[i + 1]])
                od  = abs(odw - ode) / (odw + ode + 1.0)
                if  od < d      :
                    retval[i]  += 1
                    fnd        += True
                pass
            pass

    return([ numbers[i] for i in retval[1:-1] ])


if  False and (__name__ == '__main__') :
    rtn = lambda n1, n2 : abs(n1 - n2)
    print find_representative_points_on_a_rubber_line([ 1, 4, 5, 9, 11, 100 ], 2, distance_between = rtn)
    for hm in [ 1, 2, 3, 4, 5, 9, 10 ] :
        print find_representative_points_on_a_rubber_line([ 1, 4, 3, 5, 8, 17, 14, 25, 9, 11, 100 ], hm, distance_between = rtn)
        print find_representative_points_on_a_rubber_line([ 1, 4, 3, 5, 8, 13, 17, 14, 25, 9, 11, 100 ], hm, distance_between = rtn)
        print find_representative_points_on_a_rubber_line([ 1, 4, 3, 5, 8, 13, 15, 17, 14, 25, 9, 11, 100 ], hm, distance_between = rtn)
        print find_representative_points_on_a_rubber_line([ 1, 4, 3, 5, 8, 13, 15, 18, 17, 14, 25, 9, 11, 100 ], hm, distance_between = rtn)
        print find_representative_points_on_a_rubber_line([ 1, 4, 3, 5, 8, 13, 15, 18, 17, 89, 14, 25, 9, 11, 100 ], hm, distance_between = rtn)
    sys.exit(1)




#
#
#       Test
#
#
def test() :
    """ Test some of the code. Crash if anything is found wrong. """
    ctm = elapsed_time()
    print "%20.10f" % ( ctm )

    swlock  = get_system_wide_lock()
    if  not swlock :
        raise ValueError("Get system wide lock failed!")

    try             :
        Image       = __import__('PIL.Image', fromlist = 'PIL')         # pull in PIL without letting modulefinder.py know about it.
        img         = Image.open('gimp_clr_lvl_test.png')
        sz          = img.size

        if  not numpy :
            raise ImportError('No numpy module')

        img         = numpy.asarray(img)                    # note: numpy_image is read only
        for i,  wim in enumerate([ img, [ [ [ int(img[y][x][c]) for c in ( 0, 1, 2 ) ] for x in xrange(img.shape[1]) ] for y in xrange(img.shape[0]) ], ]) :
            wim     = do_like_gimps_auto_adjust_color_levels(wim)
            try     :
                wim = Image.fromarray(wim)                  # back to PIL form
            except AttributeError :
                oim = Image.open('gimp_clr_lvl_test.png')
                pix = oim.load()
                for y in xrange(sz[1]) :
                    for x in xrange(sz[0]) :
                        pix[x, y]   = tuple(wim[y][x])
                    pass
                wim = oim
            m5      = hashlib.md5(flatten_array(numpy.asarray(wim))).hexdigest().lower()
            if  m5 != '0fb94c164e8bd252bb4456756e7f25a4' :          # note: it's a miracle that the numpy.percentage() and our median() agree and all the rest of the arithmetic agrees, and the png files don't have something like a date stamp in them, etc. etc. But that seemed to be the case.
                raise ValueError('Wrong gimp-auto-adjust MD5 (%s) for output image data %s' % ( m5, (i and " without numpy") or " using numpy", ))
            if  False :
                fn      = 'x_gt_tmp.png'
                wim.save(fn)
                d       = safe_read_whole_binary_file(fn)
                m5      = hashlib.md5(d).hexdigest().lower()
                if  m5 != 'b4ca2ae7627d4c7a72d6dd9a0e50d2be' :          # note: it's a miracle that the numpy.percentage() and our median() agree and all the rest of the arithmetic agrees, and the png files don't have something like a date stamp in them, etc. etc. But that seemed to be the case.
                    raise ValueError('Wrong gimp-auto-adjust MD5 (%s) for output image %s%s' % ( m5, fn, (i and " without numpy") or " using numpy", ))
                pass
            pass
    except  ImportError :
        print "No Image module or no numpy so I cannot test white_balance/auto-adjust"

    a = [ 1, 2, 3 ]
    d = make_dictionary(a)

    print d[1], d[2], d[3]
    d = make_dictionary(a, "17")

    b = d.keys()
    a.sort()
    b.sort()
    for i in range(0, max(len(a), len(b))) :
        if  a[i] != b[i] :
            s   = "make_dictionary a != b %d" % i
            raise ValueError(s)
        pass

    a   = [ 10, 11, 12, 13, 14 ]
    d   = make_index_dictionary(a)
    for i in range(max(len(a), len(d))) :
        if  i != d[a[i]] :
            s   = "make_index_dictionary i != d[a[i]] %d" % i
            raise ValueError(s)
        pass


    d   = { "xyzzy" : "y", "a" : "baby" }
    rd  = invert_dictionary(d)
    if  rd != { "y" : "xyzzy", "baby" : "a" } :
        s   = "invert_dictionary(%s) is %s" % ( repr(d), repr(rd) )
        raise ValueError(s)

    d   = { "x" : "y", "a" : "y" }
    rd  = invert_dictionary(d, dupable_values = { 'y' : 'x' })
    if  rd != { "y" : "x" } :
        s   = "invert_dictionary(%s) is %s" % ( repr(d), repr(rd) )
        raise ValueError(s)

    d   = { "x" : "y", "a" : "y" }
    rd  = invert_dictionary(d, dupable_values = { 'y' : 'a' })
    if  rd != { "y" : "a" } :
        s   = "invert_dictionary(%s) is %s" % ( repr(d), repr(rd) )
        raise ValueError(s)

    try :
        d   = { "x" : "y", "a" : "y" }
        rd  = invert_dictionary(d)
        raise ValueError("invert_dictionary(%s) happy" % repr(d))
    except IndexError :
        pass


    a   = [ [ 'a', 1 ], [ 'b', 2 ], ]
    rd  = buples_to_dictionary(a)
    if  len(rd) != 2 :
        s   = "buples_to_dictionary not 2 long! %s" % rd
        raise ValueError(s)
    if  (rd['a'] != 1) or (rd['b'] != 2) :
        s   = "buples_to_dictionary values wrong! %s" % rd
        raise ValueError(s)


    a   = [ 1, 2, 3, 4 ]
    d   = { 1 : 1, 2 : 2, 3 : 3, 5 : 5, }
    if  not_in(d, a) != [ 5 ] :
        raise ValueError('5 not not_in, is %s' % repr(not_in(d, a)))

    a   = [ 1, 2, 3, 4 ]
    d   = { 2 : 2, }
    if  not_in(d, a) != [] :
        raise ValueError('not_in nothing not nothing, is %s' % repr(not_in(d, a)))

    d   = [ 0, 1, 2, 3, 4, ]
    a   = { 1 : 1, 2 : 2, 3 : 3, }
    if  sorted(not_in(d, a)) != [ 0, 4 ] :
        raise ValueError('0 not not_in, is %s' % repr(not_in(d, a)))

    d   = [ 1, 2, 3, 4, ]
    a   = { 2 : 2, }
    if  sorted(not_in(d, a)) != [ 1, 3, 4, ] :
        raise ValueError('not_in not 1, 3, 4 with odd types, is %s' % repr(not_in(d, a)))

    d   = [ 1, 2, 3, 4, ]
    a   = { }
    if  not_in(d, a) != d :
        raise ValueError('not_in nothing not nothing with odd types, is %s' % repr(not_in(d, a)))


    d   = [ 1, 2, 3, 2, 3, 3 ]
    a   = sorted_by_count(d)
    if  a != [ 1, 2, 3, ] :
        raise ValueError('sorted_by_count of %s != %s' % ( str(d), str(a), ))
    d   = [ 1, 2, 3, 2, 1, 1 ]
    a   = sorted_by_count(d)
    if  a != [ 3, 2, 1, ] :
        raise ValueError('sorted_by_count of %s != %s' % ( str(d), str(a), ))
    d   = [ 4, 1, 2, 3, 2, 3, 3 ]
    a   = sorted_by_count(d)
    if  a != [ 1, 4, 2, 3, ] :
        raise ValueError('sorted_by_count of %s != %s' % ( str(d), str(a), ))
    d   = [ 1, 2, 3, 2, 3, 3, 4 ]
    a   = sorted_by_count(d)
    if  a != [ 1, 4, 2, 3, ] :
        raise ValueError('sorted_by_count of %s != %s' % ( str(d), str(a), ))


    d   = { "xYz" : 5, "zyZ" : 10, "yyz" : 15, }
    update_all_case_keys(d)
    if  (
            (d['xyz'] != 5)
         or (d['XYZ'] != 5)
         or (d['xYz'] != 5)
         or (d['zyz'] != 10)
         or (d['ZYZ'] != 10)
         or (d['zyZ'] != 10)
         or (d['yyz'] != 15)
         or (d['YYZ'] != 15)
         or (len(d) != 8)
        ) :
        s   = "update_all_case_keys " + str(d)
        raise ValueError(s)

    crc = blkcrc32(_TEST_CRC_VALUE, "now is the time")
    crc = long(crc) & 0xFFFFffffL
    print "crc=0x%08lx %lu" % ( crc, crc )
    if  crc != 0xa458b82eL :
        s    = "crc32 [%s] is wrong!" % ( str(crc) )
        raise ValueError(s)
    crc = blkcrc32(0x98765431L, "now is the time")
    crc = crc & 0xFFFFffffL
    print "crc=0x%08lx %lu" % ( crc, crc )
    if  crc != 0xb525d257L :
        s    = "crc32 [%s] is wrong!" % ( str(crc) )
        raise ValueError(s)

    crc = 0
    bts = 0
    for i in range(1000) :
        s   = "%u %f" % ( i, random.random() )
        pc  = pure_python_crc32(crc, s)
        bts |= crc
        nc  = blkcrc32(crc,          s)
        if  pc != nc :
            s   = "crc mismatch 'tween pure and zlib (%s (%08lx:%u) %08lx:%u != %08lx:%u)!" % ( s, crc, crc, pc, pc, nc, nc )
            raise ValueError(s)
        if  random.random() >= 0.5 :
            crc = pc
        else :
            crc = nc

        if  (i > 100) and ((bts & 0xFFFFffffL) == 0xFFFFffffL) :
            break
        pass

    print "bcrc = %08x" % ( crc )


    crc = blkcrc16(_TEST_CRC_VALUE, "now is the time")
    print "crc=0x%04x %u" % ( crc, crc )
    if  crc != 0xe6eb :
        s    = "crc16 [0x%04x %s] is wrong!" % ( crc, str(crc) )
        raise ValueError(s)
    crc = blkcrc16(0x1234L,         "now is the time")
    print "crc=0x%04x %u" % ( crc, crc )
    if  crc != 0x11ee :
        s    = "crc16 [0x%04x %s] is wrong!" % ( crc, str(crc) )
        raise ValueError(s)


    vls = [
            [ 0,                 '0',           ],
            [  1,                '1',           ],
            [ -1,               '-1',           ],
            [  1234,             'YA',          ],
            [ -1234,            '-YA',          ],
            [  123456787901234,  '17RF9JYFQQ',  ],
            [ -123456787901234, '-17RF9JYFQQ',  ],
            [  1234.9,           'YA',          ],
            [ -1234.9,          '-YA',          ],
          ]
    for v, vs in vls :
        s   = base_36_encode(v)
        if  s != vs :
            raise ValueError("base_36 of %s is %s" % ( str(v), str(s), ) )
        s   = base_36_decode(s)
        if  s != int(v) :
            raise ValueError("un-base_36 of %s is %s" % ( str(v), str(s), ) )
        pass



    if  bool_to_0_or_1(False) != 0 :
        raise ValueError("bool_to_0_or_1(False) == %s" % bool_to_0_or_1(False))

    if  bool_to_0_or_1(True) != 1 :
        raise ValueError("bool_to_0_or_1(True) == %s" % bool_to_0_or_1(True))


    if  array_find( [ 1, 2, 3 ], 2) != 1 :
        s = "1st array_find != 1"
        raise ValueError(s)
    if  array_find( [ 1, 2, 3 ], [ 2, 1 ] ) != 1 :
        s = "2nd array_find != 1"
        raise ValueError(s)
    if  array_find( [ 1, 2, 3 ], [ 1, 2 ] ) != 0 :
        s = "array_find != 0"
        raise ValueError(s)
    if  array_find( [ 1, 2, 3 ], [ 4, 5, 6, 7 ] ) >= 0 :
        s = "array_find >= 0"
        raise ValueError(s)

    a   = [ 1, 2, 3, 1, 3, ]
    b   = array_replaced(a, 3, 5)
    if  b == a :
        raise ValueError('array_replaced changed the array in place: %s -> %s' % ( str(a), str(b), ))
    if  b != [ 1, 2, 5, 1, 5, ] :
        raise ValueError('array_replaced goofed: %s -> %s' % ( str(a), str(b), ))

    a   = [ 1, 2, 3, 1, 3, ]
    b   = array_replace(a, 3, 5)
    if  b is not None :
        raise ValueError('array_replace return a value: %s' % str(b))
    if  a != [ 1, 2, 5, 1, 5, ] :
        raise ValueError('array_replace goofed: %s' % str(a))


    v   = [ [], [ 1, 2, 3 ], "4", [ 5, ], "", [ 6, [ 7, [ [], [ 8, 9, ], [] ], [10], [], [ 11, 12, 13, ] ], '14', ], 15, [], ]
    if  flatten_array(v) != [ 1, 2, 3, "4", 5, "", 6, 7, 8, 9, 10, 11, 12, 13, "14", 15 ] :
        s = "flatten_array %s" % flatten_array(v)
        raise ValueError(s)

    v   = [[1], 2, [[3,4], 5], [[[]]], [[[6]]], 7, 8, []]
    if  flatten_array(v) != [ 1, 2, 3, 4, 5, 6, 7, 8, ] :
        s   = "flatten_array %s" % flatten_array(v)
        raise ValueError(s)

    v   = [ 1, 2, 3, 4, 5 ]
    vv  = list(v)
    a   = pop_slice(v, 1, 3)
    if  (a != [ 2, 3, ]) or (v != [ 1, 4, 5 ]) :
        s   = "pop_slice() %s -> %s %s" % ( vv, v, a, )
        raise ValueError(s)

    v   = [ 1, 2, 3, 4, 5 ]
    vv  = list(v)
    a   = pop_slice(v, 1, 7)
    if  (a != [ 2, 3, 4, 5 ]) or (v != [ 1 ]) :
        s   = "pop_slice() %s -> %s %s" % ( vv, v, a, )
        raise ValueError(s)

    v   = [ 1, 2, 3, 4, 5 ]
    vv  = list(v)
    a   = pop_slice(v, 1)
    if  (a != [ 2, 3, 4, 5 ]) or (v != [ 1 ]) :
        s   = "pop_slice() %s -> %s %s" % ( vv, v, a, )
        raise ValueError(s)

    v   = [ 1, 2, 3, 4, 5 ]
    vv  = list(v)
    a   = pop_slice(v, to = 2)
    if  (a != [ 1, 2 ]) or (v != [ 3, 4, 5 ]) :
        s   = "pop_slice() %s -> %s %s" % ( vv, v, a, )
        raise ValueError(s)


    ov      = [ ( 1, 2, 3 ), ( 4, 5, 6 ) ]
    sov     = str(ov)
    v       = rotate_2d_array_clockwise(ov)
    sv      = str( [ ( 4, 1 ), ( 5, 2 ), ( 6, 3 ) ] )
    if  str(v) != sv :
        raise ValueError("rotate_2d_array_clockwise " + str(v) + " " + sv)

    v       = rotate_2d_array_counter_clockwise(v)
    if  str(v)  != sov :
        raise ValueError("rotate_2d_array_counter_clockwise " + str(v) + " " + sov)


    if  find_arg(  "x"   ,   "y") >= 0 :
        raise ValueError("find_arg found  y in   x")
    if  find_arg([ "x", ], [ "y", ])  >= 0 :
        raise ValueError("find_arg found [y] in [x]")
    if  find_arg(  "x"   ,   "_x_"   ) < 0 :
        raise ValueError("find_arg not found  _x_  in  x ")
    if  find_arg([ "x", ], [ "_x_", ]) < 0 :
        raise ValueError("find_arg not found [_x_] in [x]")
    if  find_arg([ "-x-", ], [ "_x_", ]) < 0 :
        raise ValueError("find_arg not found [_x_] in [-x-]")
    if  find_arg([ "b", "--x_y", ], [ "-a", "--xy", ]) < 0 :
        raise ValueError("find_arg not found [--xy] in [--x_y]")
    if  find_arg([ "b", "--x-y", ], [ "-a", "--x_y", ]) < 0 :
        raise ValueError("find_arg not found [--x_y] in [--x-y]")
    if  find_arg([ "b", "--xy", ], [ "-a", "--x_y", ]) < 0 :
        raise ValueError("find_arg not found [--x_y] in [--xy]")

    if  find_argi(  "x"   ,   "y") >= 0 :
        raise ValueError("find_argi found  y in   x")
    if  find_argi([ "x", ], [ "y", ])  >= 0 :
        raise ValueError("find_argi found [y] in [x]")
    if  find_argi(  "x"   ,   "_x_"   ) != 0 :
        raise ValueError("find_argi not found  _x_  in  x ")
    if  find_argi([ "x", ], [ "_x_", ]) != 0 :
        raise ValueError("find_argi not found [_x_] in [x]")
    if  find_argi([ "-x-", ], [ "_x_", ]) != 0 :
        raise ValueError("find_argi not found [_x_] in [-x-]")
    if  find_argi([ "b", "--x_y", ], [ "-a", "--x-y", ]) != 1 :
        raise ValueError("find_argi not found [--xy] in [--x-y]")
    if  find_argi([ "b", "--x-y", ], [ "-a", "--x_y", ]) != 1 :
        raise ValueError("find_argi not found [--x_y] in [--x-y]")
    if  find_argi([ "b", "--xy", ], [ "-a", "--x_y", ]) != 1 :
        raise ValueError("find_argi not found [--x_y] in [--xy]")
    if  find_argi([ "---xy", "--x_y", '--xy', ], [ "-a", "---x-y", ]) != 0 :
        raise ValueError("find_argi not found [---xy] in [---x-y]")


    s   = comma_and_join([], comma = ': ', ands = " AND ")
    cs  = ""
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([ 'one', ], comma = ': ', ands = " AND ")
    cs  = "one"
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([ 'one', 'two', ], comma = ': ', ands = " AND ")
    cs  = "one AND two"
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([ 'one', 'two', 'three', ], comma = ': ', ands = " AND ")
    cs  = "one: two: AND three"
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([ 'one', 'two', 'three', 'four', ], comma = ': ', ands = " AND ")
    cs  = "one: two: three: AND four"
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([])
    cs  = ""
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([ 'one', ])
    cs  = "one"
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([ 'one', 'two', ])
    cs  = "one and two"
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([ 'one', 'two', 'three', ])
    cs  = "one, two, and three"
    if  s != cs :
        raise ValueError(s + " is not " + cs)
    s   = comma_and_join([ 'one', 'two', 'three', 'four', ])
    cs  = "one, two, three, and four"
    if  s != cs :
        raise ValueError(s + " is not " + cs)


    if  strrev("x") != "x" :
        s = "strrev of 'x' is " + strrev("x")
        raise ValueError(s)

    if  strrev("xy") != "yx" :
        s = "strrev of 'xy' is " + strrev("xy")
        raise ValueError(s)

    if  strrev("xyz") != "zyx" :
        s = "strrev of 'xyz' is ", strrev("xyz")
        raise ValueError(s)


    s   = file_name_able("abc:^%&\"\';?*blah")
    if  s != "abc__%__;__blah" :
        raise ValueError("file_name_able not right! [%s]" % s)

    s   = file_name_able("-abc")
    if  s != "_abc" :
        raise ValueError("file_name_able not right! [%s]" % s)

    s   = file_name_able(u"\xe0bc:^%&\"\';?*blah")
    if  s != u"abc__%__;__blah" :
        raise ValueError("file_name_able not right! [%s]" % s)

    s   = undotted_file_name_able(u"\xe0bc:^%&\"\';?*bl.a.h")
    if  s != u"abc__%__;__bl_a_h" :
        raise ValueError("file_name_able not right! [%s]" % s)

    s   = undotted_file_name_able(u"brak[et]t")
    if  s != u"brak_et_t" :
        raise ValueError("file_name_able not right! [%s]" % s)



    s   = safe_relpath(None)
    if  s is not None :
        raise ValueError("safe_relpath of None! [%s]" % str(s))

    s   = safe_relpath("")
    if  s != "" :
        raise ValueError("safe_relpath of ''! [%s]" % str(s))

    s   = safe_relpath("./x.y")
    if  s != "x.y".replace('/', os.path.sep) :
        raise ValueError("safe_relpath of ./x.y! [%s]" % str(s))

    s   = safe_relpath("../x.y")
    if  s != "../x.y".replace('/', os.path.sep) :
        raise ValueError("safe_relpath of ../x.y! [%s]" % str(s))


    if  same_file("tzlib.py", "tz_lib_test.py") :
        raise ValueError('same_file("tzlib.py", "tz_lib_test.py")' is True)

    if  not same_file("tzlib.py", "tzlib.py") :
        raise ValueError('same_file("tzlib.py", "tzlib.py")' is False)

    if  not same_file("../tzpython/tzlib.py", "tzlib.py") :
        raise ValueError('same_file("../tzpython/tzlib.py", "tzlib.py")' is False)



    if  not can_run_program('shipit.py') :
        s = "Cannot run ship.py, apparently!"
        raise ValueError(s)

    if  can_run_program('xxx.yyy') :
        s = "Can run xxx.yyy, apparently!"
        raise ValueError(s)

    if  sys.platform != 'win32' :
        if  can_run_program('tzlib.py') :
            s = "Can run tzlib.py, apparently!"
            raise ValueError(s)
        pass

    ( r, rs )   = run_program("python factor.py %u" % ( 2 * 3 * 5 * 7 * 11 * 13 ) )
    if  r       :
        raise ValueError("Failure to run_program('python factor.py') : %s", str(r))
    if  rs.find("30030") < 0 :
        raise ValueError("run_program('python factor.py') failed to print result")
    print rs


    print "Dir of *.py files in this directory:"
    files = ambiguous_file_list("*.py")
    print len(files), files

    print "Dir of *.py files in this directory and sub-dirs:"
    files = ambiguous_file_list("*.py", True)
    print len(files), files

    print "Ext dir of *.py files in this directory:"
    files = ext_ambiguous_file_list(".", "py")
    print len(files), files

    print "Ext dir of *.py files in this directory and sub-dirs:"
    files = ext_ambiguous_file_list(".", "py", True)
    print len(files), files

    if  tz_vector_cosine( [ 1, 2, 3 ], [ 4, -5, 6 ]) != 0.365486942323903610 :
        s = "Cosine problem %2.50f" % ( tz_vector_cosine( [ 1, 2, 3 ], [ 4, -5, 6 ]) )
        raise ValueError(s)

    if  numpy :
        a   = angle_between_two_vectors(numpy.asarray((1, 0, 0), dtype = numpy.float), numpy.asarray((0, 1, 0), dtype = numpy.float))
    else    :
        a   = angle_between_two_vectors(              (1, 0, 0),                                     (0, 1, 0))
    r   = 1.5707963267948966
    if  abs(a - r) > 0.0000000000000001 :
        s = "Vector angle problem %2.50f should be %2.50f" % ( a, r, )
        raise ValueError(s)

    if  numpy :
        a   = angle_between_two_vectors(numpy.asarray((1, None, 0, 0), dtype = numpy.float), numpy.asarray((0, 4, 1, 0), dtype = numpy.float))
    else    :
        a   = angle_between_two_vectors(              (1, None, 0, 0),                                     (0, 4, 1, 0))
    r   = 1.5707963267948966
    if  abs(a - r) > 0.0000000000000001 :
        s = "Vector angle problem %2.50f should be %2.50f" % ( a, r, )
        raise ValueError(s)

    a   = angle_between_two_vectors(                  (1, None, 0, 0),                                     (0, 4, 1, 0))
    r   = 1.5707963267948966
    if  abs(a - r) > 0.0000000000000001 :
        s = "Vector angle problem %2.50f should be %2.50f" % ( a, r, )
        raise ValueError(s)

    a   = angle_between_two_vectors((1, 0, 0), (1, 0, 0))
    r   = 0.0
    if  abs(a - r) > 0.0000000000000001 :
        s = "Vector angle problem %2.50f should be %2.50f" % ( a, r, )
        raise ValueError(s)

    a   = angle_between_two_vectors((1, 0, 0), (-1, 0, 0))
    r   = math.pi
    if  abs(a - r) > 0.0000000000000001 :
        s = "Vector angle problem %2.50f should be %2.50f" % ( a, r, )
        raise ValueError(s)

    a   = angle_between_two_vectors((1, 0, 0), (0, 0, 0))
    if  a is not None :
        s = "Vector angle problem %s should be None" % a
        raise ValueError(s)


    s   = " ".join(string_pairs( [ "a", "bb", "c", "d" ], 1, "x"))
    if  s != "axbb bbxc cxd" :
        s = "string_pairs problem: " + s
        raise ValueError(s)


    s   = " ".join(flat_positional_strings( [ "a", "bb", "c", "d" ], 3, "_%u_%s_"))
    if  s != "_0_a_ _1_bb_ _1_c_ _2_d_ _3_a_ _3_bb_ _4_c_ _4_d_" :
        s = "flat_positional_strings problem: " + s
        raise ValueError(s)


    s   = " ".join(log_positional_strings( [ "a", "bb", "c", "d", "e", "f", "g", "h", "i", "j" ], 2.0, "_%u_%s_"))
    if  s != "_0_a_ _1_bb_ _2_c_ _2_d_ _3_e_ _3_f_ _3_g_ _4_h_ _4_i_ _4_j_" :
        s = "log_positional_strings problem: " + s
        raise ValueError(s)


    s   = " ".join(log_positional_strings( [ "a", "bb", "c", "d", "e", "f", "g", "h", "i", "j" ], 3.0, "_%u_%s_"))
    if  s != "_0_a_ _2_bb_ _3_c_ _4_d_ _4_e_ _5_f_ _5_g_ _6_h_ _6_i_ _6_j_" :
        s = "log_positional_strings problem: " + s
        raise ValueError(s)


    ( m, a )    = linear_regression( ( -2, -1, 0, 1, 2, 3 ), ( -1, 0, 1, 2, 3, 4 ) )
    if  (m != 1.0) or (a != 1.0) :
        s   = "Line: m=" + str(m) + " a=" + str(a)
        raise ValueError(s)


    ( m, a )    = linear_regression( None, ( -1, 1, 3, 5, 7 ) )
    if  (m != 2.0) or (a != -1.0) :
        s   = "NoneX: m=" + str(m) + " a=" + str(a)
        raise ValueError(s)


    ( m, a )    = linear_regression( 1, ( -1, 1, 3, 5, 7 ) )
    if  (m != 2.0) or (a != -3.0) :
        s   = "OneX: m=" + str(m) + " a=" + str(a)
        raise ValueError(s)


    ( m, a )    = linear_regression( ( -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6 ), ( -4, -5, -2, -3, 0, -1, 1, 3, 2, 5, 4, 7, 6 ) )
    if  (int(round(m * 1000.0)) != 967) or (a != 1.0) :
        s   = "Jag: m=" + str(m) + " a=" + str(a)
        raise ValueError(s)

    try :
        linear_regression( ( 5.0, 5.0, 5.0 ), ( -6.0, 0.0, 10.0 ) )
        raise ValueError("linear_regression did not ValueError on vertical line")
    except ValueError :
        pass


    s   = strip_c_comments("""/* now is the time
*/ for all "/* */" fun "//" blah // testing
// more test
and finally/**/ this end
""")
    if  s != ' for all "/* */" fun "//" blah \n\nand finally this end\n' :
        s   = repr(s)
        raise ValueError(s)


    s   = decode_html_entities("hearts:&hearts; sigma:&sigma; oacute=&oacute; gt=&gt; lt=&#60;").encode('utf8')
    ss  = ("hearts:" + unichr(0x2665) + " sigma:" + unichr(0x3c3) + " oacute=" + "\xf3".decode('latin1') + " gt=> lt=<").encode('utf8')
    if  s != ss :
        s = "decode_html_entities problem: " + s
        raise ValueError(s)


    s   = safe_html("\x00&<><>&~\x7f\x80\xff#\r\n-----\r\n------\r\n-------\r\n-\r-\n-\r\n")
    if  s != "&#0;&amp;&lt;&gt;&lt;&gt;&amp;~&#127;&#128;&#255;#<BR>-----<BR><HR><BR><HR><BR>-<BR>-<BR>-<BR>" :
        s = "safe_html problem: " + s
        raise ValueError(s)


    s   = printable(" !@#$%^&*()_-|\\{}[];:'\x22<>,.?/`~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890  \x00\x01\x02\x1f\x7f\x80\xff z", "~+~")
    if  s !=        " !@#$%^&*()_-|\\{}[];:'\x22<>,.?/`~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890  ~+~~+~~+~~+~~+~~+~~+~ z" :
        s =  "printable problem: " + s
        raise ValueError(s)

    s   = printable(" !@#$%^&*()_-|\\{}[];:'\x22<>,.?/`~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890  \x00\x01\x02\x1f\x7f\x80\xff z")
    if  s !=        " !@#$%^&*()_-|\\{}[];:'\x22<>,.?/`~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890  _______ z" :
        s =  "printable problem: " + s
        raise ValueError(s)


    s   = printable_str(" !@#$%^&*()_-|{}[];:'\x22<>,.?/`~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890  \x00\x01\x02\x1f\x7f\x80\xff z")
    ss  =            r""" !@#$%^&*()_-|{}[];:'"<>,.?/`~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890  \x00\x01\x02\x1f\x7f\x80\xff z"""
    if  s != ss :                                               # note: the backslash, "\\" is interpreted or converted differently in Python 2.4 and 2.5. Arrgh.
        for ci in range(min(len(ss), len(s))) :
            if  ss[ci] != s[ci] :
                print "char mismatch %u [%s]!=[%s]" % ( ci, s[ci], ss[ci] )
                break
            pass
        s =  "printable_str problem: %s %u %u" % ( s, len(s), len(ss) )
        raise ValueError(s)


    ss      = u"MR\u0181T"
    s       = unicode_strftime(ss)
    if  s  != ss :
        raise ValueError("unicode_strftime failed Unicode \\u0181")

    ss      = u"MR\u00e1T"
    s       = unicode_strftime(ss)
    if  s  != ss :
        raise ValueError("unicode_strftime failed Unicode \\u00e1")

    ss      = u"MR\u00c0T"
    s       = unicode_strftime(ss)
    if  s  != ss :
        raise ValueError("unicode_strftime failed Unicode \\u00c0")


    s       =   c_string("[\\ \' \" \a \b \f \n \r \t \v \\]")
    if  s  !=   r"[\134 \' \" \a \b \f \n \r \t \v \134]" :
        s   =   "c_string: " + repr(s)
        raise ValueError(s)


    s       =   c_ctrl_esc("[\\ [null\0null] [1f\x1f1f] [at@at] [dollar$dollar] [percent%percent] \' \" \a \b \f \n \r \t \v \\]")
    if  s  !=   r"[\134 [null\000null] [1f\0371f] [at\100at] [dollar\044dollar] [percent\045percent] \' \" \a \b \f \n \r \t \v \134]" :
        s   =   "c_ctrl_esc: " + repr(s)
        raise ValueError(s)


    s       =   lf_only("\r\r\r\n\n\tnow\t\r\nis\r the \n\r\ntime  \r\r\nfor")
    if  s  !=   "\n\n\tnow\t\nis\n the \n\ntime  \nfor" :
        s   =   "lf_only: " + c_string(s)
        raise ValueError(s)

    s       =   lf_only_with_no_trailing_white_space("   \n   blah   \n lkjsd    \f   \t   \n lkjsdf  \t")
    if  s  !=   "\n   blah\n lkjsd\n lkjsdf\n" :
        s   =   "lf_only_with_no_trailing_white_space: " + c_string(s)
        raise ValueError(s)

    s       =   lf_only_with_no_trailing_white_space("   \n   blah   \n lkjsd    \f   \t   \n lkjsdf  \t\n")
    if  s  !=   "\n   blah\n lkjsd\n lkjsdf\n" :
        s   =   "lf_only_with_no_trailing_white_space: " + c_string(s)
        raise ValueError(s)

    s       =   no_blank_lines("\r\r\r\n\n\tnow\t\r\nis\r the \n\r\ntime  \r\r\nfor")
    if  s  !=   "\tnow\nis\n the\ntime\nfor\n" :
        s   =   "no_blank_lines: " + c_string(s)
        raise ValueError(s)


    s       =   multiline_strip("  x \r\n\t \tx \r  \t x\nbla   \t   x \n test \n")
    if  s  !=   "x\nx\nx\nbla   \t   x\ntest\n" :
        s   =   "multiline_strip: " + c_string(s)
        raise ValueError(s)

    s       =   multiline_strip("x \r\n\t \tx \r  \t x\nbla   \t   x \n test ")
    if  s  !=   "x\nx\nx\nbla   \t   x\ntest" :
        s   =   "multiline_strip: " + c_string(s)
        raise ValueError(s)

    s       =   multiline_strip("\r  x \r\n\t \tx \r  \t x\nbla   \t   x \n test ")
    if  s  !=   "\nx\nx\nx\nbla   \t   x\ntest" :
        s   =   "multiline_strip: " + c_string(s)
        raise ValueError(s)

    s       =   multiline_strip("  \r  x \r\n\t \tx \r  \t x\nbla   \t   x \n test ")
    if  s  !=   "\nx\nx\nx\nbla   \t   x\ntest" :
        s   =   "multiline_strip: " + c_string(s)
        raise ValueError(s)

    s       =   multiline_strip("  \r  xZ \r\n\tZ \tx \r  \t x\nbla   \t   x \n test ", " \tZ")
    if  s  !=   "\nx\nx\nx\nbla   \t   x\ntest" :
        s   =   "multiline_strip: " + c_string(s)
        raise ValueError(s)


    s       =   multiline_flush_left("   \r   xZ \r\n  \tZ \tx \r  \t x \r\n    bla   \t   x \r\n\r\n      test ")
    if  s  !=   " \r   xZ\n\tZ \tx \r  \t x\n  bla   \t   x\n\n    test" :
        s   =   "multiline_flush_left: [%s]" % c_string(s)
        raise ValueError(s)


    s       =   " now is the time "
    cs      =   maybe_wrap_with_cdata(s)
    if  s  != cs :
        raise ValueError("gratuitous cdata")

    s       =   " now is the <time "
    cs      =   maybe_wrap_with_cdata(s)
    if  s  == cs :
        raise ValueError("Missed cdata <")

    s       =   " now is the &time "
    cs      =   maybe_wrap_with_cdata(s)
    if  s  == cs :
        raise ValueError("Missed cdata &")

    s       =   " now is the \ntime "
    cs      =   maybe_wrap_with_cdata(s)
    if  s  == cs :
        raise ValueError("Missed cdata \\n")

    s       =   " now is the \rtime "
    cs      =   maybe_wrap_with_cdata(s)
    if  s  == cs :
        raise ValueError("Missed cdata \\r")

    s       =   "<![CDATA[ now is the &time ]]>"
    cs      =   maybe_wrap_with_cdata(s)
    if  s  != cs :
        raise ValueError("Double cdata")

    s       =   " <![CDATA[ now is the &time ]]>"
    cs      =   maybe_wrap_with_cdata(s)
    if  s  == cs :
        raise ValueError("Missed imperfect cdata")
    if  cs !=   "<![CDATA[ <![CDATA[ now is the &time &#93;&#93;&gt;]]>" :
        raise ValueError("Missed included cdata data [%s]" % ( cs ))

    if  s_except_1(1) == 's' :
        raise ValueError("s_except_1 for 1")
    if  s_except_1(1.0) == 's' :
        raise ValueError("s_except_1 for 1")
    if  s_except_1(0) != 's' :
        raise ValueError("s_except_1 for 0")
    if  s_except_1(2) != 's' :
        raise ValueError("s_except_1 for 2")
    if  s_except_1(-1) != 's' :
        raise ValueError("s_except_1 for -1")


    s   = u"a L1 x91 \u0091".encode('latin1')
    cs  = convert_to_unicode(s)
    if  cs != u"a L1 x91 \u0091" :
        raise ValueError("convert_to_unicode of %s is %s" % ( repr(s), repr(cs) ) )

    s   = u"a L1 D-cross \u00d0 e-umlaut \u00eb".encode('latin1')
    cs  = convert_to_unicode(s)
    if  cs != u"a L1 D-cross \u00d0 e-umlaut \u00eb" :
        raise ValueError("convert_to_unicode of %s is %s" % ( repr(s), repr(cs) ) )

    s   = u"a utf8 L1 C-ish char \u00c7 - a C-ish".encode('utf8')
    cs  = convert_to_unicode(s)
    if  cs != u"a utf8 L1 C-ish char \u00c7 - a C-ish" :
        raise ValueError("convert_to_unicode of %s is %s" % ( repr(s), repr(cs) ) )

    s   = u"a utf8 U chars \u0189 \ua72a - D and E-ish".encode('utf8')
    cs  = convert_to_unicode(s)
    if  cs != u"a utf8 U chars \u0189 \ua72a - D and E-ish" :
        raise ValueError("convert_to_unicode of %s is %s" % ( repr(s), repr(cs) ) )


    s   = "abcdx&* \0\033\010\015\012\x7f\t"
    cs  = best_ascii(s)
    if  cs != "abcdx&* \0\033\010\015\012\x7f\t" :
        raise ValueError("best_ascio of %s is %s" % ( repr(s), repr(cs) ) )

    s   = "a\xc1\xe1b"
    cs  = best_ascii(s)
    if  cs != "aAab" :
        raise ValueError("best_ascio of %s is %s" % ( repr(s), repr(cs) ) )

    s   = "o\xc2\xba\xc3\xb2\xc3\xb3\xc3\xb4\xc3\xb5\xc3\xb6\xc3\xb8\xc5\x93 9{"
    cs  = best_ascii(s)
    if  cs != u'ooooooo\xf8\u0153 9{' :
        raise ValueError("best_ascio of %s is %s" % ( repr(s), repr(cs) ) )


    s   = ""
    cs  = de_dupe_str(s)
    if  cs != s :
        raise ValueError("de_dupe_str of %s is %s" % ( repr(s), repr(cs) ) )

    s   = "abc"
    cs  = de_dupe_str(s)
    if  cs != s :
        raise ValueError("de_dupe_str of %s is %s" % ( repr(s), repr(cs) ) )

    s   = "aabbbccabccc"
    cs  = de_dupe_str(s)
    if  cs != "abc" :
        raise ValueError("de_dupe_str of %s is %s" % ( repr(s), repr(cs) ) )

    s   = "dabababcabcabc"
    cs  = de_dupe_str(s)
    if  cs != "dabc" :
        raise ValueError("de_dupe_str of %s is %s" % ( repr(s), repr(cs) ) )

    s   = u"ab\u1234\u5678\u1234"
    cs  = de_dupe_str(s)
    if  cs != u"ab\u1234\u5678" :
        raise ValueError("de_dupe_str of %s is %s" % ( repr(s), repr(cs) ) )


    if  cmp_str_with_ints('abc23', 'abc219') >= 0 :
        raise ValueError("cmp_str_with_ints abc32 abc219")

    if  cmp_str_with_ints('abc00532', 'abc51') <= 0 :
        raise ValueError("cmp_str_with_ints abc00532 51")

    if  cmp_str_with_ints('abc32A', 'abc32a') >= 0 :
        raise ValueError("cmp_str_with_ints abc32A abc32a")

    if  cmp_lower_str_with_ints('abc32A', 'abc32a') != 0 :
        raise ValueError("cmp_lower_str_with_ints abc32A abc32a")

    if  cmp_lower_str_with_ints('abc32Z', 'abc32a') <= 0 :
        raise ValueError("cmp_lower_str_with_ints abc32Z abc32a")

    a   = [ [ str_diff_distance("test", sdd), sdd ] for sdd in [ 'wonejtsllf s', 'tes', 'stcover', ] ]
    r   = min(a)
    if  r[1] != 'tes' :
        raise ValueError("str_diff_distance() not 'tes' is %s in %s" % ( r[1], a, ))

    r   = binary_search([ 3, 6, 9, 12, 12, 13 ], 12)
    if  r != 3 :
        raise ValueError("binary_search(12) not 3 is %d" % r)

    r   = binary_search([ 3, 6, 9, 10, 12, 13, 14 ], 10)
    if  r != 3 :
        raise ValueError("binary_search(10) not 3 is %d" % r)

    r   = binary_search([ 3, 6, 9, 10, 12, 13, 14 ], 11)
    if  r != 4 :
        raise ValueError("binary_search(11) not 4 is %d" % r)

    r   = binary_search([ 3, 6, 9, 10, 12, 13, 14 ], 110)
    if  r != 7 :
        raise ValueError("binary_search(110) not 7 is %d" % r)

    r   = binary_search([ 3, 6, 9, 10, 12, 13, 14 ], -1)
    if  r != 0 :
        raise ValueError("binary_search(-1) not 0 is %d" % r)

    r   = max_index([ 1, 2, 5, -1, 0 ])
    if  r != 2 :
        raise ValueError("max_index() not 2 is %d" % r)

    r   = max_index([])
    if  r != -1 :
        raise ValueError("max_index of [] not -1")

    r   = min_index([ 1, 2, 5, -1, 0 ])
    if  r != 3 :
        raise ValueError("min_index() not 3 is %d" % r)

    r   = min_index([])
    if  r != -1 :
        raise ValueError("min_index of [] not -1")


    for vv in [ 1/2.0, 3/4.0, 2/3.0, 66/100.0, 1/3.0, 1/7.0, 1/5.0, 14/100.0, 3/10.0, 12/10.0, 1.0 / math.pi, math.pi, 5/1.0, -3/10.0, -25/10.0, ] :
        n, d    = as_integer_ratio(vv)
        if  abs(vv - n / d) > .00000001 :
            raise ValueError("as_integer_ratio(%s) is %s/%s" % ( vv, n, d, ))
        pass


    for a, e, m in [
                    [ [ -3, 1, 5, ],      1, None ],
                    [ [ -3, -2, 5, ],    -2, None ],
                    [ [ -2, -3, 0, ],    -2, None ],
                    [ [  0,  3, 2, ],     2, None ],
                    [ [ ],             None, None ],
                    [ [  3,  2, 5, ],     3, None ],
                    [ [  3,  2, 4, 5, ],  3.5, None ],
                    [ [  3,  2, 5, 5, ],  4,   None ],
                    [ [  9,  2, 5, 3, ],  4,   None ],
                    [ [  9, ],            9,   0.1234 ],
                    [ [  9, ],            9,   None ],
                    [ [  1, 2, 3, 4, 5, 6, 7, 8, 9,    ],  5,   0.5     ],
                    [ [  1, 2, 3, 4, 5, 6, 7, 8, 9     ],  3,   0.25    ],
                    [ [  1, 2, 3, 4, 5, 6, 7, 8, 9,    ],  7,   0.75    ],
                    [ [  1, 2, 3, 4, 5, 6, 7,          ],  3,   1/3.0   ],
                    [ [  1, 2, 3, 4, 5, 6, 7, 8,       ],  3 + 1/3.0, 1/3.0   ],
                    [ [  1, 2, 3, 4, 5, 6, 7, 8, 9,    ],  3 + 2/3.0, 1/3.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  3,         1/3.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  4.5, 0.5     ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  2.25,   0.25    ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  6.75,   0.75    ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  6,   2/3.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  3,   1/3.0   ],
                    [ [  0, 1, 2, 3, 4, 5,             ],  1,   1/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5,             ],  2,   2/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5,             ],  3,   3/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5,             ],  4,   4/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6,          ],  1.2,   1/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6,          ],  2.4,   2/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6,          ],  3.6,   3/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6,          ],  4.8,   4/5.0   ],

                    [ [  0, 1, 2, 3, 4, 5, 6, 7,       ],  1.4,   1/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7,       ],  2.8,   2/5.0   ],

                    [ [  0, 1, 2, 3, 4, 5, 6, 7,       ],  4.2,   3/5.0   ],

                    [ [  0, 1, 2, 3, 4, 5, 6, 7,       ],  5.6,   4/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8,    ],  1.6,   1/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8,    ],  3.2,   2/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8,    ],  4.8,   3/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8,    ],  6.4,   4/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  1.8,   1/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  3.6,   2/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  5.4,   3/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],  7.2,   4/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ],  2,     1/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ],  4,     2/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ],  6,     3/5.0   ],
                    [ [  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ],  8,     4/5.0   ],
                    [ [  1, -3, 5, ],     -3, 0 ],
                    [ [ -3,  5, 1, ],      5, 1 ],
                    [ [ [  8,  5, 1, ], [ 2, 3, 4, [ 5, 6, 7, ], 9, ], ], 5, None ],
                ] :
        # import  numpy
        # r       = median(numpy.array(a), middle = m)      # note: comment the non-1D array at the end of the test items
        r       = median(a, middle = m)
        if  ((r is None) != (e is None)) or ((r is not None) and (abs(r - e) > 0.0000000000001)) :
            raise ValueError("median(%s) of %s is %s, not %s as it should be" % ( str(m), str(a), str(r), str(e), ))
        pass

    a   = [ 'a9', 'a10', 'a 5a', 'a 5A', 'a 50', 'aCc', 'a 1.5', 'a 1.11', 'a .118', 'a .11', 'a.0122', 'a -10', 'a -9', 'a20.10', 'a200', 'a20.1', 'a20.10', 'a -.11', 'a -.111', "abc", "aBc", "abC", "Abc", "ABc", "AbC", "ABC", "ABCD", "abcd", "acc", ]
    b   = sorted_numerically(a)
    c   = ['ABC', 'ABCD', 'ABc', 'AbC', 'Abc', 'a -.11', 'a -.111', 'a -9', 'a -10', 'a .11', 'a .118', 'a 1.11', 'a 1.5', 'a 5A', 'a 5a', 'a 50', 'a.0122', 'a9', 'a10', 'a20.1', 'a20.10', 'a20.10', 'a200', 'aBc', 'aCc', 'abC', 'abc', 'abcd', 'acc']
    if  b != c :
        raise ValueError("sorted_numerically of\n %s is\n %s should be\n %s" % ( a, b, c, ))
    b   = sorted_numerically(a, cmp_rtn = cmp_lower)
    c   = ['a -.11', 'a -.111', 'a -9', 'a -10', 'a .11', 'a .118', 'a 1.11', 'a 1.5', 'a 5A', 'a 5a', 'a 50', 'a.0122', 'a9', 'a10', 'a20.1', 'a20.10', 'a20.10', 'a200', 'ABC', 'ABc', 'AbC', 'Abc', 'aBc', 'abC', 'abc', 'ABCD', 'abcd', 'aCc', 'acc']
    if  b != c :
        raise ValueError("sorted_numerically lowercase of\n %s is\n %s should be\n %s" % ( a, b, c, ))


    r   = west_valley([ 1, 2, 3, 2, 1 ], 2)
    if  r != 0 :
        raise ValueError("west_valley not 0")

    r   = west_valley([ 3, 2, 2, 3, 2, 1, 1, 2 ], 3)
    if  r != 1 :
        raise ValueError("west_valley not 1")

    r   = east_valley([ 1, 2, 3, 2, 1 ], 2)
    if  r != 4 :
        raise ValueError("east_valley not 4")

    r   = east_valley([ 3, 2, 2, 3, 2, 1, 1, 2 ], 3)
    if  r != 6 :
        raise ValueError("east_valley not 6")


    for a, xywh, sm, avg in [
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 1, 1, 2, 2, ],
                                    28,
                                    7,
                                ],
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 0, 0, 1, 1, ],
                                    1,
                                    1,
                                ],
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 0, 0, 0, 1, ],
                                    0,
                                    None,
                                ],
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 0, 0, 1, 0, ],
                                    0,
                                    None,
                                ],
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 0, 0, -1, 1, ],
                                    0,
                                    None,
                                ],
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 0, 0, 1, -1, ],
                                    0,
                                    None,
                                ],
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 0, 0, 10, 1, ],
                                    6,
                                    2,
                                ],
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 0, 0, 1, 10, ],
                                    12,
                                    4,
                                ],
                                [
                                    [
                                        [ 1, 2, 3, ],
                                        [ 4, 5, 6, ],
                                        [ 7, 8, 9, ],
                                    ],
                                    [ 0, 0, 10, 10, ],
                                    45,
                                    5,
                                ],
                            ] :
        cs  = cumsum(a)

        r   = cumsum_sum(cs, xywh[0], xywh[1], xywh[2], xywh[3])
        if  r != sm :
            raise ValueError("cumsum_sum     of %s in %s is %u, should be %u" % ( repr(xywh), repr(a), r, sm, ))

        r   = cumsum_average(cs, xywh[0], xywh[1], xywh[2], xywh[3])
        if  r != avg :
            raise ValueError("cumsum_average of %s in %s is %u, should be %u" % ( repr(xywh), repr(a), r, avg, ))

        if  True   :
            cs  = cumsum(numpy.array(a))

            r   = cumsum_sum(cs, xywh[0], xywh[1], xywh[2], xywh[3])
            if  r != sm :
                raise ValueError("cumsum_sum     of %s in %s is %u, should be %u" % ( repr(xywh), repr(a), r, sm, ))

            pass

        pass



    # print "tfn", temp_file_name()
    # print "tfn", temp_file_name()

    if  excel_column_name(0) != 'a' :
        raise ValueError("Excel column name 0 is %s!" % str(excel_column_name(0)))
    if  excel_column_name(1) != 'b' :
        raise ValueError("Excel column name 1 is %s!" % str(excel_column_name(1)))
    if  excel_column_name(26) != 'aa' :
        raise ValueError("Excel column name 26 is %s!" % str(excel_column_name(26)))
    if  excel_column_name(53) != 'bb' :
        raise ValueError("Excel column name 53 is %s!" % str(excel_column_name(53)))
    if  excel_column_name(676) != 'za' :
        raise ValueError("Excel column name 676 is %s!" % str(excel_column_name(676)))
    if  excel_column_name(701) != 'zz' :
        raise ValueError("Excel column name 701 is %s!" % str(excel_column_name(701)))


    a   = list_lstrip([ "bad", "bad", "good", "bad"], "bad")
    if  len(a) != 2 :
        raise ValueError("list_lstrip: 3 "  + str(a))
    a   = list_lstrip([ "bad", "bad", "good", "bad"], "good")
    if  len(a) != 4 :
        raise ValueError("list_lstrip: 4 "  + str(a))
    a   = list_lstrip([ "bad", "bad", "good", "bad"], [ "bad" ])
    if  len(a) != 2 :
        raise ValueError("list_lstrip: 2a " + str(a))
    a   = list_lstrip([ "", "", "", "good"], [ "" ])
    if  len(a) != 1 :
        raise ValueError("list_lstrip: 1a " + str(a))
    a   = list_lstrip([ "", "", "", "good"], "")
    if  len(a) != 1 :
        raise ValueError("list_lstrip: 1 "  + str(a))
    a   = list_lstrip([ 2, 2, 2 ], 2)
    if  len(a) != 0 :
        raise ValueError("list_lstrip: 0 "  + str(a))

    s   = de_html_str(u"<HTML> <BODY>\n\r\n\n\r\r\nText  on  a\tline<BR>after <script>scripting</script>break</P attrib='lksjdflkjsldkjflsjdlfkjsldjfljsdf'> after paragraph <HR><script >and more scripting</script ><HR> \xd0 &amp; &lt;DIR&gt; after angled DIR <DIR>after DIR</LI>After end li")
    cs  = unicode("\nText on a line\nafter break\n\nafter paragraph\n----------------------------------------\n\n----------------------------------------\n\xd0 & <DIR> after angled DIR\nafter DIR\nAfter end li", 'latin1')
    if  s != cs :

        print len(s), len(cs)

        def cstr(c) :
            if  (ord(c) >= 32) and (ord(c) < 0x7f) :
                return(c)
            return("|" + str(ord(c)) + "|")

        for i in xrange(len(s)) :
            if  s[i] != cs[i] :

                print i, cstr(s[i]), cstr(cs[i])
                break
            pass

        s = "de_html_str problem: [" + "".join([ cstr(s[i]) for i in xrange(len(s)) ]) + "]"
        raise ValueError(s)


    t   = elapsed_time()
    print "%20.10f %20.10f" % ( t, t - ctm )

    ctm = t
    time.sleep(0.2)

    t   = elapsed_time()
    dt  = t - ctm
    print "%20.10f %20.10f" % ( t, dt )
    if  not (0.19999 <= dt <= 0.25) :
        raise ValueError('elapsed_time of sleep(.02) was %s' % str(dt))

    ta  = [
            [   1.23456      +  2 * 60 +  3 * 60 * 60 + 4 * 60 * 60 * 24 + 567 * 60 * 60 * 24 * 7, "3973:03:02:01.23456", "567:4:03:02:01.23456"    ],
            [   0            +  0 * 60 +  0 * 60 * 60 + 0 * 60 * 60 *  0 + 567 * 60 * 60 * 24 * 7, "3969:00:00:00"      , "567:0:00:00:00"          ],
            [   1            +  2 * 60 +  3 * 60 * 60 + 4 * 60 * 60 * 24 + 567 * 60 * 60 * 24 * 7, "3973:03:02:01"      , "567:4:03:02:01"          ],
            [   50           +  2 * 60 +  3 * 60 * 60 + 4 * 60 * 60 * 24 + 567 * 60 * 60 * 24 * 7, "3973:03:02:50"      , "567:4:03:02:50"          ],
            [   1.0000000001 + 59 * 60 + 23 * 60 * 60 + 1 * 60 * 60 * 24                         , "1:23:59:01"         , "1:23:59:01"              ],
            [   1.0000000001 + 59 * 60 + 23 * 60 * 60                                            , "23:59:01"           , "23:59:01"                ],
            [   1.0000000001 + 59 * 60                                                           , "59:01"              , "59:01"                   ],
            [   59           + 59 * 60                                                           , "59:59"              , "59:59"                   ],
            [   59.9999                                                                          , "59.9999"            , "59.9999"                 ],
            [   59.9999      + 1  * 60                                                           , "1:59.9999"          , "1:59.9999"               ],
            [   .5                                                                               , ".5"                 , ".5"                      ],
            [   .05                                                                              , ".05"                , ".05"                     ],
           ]

    for ti, tt in enumerate(ta) :
        s   = wdhms_str(tt[0])
        if  s != tt[1] :
            raise ValueError("wdhms_str of %s[%u] is %s not %s" % (tt[0], ti, s, tt[1] ) )
        s   = wdhms_str(tt[0], weeks = True)
        if  s != tt[2] :
            raise ValueError("wdhms_str(weeks) of %s[%u] is %s not %s" % (tt[0], ti, s, tt[2] ) )
        pass


    d   = same_time_ish(1000000000, 1000000000 + 7.9 * 3600)
    if  not d :
        s   = "same_time_ish of 7.9 hours is different 'time'"
        raise ValueError(s)

    d   = same_time_ish(1000000000, 1000000000 + 14.1 * 3600)
    if  d   :
        s   = "same_time_ish of 14.1 hours is same 'time'"
        raise ValueError(s)


    d   = inside_daytime_window([ [ 100, 200, ], [ 2300, 2700, ], ], now = 2300)
    if  not d :
        raise ValueError("2300 is not inside_daytime_window()")

    d   = inside_daytime_window([ [ 100, 200, ], [ 2300, 2700, ], ], now = 2700)
    if  d :
        raise ValueError("2700 is inside_daytime_window()")

    d   = inside_daytime_window([ [ 0, 200, ], [ 2300, 2700, ], ], now = 0)
    if  not d :
        raise ValueError("0 is not inside_daytime_window()")

    d   = inside_daytime_window([ [ 0, 0.1, ], ], now = 0)
    if  not d :
        raise ValueError("0 is not inside_daytime_window()")

    d   = inside_daytime_window([ [ 24 * 3600 + 1, 24 * 3600 + 100, ], ], now = 50)
    if  d :
        raise ValueError("50 is out-of-day inside_daytime_window()")

    d   = inside_daytime_window([ [ 23 * 3600, 2 * 3600, ], ], now = 23 * 3600 + 1800)
    if  not d :
        raise ValueError("11:30pm is not inside_daytime_window() spanning midnight")

    twins   = [ [ 0, 9 * 3600, ], [ 17 * 3600, 24 * 3600, ] ]
    print "Working 9 to 5: %s" % str(not inside_daytime_window(twins))


    d   = find_upper_dir("tzpython")
    if  os.path.split(d)[1] != "tzpython" :
        s   = "Could not find this or parent 'tzpython' directory [%s]" % ( str(d) )
        raise ValueError(s)

    d   = find_upper_dir("blahblahblahblah")
    if  os.path.split(d)[1] == "blahblahblahblah" :
        s   = "Found this or parent 'blahblahblahblah' directory [%s]" % ( str(d) )
        raise ValueError(s)
    if  d :
        s   = "Found this or parent 'blahblahblahblah' directory [%s]" % ( str(d) )
        raise ValueError(s)


    d   = find_upper_file_or_dir("tzpython/tzlib.py")
    if  os.path.split(d)[1] != "tzlib.py" :
        s   = "Could not find this or parent 'tzpython' directory [%s]" % ( str(d) )
        raise ValueError(s)

    d   = find_upper_file_or_dir("tzpython/blahblah.blah")
    if  d   :
        s   = "Found tzpython/blahblah.blah [%s]" % ( str(d) )
        raise ValueError(s)


    for v, r in [
                    [ 0b0100, 0b0101 ],
                    [ 0b0101, 0b0111 ],
                    [ 0b0111, 0b1111 ],
                    [ 0b1001, 0b1011 ],
                ] :
        a   = raise_left_zero_bit(v)
        if  a != r :
            raise ValueError("raise_left_zero_bit(%s) == %s, not %s" % ( bin(v), bin(a), bin(r), ))
        pass


    for v, r in [
                    [ 0b1001, 0b0010 ],
                    [ 0b0111, 0b1000 ],
                    [ 0b1011, 0b0100 ],
                ] :
        a   = only_right_zero_bit(v)
        if  a != r :
            raise ValueError("only_right_zero_bit(%s) == %s, not %s" % ( bin(v), bin(a), bin(r), ))
        pass


    for v, r in [
                    [ 0b0100, 0b0000 ],
                    [ 0b0101, 0b0100 ],
                    [ 0b0111, 0b0110 ],
                    [ 0b0110, 0b0100 ],
                ] :
        a   = lower_left_bit(v)
        if  a != r :
            raise ValueError("lower_left_bit(%s) == %s, not %s" % ( bin(v), bin(a), bin(r), ))
        pass


    for v, r in [
                    [ 0b0100, 0b0111 ],
                    [ 0b0101, 0b0101 ],
                ] :
        a   = raise_all_right_xero_bits(v)
        if  a != r :
            raise ValueError("raise_all_right_xero_bits(%s) == %u, not %u" % ( hex(v), a, r, ))
        pass


    for v, r in [
                    [ 0b0000, 0 ],
                    [ 0b0100, 1 ],
                    [ 0b0101, 2 ],
                    [ 0b0111, 3 ],
                    [ 0b1010, 2 ],
                    [ 0xfffe, 15 ],
                    [ 0xfffb, 15 ],
                    [ 0xefff, 15 ],
                    [ 0xffffFFFF, 32 ],
                    [ 0x1ffffFFFFffffFFFF, 65 ],
                    [ 0x10000000000000000, 1 ],
                ] :
        a   = bit_count(v)
        if  a != r :
            raise ValueError("bit_count(%s) == %u, not %u" % ( hex(v), a, r, ))
        pass


    for v, r in [
                [ 4, 3 ],
                [ 5, 3 ],
                [ 7, 3 ],
                [ 1, 1 ],
                [ 0, 0 ],
                [ -4, 3 ],
                [ -5, 3 ],
                [ -7, 3 ],
                [ -1, 1 ],
                [ -0, 0 ],
                [ 0x700000000, 35 ],
                [ 0x80000000,  32 ],
                [ 0x8000000000000000,  64 ],
                [ 0x10000000000000000,  65 ],
                [ 0x30000000000000000,  66 ],
                [ 0x40000000,  31 ],
              ] :
        a   = bit_length(v)
        if  a != r :
            raise ValueError("bit_length(%s) == %u, not %u" % ( hex(v), a, r, ))
        pass


    for v, r, u in [
                    [ 0, 0, 0 ],
                    [ 1, 1, 1 ],
                    [ 3, 2, 2 ],
                    [ 2, 3, 3 ],
                    [ 6, 4, 4 ],
                    [ 7, 5, 0x0b ],
                    [ 5, 6, 0x0c ],
                    [ 4, 7, 0x0d ],
                    [ 0xc, 8, 0x0e ],
                    [ 0x8, 0xf, 0xf ],
                    [ 0xfe, 0xab, 0x5d ],
                    [ 0xff, 0xaa, 0xbc ],
                    [ 0x8000, 0xffff, None ],
                    [ 0xfffe, 0xaaab, None ],
                    [ 0x010000000, 0x1fffFFFF,  None ],
                    [ 0x0ffffFFFF, 0x0aaaaAAAA, None ],
                    [ 0x100000000, 0x1ffffFFFF, None ],
                    [ 0x200000000, 0x3ffffFFFF, None ],
                    [ 0x23456789a, 0x3d86450ec, None ],
                    [  1 <<  893,                       0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffL, None ],
                    [ (1 << 1279) - 0xfedcab87564321a,  0x55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555fe3d9850ced76bbL, None ]
                ] :
        a   = un_gray_code(v)
        if  a != r :
            raise ValueError("un_gray_code(%s) == %s, not %s" % ( hex(v), hex(a), hex(r), ))
        r   = gray_code(a)
        if  r != v :
            raise ValueError("gray_code(%s) == %s, not %s" % ( hex(a), hex(r), hex(v), ))
        if  0 <= v < 256 :
            a   = balanced_8_bit_un_gray_code(v)
            if  a != u :
                raise ValueError("balanced_8_bit_un_gray_code(%s) == %s, not %s" % ( hex(v), hex(a), hex(u), ))
            r   = balanced_8_bit_gray_code(a)
            if  r != v :
                raise ValueError("balanced_8_bit_gray_code(%s) == %s, not %s" % ( hex(a), hex(r), hex(v), ))
            pass
        pass
    cnts    = [ 0 ] * 9
    pc      = 0x80
    for n in xrange(256) :
        gc  = balanced_8_bit_gray_code(n)
        bd  = gc ^ pc
        if  bit_count(bd) != 1  :
            raise ValueError("balanced_8_bit_gray_code(%s) == %s is not gray compared to previous code %s -> %s has %u bits" % ( hex(n), hex(gc), hex(pc), hex(bd), bit_count(bd), ))
        cnts[bit_length(bd)]   += 1
        pc  = gc
        nn  = balanced_8_bit_un_gray_code(gc)
        if  nn != n :
            raise ValueError("round trip balanced_8_bit_un_gray_code(balanced_8_bit_gray_code(%s)) == %s, bgc == %s" % ( hex(n), hex(nn), hex(gc), ))
        pass
    cnts    = cnts[1:]
    cnts.sort()
    if  cnts[0] != cnts[-1] :
        raise ValueError("balanced_8_bit_gray_code table is not balanced %s" % str(cnts))


    r   = ("%11.8f " * 6) % (    tanh(- 2),    tanh(0),    tanh( 2),    tanh_delta(- 2),    tanh_delta(0),    tanh_delta( 2), )
    s   = "-0.96402758  0.00000000  0.96402758  0.07065082  1.00000000  0.07065082 "
    if  r != s :
        raise ValueError("Something is wrong with tanh.\n%s is not\n%s !" % ( r, s, ))
    r   = ("%11.8f " * 6) % ( sigmoid(-10), sigmoid(0), sigmoid(10), sigmoid_delta(-10), sigmoid_delta(0), sigmoid_delta(10), )
    s   = " 0.00004540  0.50000000  0.99995460  0.00004540  0.25000000  0.00004540 "
    if  r != s :
        raise ValueError("Something is wrong with sigmoid.\n%s is not\n%s !" % ( r, s, ))



    for v, e in [
                    [ ( 2, 3 ),         [ 0, 0, 0, 1, 0, 1, 1, 1, ]    ],
                    [ ( "abcd", 2 ),    "aabacadbbcbdccdd"          ],
                ] :
        r   = de_bruijn(*v)
        if  r != e :
            raise ValueError("de_bruijn of %s not %s, but is %s" % ( v, e, r, ))
        pass


    for WH in [ 2, 4, 8, 64 ] :
        xys     = {}
        for i in xrange(0, WH * WH) :
            xy  = square_hilbert_i_to_xy(WH, i)
            if  str(xy) in xys :
                s   = "Hilbert curve index %u in duplicated at %s" %  ( i, str(xy), )
                raise ValueError(s)
            xys[str(xy)]    = True
            j   = square_hilbert_xy_to_i(WH, xy[0], xy[1])
            if  i != j :
                s   = "Hilbert curve index %u out not same as in %u at %s" %  ( j, i, str(xy), )
                raise ValueError(s)
            if  str(j) in xys :
                s   = "Hilbert curve index %u out duplicated at %s" %  ( j, str(xy), )
                raise ValueError(s)
            xys[str(j)] = True
            # print i, j, xy
        pass


    v   = make_2D_starburst(rings = 3)
    for p, r in zip(v, [
                        [ 1.0,  0.0],
                        [ 0.0,  1.0],
                        [-1.0,  0.0],
                        [ 0.0, -1.0],
                        [ 1.41421356237,  1.41421356237],
                        [-1.41421356237,  1.41421356237],
                        [-1.41421356237, -1.41421356237],
                        [ 1.41421356237, -1.41421356237],
                        [ 0.0,  3.0],
                        [-3.0,  0.0],
                        [ 0.0, -3.0],
                       ]
                   )   :
        if  (abs(p[0] - r[0]) >= 0.00000000001) or (abs(p[1] - r[1]) >= 0.0000000001) :
            s   = "make_2D_starburst wrong %s!=%s or %s!=%s" % ( repr(p[0]), repr(r[0]), repr(p[1]), repr(r[1]), )
            raise ValueError(s)
        pass


    for a, b, d in [
                    [  1, 99,  2 ],
                    [ 99,  2,  3 ],
                    [ 90, 40, 50 ],
                    [ 90, 41, 49 ],
                    [ 90, 39, 49 ],
                    [ 40, 90, 50 ],
                    [ 41, 90, 49 ],
                    [ 39, 90, 49 ],
                    [  0, 50, 50 ],
                    [ 50,  0, 50 ],
                    [  1, 52, 49 ],
                    [ 52,  1, 49 ],
                    [ 25, 26,  1 ],
                    [ 26, 23,  3 ],
                   ] :
        r   = torus_distance(a, b, 100)
        if  r != d :
            s   = "torus_distance(%u, %u, 100) != %u" % ( a, b, d, )
            raise ValueError(s)
        pass


    for w in [ 6, 7 ] :
        for frm in range(0, w) :
            for to in range(0, w) :
                r   = torus_goto_distance(frm, to, w)
                ar   = min(abs(frm - to), abs(frm + w - to), abs(frm - to - w))
                if  abs(r) != ar :
                    raise ValueError("torus_goto_distance in %u wide torus: abs(%d to %d = %d) is not %d" % ( w, frm, to, r, ar, ))
                pass
            pass
        pass

    for x, y, d in [
                        [  0,  1,   0 ],
                        [  4,  4,  45 ],
                        [  4,  0,  90 ],
                        [  4, -4, 135 ],
                        [  0, -4, 180 ],
                        [ -4, -4, 225 ],
                        [ -4,  0, 270 ],
                        [ -4,  4, 315 ],
                   ]    :
        a   = compass_angle(math.atan2(y, x))
    if  a != d :
        s   = "Wrong compass angle for %i, $i %i != %f!" % ( x, y, d, a, )
        raise ValueError(s)


    a   = math.degrees(radian_angle_difference(math.radians(0), math.radians(10)))
    if  not (-11 <= a <= -9) :
        s   = "radian(0 - 10) == %f" % a
        raise ValueError(s)

    a   = math.degrees(radian_angle_difference(math.radians(10), math.radians(1)))
    if  not (8 <= a <= 10) :
        s   = "radian(10 - 1) == %f" % a
        raise ValueError(s)

    a   = math.degrees(radian_angle_difference(math.radians(170), math.radians(-170)))
    if  not (-21 <= a <= -19) :
        s   = "radian(170 - -170) == %f" % a
        raise ValueError(s)

    a   = math.degrees(radian_angle_difference(math.radians(90), math.radians(-170)))
    if  not (-101 <= a <= -99) :
        s   = "radian(90 - -170) == %f" % a
        raise ValueError(s)

    a   = math.degrees(radian_angle_difference(math.radians(-170), math.radians(90)))
    if  not (99 <= a <= 101) :
        s   = "radian(-170 - 90) == %f" % a
        raise ValueError(s)

    a   = math.degrees(radian_angle_difference(math.radians(-170), math.radians(170)))
    if  not (19 <= a <= 21) :
        s   = "radian(-170 - 170) == %f" % a
        raise ValueError(s)

    a   = math.degrees(radian_angle_difference(math.radians(170), math.radians(10)))
    if  not (159 <= a <= 161) :
        s   = "radian(170 - 10) == %f" % a
        raise ValueError(s)

    a   = math.degrees(radian_angle_difference(math.radians(170), math.radians(100)))
    if  not (69 <= a <= 71) :
        s   = "radian(170 - 100) == %f" % a
        raise ValueError(s)

    a   = math.degrees(radian_angle_difference(math.radians(100), math.radians(170)))
    if  not (-71 <= a <= -69) :
        s   = "radian(100 - 170) == %f" % a
        raise ValueError(s)


    for ang, rv in  [
                        [  100, 80  ],
                        [  -10, 10  ],
                        [  -80, 80  ],
                        [ -269, 89  ],
                        [  269, 89  ],
                        [   19, 19  ],
                        [  219, 39  ],
                        [    0,  0  ],
                        [  180,  0  ],
                        [  179,  1  ],
                        [ -179,  1  ],
                        [  181,  1  ],
                        [ -181,  1  ],
                    ] :
        a   = math.degrees(radian_angle_from_horizontal(math.radians(ang)))
        if  (rv < 0) or (abs(a - rv) > 0.00000001) :
            s   = "from_hz(%u) == %f" % ( ang, rv, )
            raise ValueError(s)
        pass


    aa  = None
    a   = average_angle(aa)
    if  a is not None :
        raise ValueError("average_angle of %s is %s not None" % ( str(aa), str(a), ))
    aa  = []
    a   = average_angle([ math.radians(a) for a in aa ])
    if  a != 0.0 :
        raise ValueError("average_angle of %s is %s not 0.0" % ( str(aa), str(a), ))
    aa  = [ 0 ]
    a   = average_angle([ math.radians(a) for a in aa ])
    if  a != 0.0 :
        raise ValueError("average_angle of %s is %s not 0.0" % ( str(aa), str(a), ))
    aa  = [ 0, 10, 350 ]
    a   = average_angle([ math.radians(a) for a in aa ])
    if  abs(radian_angle_difference(a, 0.0)) >= 0.000000001 :
        raise ValueError("average_angle of %s is %s not 0.0" % ( str(aa), str(a), ))
    aa  = [ 0, 180 ]
    a   = average_angle([ math.radians(a) for a in aa ])
    if  a != 0.0 :                  # radian_angle_difference(a, math.radians(90)) >= 0.000000000000001 :
        raise ValueError("average_angle of %s is %s not 0.0" % ( str(aa), str(a), ))
    aa  = [ 0, 180, 270 ]
    a   = average_angle([ math.radians(a) for a in aa ])
    if  a != math.radians(270) :    # radian_angle_difference(a, math.radians(270)) >= 0.00000000000001 :
        raise ValueError("average_angle of %s is %s not 270.0" % ( str(aa), str(math.degrees(a)), ))
    aa  = [ 90, 180, 270 ]
    a   = average_angle([ math.radians(a) for a in aa ])
    if  a != math.pi :              # radian_angle_difference(a, math.pi) >= 0.0000000000000001 :
        raise ValueError("average_angle of %s is %s not pi" % ( str(aa), str(math.degrees(a)), ))
    aa  = [ 0, 90, 180, 270 ]
    a   = average_angle([ math.radians(a) for a in aa ])
    if  a != 0.0 :                  # radian_angle_difference(a, math.radians(0)) >= 0.00000000000000000001 :
        raise ValueError("average_angle of %s is %s not 0.0" % ( str(aa), str(math.degrees(a)), ))
    aa  = [ 320, 330, 340, 350, 0, ]
    a   = average_angle([ math.radians(a) for a in aa ])
    if  abs(radian_angle_difference(a, math.radians(340)))     >= 0.000000000000001 :
        raise ValueError("average_angle of %s is %s not 340.0" % ( str(aa), str(math.degrees(a)), ))
    aa  = [ 320, 330, 340, 350, 0, 10, ]
    a   = average_angle([ math.radians(a) for a in aa ])
    if  abs(radian_angle_difference(a, math.radians(345)))     >= 0.000000000000001 :
        raise ValueError("average_angle of %s is %s not 345.0" % ( str(aa), str(math.degrees(a)), ))
    if  True :
        aa  = [ 320, 330, 340, 350, 10, ]
        a   = average_angle([ math.radians(a) for a in aa ])
        if  abs(radian_angle_difference(a, math.radians(341.894795196)))    >= 0.00000000001 :
            raise ValueError("average_angle of %s is %s not 341.894795196"  % ( str(aa), str(math.degrees(a)), ))
        aa  = [ 320, 330, 340, 350, 380, 400, ]
        a   = average_angle([ math.radians(a) for a in aa ])
        if  abs(radian_angle_difference(a, math.radians(352.705023342)))    >= 0.00000000001 :
            raise ValueError("average_angle of %s is %s not 352.705023342"  % ( str(aa), str(math.degrees(a)), ))
        aa  = [ 320, 330, 340, 20, ]
        a   = average_angle([ math.radians(a) for a in aa ])
        if  abs(radian_angle_difference(a, math.radians(341.972749481)))    >= 0.00000000001 :
            raise ValueError("average_angle of %s is %s not 341.972749481"  % ( str(aa), str(math.degrees(a)), ))
        aa  = [ 252.0, 288.0, 324.0, 72.0, ]
        a   = average_angle([ math.radians(a) for a in aa ])
        if  abs(radian_angle_difference(a, math.radians(306)))              >= 0.00000000001 :
            raise ValueError("average_angle of %s is %s not 306"            % ( str(aa), str(math.degrees(a)), ))
        pass

    aa  = [ 6, 7, 8, 9, ]
    a   = average_modulo(aa, 10)
    if  abs(a - 7.5) > 0.00000000000001 :
        raise ValueError("average_modulo of %s is %s not 7.5" % ( str(aa), str(a), ))
    aa  = [ 7, 8, 9, 0 ]
    a   = average_modulo(aa, 10)
    if  abs(a - 8.5) > 0.00000000000001 :
        raise ValueError("average_modulo of %s is %s not 8.5" % ( str(aa), str(a), ))
    aa  = [ 7, 8, 9, 2 ]
    a   = average_modulo(aa, 10)
    if  abs(a - 8.5) > 0.00000000000001 :
        raise ValueError("average_modulo of %s is %s not 9" % ( str(aa), str(a), ))

    if  True :
        aa  = [ 7, 8, 9, 1 ]
        a   = average_modulo(aa, 10)
        if  abs(a - 8.62183826553) > 0.00000000001 :
            raise ValueError("average_modulo of %s is %s not 8.62183826553" % ( str(aa), str(a), ))
        pass


    a   = rectangle_union_area([ 3, 4, 5, 6 ], [ 4, 6, 7, 3 ])
    if  a != 39 :
        raise ValueError("rectangle_union_area problem != 39, is %d" % a)


    (x, y)  = get_line_intersection([ [ -4, 10 ], [ -4, 19 ], ], [ [ -4, 21 ], [ -4, 19 ], ], 0.0)
    if  (x != -4) or (y != 19) :
        s   = "Fail %s:%s" % ( str(x), str(y) )
        raise ValueError(s)

    (x, y)  = get_line_intersection([ [ -4, 10 ], [ -4, 19 ], ], [ [ -4, 21 ], [ -4, 20 ], ], 0.0)
    if  (x is not None) or (y is not None) :
        s   = "Fail %s:%s" % ( str(x), str(y) )
        raise ValueError(s)

    (x, y)  = get_line_intersection([ [ 3, 2 ], [ 5, 6 ], ], [ [ 1, 3 ], [ 5, 1 ], ])
    if  (x != 3) or (y != 2) :
        s   = "Fail %s:%s" % ( str(x), str(y) )
        raise ValueError(s)

    (x, y)  = get_line_intersection([ [ 3, 2 ], [ 5, 6 ], ], [ [ 1, 3 ], [ 5, 1 ], ], -0.1)
    if  (x is not None) or (y is not None) :
        s   = "Fail %s:%s" % ( str(x), str(y) )
        raise ValueError(s)


    if  (find_3d_line_to_plane_intersection([ 4, 2.0, 9.0 ], [ 2.0, 3.0, 8.0 ], [ [ 10.0, 10.0, 10.0 ], [ -5.0, 10.0, 10.0 ], [ -5.0, -10.0, 10.0 ] ]) - [ 6.0, 1.0, 10.0 ]).sum() > epsilon :
        raise ValueError("find_3d_line_to_plane_intersection wrong A")

    if  (find_3d_line_to_plane_intersection([ 4, 2, 9 ], [ 2, 5, 6 ], [ [ 10, 10, 10 ], [ 0, 0, 20 ], ]) - [ 4, 2, 9 ]).sum() > epsilon :
        raise ValueError("find_3d_line_to_plane_intersection wrong B")

    r       = area_of_irregular_polygon([ [ -3, -2 ], [ -1, 4 ], [ 6, 1 ], [ 3, 10 ], [ -4, 9 ], ])
    if  r  != 60.0 :
        raise ValueError("area_of_irregular_polygon() is not 60.0, is %.1f" % r)


    va      = [ a_point(100, 100), a_point(200, 100), a_point(200, 200), a_point(100, 200), ]   # square
    for p  in [
                [  50, 150 ],
                [ 150,  50 ],
                [ 150, 250 ],
                [ 250, 150 ],
                [ 150, 200 ],   # rt/bt
                [ 200, 150 ],   # rt/bt
                [ 200, 200 ],   # rt/bt
              ] :
        r       = point_in_polygon(va, a_point(p[0], p[1]))
        if  r   :
            s   = "%u:%u is inside a 100x100 square at 100:100!" % ( p[0], p[1], )
            raise ValueError(s)
        pass
    for p  in [
                [ 150, 150 ],
                [ 100, 110 ],
                [ 100, 100 ],
                [ 110, 100 ],
                [ 100, 150 ],
              ] :
        r       = point_in_polygon(va, a_point(p[0], p[1]))
        if  not r :
            s   = "%u:%u is not inside a 100x100 square at 100:100!" % ( p[0], p[1], )
            raise ValueError(s)
        pass

    va      = [ a_point(200, 200), a_point(300, 300), a_point(200, 400), a_point(100, 300), ]       # diamond
    for p  in [
                [ 100, 100 ],
                [ 300, 200 ],
                [ 300, 400 ],
                [ 100, 400 ],
                [ 110, 110 ],
                [ 280, 210 ],
                [ 290, 390 ],
                [ 110, 390 ],
                [ 200, 400 ],           # rt/bt
                [ 300, 300 ],           # rt/bt
                [ 250, 250 ],           # rt/bt
                [ 250, 350 ],           # rt/bt
                [ 200, 200 ],           # this should really be inside, I'd think
              ] :
        r       = point_in_polygon(va, a_point(p[0], p[1]))
        if  r   :
            s   = "%u:%u is inside a a diamond 200:200!" % ( p[0], p[1], )
            raise ValueError(s)
        pass
    for p  in [
                [ 200, 300 ],
                [ 199, 299 ],
                [ 201, 299 ],
                [ 199, 301 ],
                [ 201, 301 ],
                [ 150, 250 ],
                [ 250, 251 ],
                [ 250, 349 ],
                [ 150, 251 ],
                [ 150, 349 ],
                [ 100, 300 ],
                [ 150, 350 ],
              ] :
        r       = point_in_polygon(va, a_point(p[0], p[1]))
        if  not r :
            s   = "%u:%u is not inside a a diamond 200:200!" % ( p[0], p[1], )
            raise ValueError(s)
        pass

    make_color_wheel()


    poly    = poly_from_xy_list([ 50,150, 200,50, 350,150, 350,300, 250,300, 200,250, 150,350, 100,250, 100,200 ])
    for clipopy in [
                    poly_from_xy_list([ 100,100, 100,300, 300,300, 300,100 ]),
                    poly_from_xy_list([ 100,100, 300,100, 300,300, 100,300 ]),
                   ] :
        r   = clip_polygon(poly, clipopy)
        ra  = [ [100.0, 116.66666666666667], [125.00000000000001, 100.0], [275.0, 100.0], [300.0, 116.66666666666667], [300.0, 299.99999999999994], [250.0, 300.0], [200, 250], [175.0, 300.0], [125.0, 300.0], [100.0, 250.0] ]
        for pi, p in enumerate(ra) :
            for xy in [ 0, 1 ] :
                d   = abs(p[xy] - r[pi][xy])
                if  d   > 0.00000000001 :
                    s   = "clip_polygon failed on (1-based) %u'th vertex XY[%u] %.14f should be %.14f!" % ( pi + 1, xy, r[pi][xy], p[xy], )
                    raise ValueError(s)
                pass
            pass
        pass
    poly    = [ [ 100, 100 ], [ 200, 100 ], [ 200, 200 ], [ 100, 200 ] ]
    clipopy = [ [ 600, 100 ], [ 800, 100 ], [ 800, 200 ], [ 600, 200 ] ]
    r       = clip_polygon(poly, clipopy)
    if  len(r) :
        s   = "clip_polygon found overlap where there is none!"
        raise ValueError(s)
    poly    = [ [ 100, 100 ], [ 200, 100 ], [ 200, 200 ], [ 100, 200 ] ]
    clipopy = [ [  20, 100 ], [  40, 100 ], [  40, 200 ], [  20, 200 ] ]
    r       = clip_polygon(poly, clipopy)
    if  len(r) :
        s   = "clip_polygon found overlap where there is none!"
        raise ValueError(s)
    poly    = [ [ 100, 100 ], [ 200, 100 ], [ 200, 200 ], [ 100, 200 ] ]
    clipopy = [ [ 600,  10 ], [ 800,  10 ], [ 800,  20 ], [ 600,  20 ] ]
    r       = clip_polygon(poly, clipopy)
    if  len(r) :
        s   = "clip_polygon found overlap where there is none!"
        raise ValueError(s)
    poly    = [ [ 100, 100 ], [ 200, 100 ], [ 200, 200 ], [ 100, 200 ] ]
    clipopy = [ [ 600, 510 ], [ 800, 510 ], [ 800, 520 ], [ 600, 520 ] ]
    r       = clip_polygon(poly, clipopy)
    if  len(r) :
        s   = "clip_polygon found overlap where there is none!"
        raise ValueError(s)


    a = (0,0)
    b = (0,1)
    c = (1,1)
    d = (1,0)
    if  do_line_segments_intersect((a, b), (c, d)) :
        s   = "Intersect seg %s-%s %s-%s" % ( str(a), str(b), str(c), str(d) )
        raise ValueError(s)
    if  do_line_segments_intersect((a, b), (d, c)) :
        s   = "Intersect seg %s-%s %s-%s" % ( str(a), str(b), str(d), str(c) )
        raise ValueError(s)
    if  not do_line_segments_intersect((a, c), (b, d)) :
        s   = "Intersect not seg %s-%s %s-%s" % ( str(a), str(c), str(b), str(d) )
        raise ValueError(s)
    if  do_line_segments_intersect((a, d), (b, c)) :
        s   = "Intersect seg %s-%s %s-%s" % ( str(a), str(d), str(b), str(c) )
        raise ValueError(s)


    print "ThreadID:", get_tid()


    a   = value_array_for_key([ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 3 } ], 'a')
    if  a  != [ 1, 2 ] :
        s   = "%s should be '[ 1, 2 ]'" % str(a)
        raise ValueError(s)
    a   = value_array_for_key([ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 3 } ], 'b')
    if  a  != [ 2, 3, 4 ] :
        s   = "%s should be '[ 2, 3, 4 ]'" % str(a)
        raise ValueError(s)
    a   = value_array_for_key([ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 4 } ], 'c')
    if  a  != [ 3, 4 ] :
        s   = "%s should be '[ 3, 4 ]'" % str(a)
        raise ValueError(s)

    a   = value_array_for_key([ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 3 } ], 'a', 9)
    if  a  != [ 1, 2, 9 ] :
        s   = "%s should be '[ 1, 2, 9 ]'" % str(a)
        raise ValueError(s)
    a   = value_array_for_key([ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 3 } ], 'b', 9)
    if  a  != [ 2, 3, 4 ] :
        s   = "%s should be '[ 2, 3, 4 ]'" % str(a)
        raise ValueError(s)
    a   = value_array_for_key([ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 4 } ], 'c', 9)
    if  a  != [ 9, 3, 4 ] :
        s   = "%s should be '[ 9, 3, 4 ]'" % str(a)
        raise ValueError(s)

    a   = [ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 4 } ]
    replace_value_array_for_key(a, 'd', [ 5, 6, 7 ])
    a   = value_array_for_key(a, 'd')
    if  a  != [ 5, 6, 7 ] :
        s   = "%s should be '[ 5, 6, 7 ]'" % str(a)
        raise ValueError(s)

    a   = [ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 4 } ]
    try :
        replace_value_array_for_key(a, 'e', [ 5, ])
        raise ValueError("Short replace_value_array_for_key did not raise exception")
    except IndexError :
        pass

    a   = [ { 'b' : 2, 'a' : 1 }, { 'b' : 3, 'a' : 2, 'c' : 3 }, { 'b' : 4, 'c' : 4 } ]
    try :
        replace_value_array_for_key(a, 'f', [ 5, 6, 7, 8 ])
        raise ValueError("Long replace_value_array_for_key did not raise exception")
    except IndexError :
        pass



    r   = best_w_h_fit_scale(10, 10, 5, 2)
    if  r != 2 :
        raise ValueError("best_w_h_fit_scale(10,10,5,2) != 2, is %f" % r)
    r   = best_w_h_fit_scale(20, 10, 5, 2)
    if  r != 4 :
        raise ValueError("best_w_h_fit_scale(20,10,5,2) != 4, is %f" % r)
    r   = best_w_h_fit_scale(30, 10, 5, 2)
    if  r != 5 :
        raise ValueError("best_w_h_fit_scale(30,10,5,2) != 5, is %f" % r)

    r   = max_w_h_scale(10400, 10300)
    if  r != 1.0 :
        raise ValueError("max_w_h_scale of 10400:10300->None is %f not 1.0" % r)
    r   = max_w_h_scale(400, 300, max_w_and_h = 500)
    if  r != 1.0 :
        raise ValueError("max_w_h_scale of 400:300->500 is %f not 1.0" % r)
    r   = max_w_h_scale(1000, 300, max_w_and_h = 500)
    if  r != 0.5 :
        raise ValueError("max_w_h_scale of 1000:300->500 is %f not 0.5" % r)
    r   = max_w_h_scale(800, 2000, max_w_and_h = 500)
    if  r != 0.25 :
        raise ValueError("max_w_h_scale of 800:2000->500 is %f not 0.25" % r)
    r   = max_w_h_scale(800, 2000, max_w = 200)
    if  r != 0.25 :
        raise ValueError("max_w_h_scale of 800:?->400 is %f not 0.25" % r)
    r   = max_w_h_scale(800, 2000, max_h = 500)
    if  r != 0.25 :
        raise ValueError("max_w_h_scale of ?:2000->400 is %f not 0.25" % r)
    r   = max_w_h_scale(800, 2000, max_w = 900)
    if  r != 1.0 :
        raise ValueError("max_w_h_scale of 800:?->900 is %f not 1.0" % r)
    r   = max_w_h_scale(720,  405, max_w = 800, max_h = 600)
    if  r != 1.0 :
        raise ValueError("max_w_h_scale of 720:405->800:600h is %f not 1.0" % r)
    r   = max_w_h_scale(405,  720, max_w = 800, max_h = 800)
    if  r != 1.0 :
        raise ValueError("max_w_h_scale of 720:405->800:800h is %f not 1.0" % r)


    r   = best_line_fit(2, 7, 0, 10)
    if  r != 0 :
        raise ValueError("best_line_fit(2, 7, 0, 10) != 0, is %u" % r)
    r   = best_line_fit(5, 7, 0, 10)
    if  r != 2 :
        raise ValueError("best_line_fit(2, 7, 0, 10) != 1, is %u" % r)
    r   = best_line_fit(9, 4, 0, 10)
    if  r != 6 :
        raise ValueError("best_line_fit(9, 4, 0, 10) != 6, is %u" % r)
    r   = best_line_fit(8, 4, 0, 10)
    if  r != 6 :
        raise ValueError("best_line_fit(8, 4, 0, 10) != 6, is %u" % r)
    r   = best_line_fit(8, 4, 1,  3)
    if  r != 1 :
        raise ValueError("best_line_fit(8, 4, 1,  3) != 1, is %u" % r)

    sa  = [
            23, 23, 23, 24, 26, 31, 36, 43, 50, 57, 63, 67, 70, 71, 72, 72, 72, 71, 69, 66, 64, 61, 59, 57, 56, 55, 53, 52, 51, 49, 47, 46,
            42, 41, 40, 39, 38, 36, 35, 34, 33, 32, 31, 31, 30, 29, 29, 30, 32, 35, 40, 45, 52, 62, 66, 68, 69, 70, 70, 69, 67, 65, 62, 59,
            54, 52, 50, 49, 48, 46, 45, 43, 41, 40, 39, 37, 35, 34, 33, 31, 30, 29, 29, 28, 27, 27, 26, 25, 24, 23, 23, 22, 21, 21, 20, 21,
            24, 28, 34, 40, 47, 54, 60, 64, 67, 69, 70, 70, 70, 69, 66, 64, 60, 57, 54, 52, 50, 48, 47, 46, 44, 43, 41, 40, 39, 37, 37, 38,
            40, 41, 44, 45, 46, 45, 43, 40, 38, 33, 31, 30, 30, 29, 29, 29, 29, 29, 28, 27, 27, 26, 25, 24, 24, 23, 22, 22, 21, 21, 21, 21,
            21, 24, 28, 33, 40, 48, 57, 64, 70, 74, 77, 78, 79, 79, 77, 75, 73, 69, 66, 63, 60, 58, 56, 54, 52, 51, 49, 47, 45, 43, 42, 40,
            39, 37, 36, 34, 33, 32, 30, 29, 28, 27, 27, 26, 26, 28, 32, 37, 42, 50, 57, 63, 67, 71, 73, 74, 74, 73, 71, 69, 65, 62, 58, 55,
            52, 50, 48, 45, 43, 42, 40, 39, 37, 35, 33, 32, 30, 29, 28, 26, 26, 25, 24, 23, 23, 22, 21, 21, 20, 19, 18, 17, 16, 16, 16, 18,
            22, 29, 36, 43, 52, 59, 64, 68, 71, 73, 73, 73, 72, 70, 67, 63, 56, 53, 50, 48, 46, 45, 43, 41, 40, 38, 36, 34, 32, 32, 32, 34,
            36, 39, 41, 43, 44, 44, 42, 40, 37, 33, 30, 28, 26, 25, 25, 25, 25, 25, 24, 23, 23, 22, 23, 24, 27, 31, 36, 40, 45, 48, 49, 49,
            48, 46, 40, 38, 35, 34, 33, 33, 33, 33, 34, 34, 34, 33, 33, 32, 31, 31, 30, 29, 29, 28, 27, 27, 26, 26, 26, 28, 31, 37, 43, 52,
            61, 69, 76, 85, 86, 87, 87, 85, 83, 79, 75, 71, 67, 63, 60, 58, 55, 53, 51, 49, 47, 45, 42, 40, 39, 37, 35, 33, 32, 30, 29, 28,
            27, 26, 25, 24, 23, 23, 22, 21, 20, 20, 19, 19, 18, 18, 17, 17, 16, 16, 16, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 14, 14,
            15, 15, 15, 14, 14, 14, 16, 25, 33, 42, 53, 63, 73, 80, 85, 89, 92, 93, 93, 92, 90, 87, 61, 59, 57, 55, 53, 52, 51, 50, 49, 47,
            46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 35, 34, 34, 33, 33, 33, 32, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29,
            31, 34, 37, 40, 45, 49, 53, 56, 58, 59, 60, 60, 60, 59, 58, 56, 54, 52, 50, 48, 47, 44, 43, 42, 42, 41, 40, 39, 38, 37, 36, 36,
            35, 36, 37, 38, 40, 42, 47, 48, 49, 49, 48, 46, 45, 43, 41, 40, 39, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32, 31, 31, 30,
            30, 29, 29, 29, 28, 28, 28, 29, 31, 34, 38, 42, 46, 50, 54, 56, 58, 59, 59, 59, 58, 57, 56, 54, 52, 50, 49, 47, 46, 46, 45, 44,
            44, 43, 42, 42, 41, 41, 42, 43, 43, 44, 45, 44, 44, 43, 41, 40, 39, 38, 37, 37, 37, 36, 36, 36, 35, 35, 34, 34, 33, 33, 33, 32,
            32, 31, 31, 31, 31, 30, 30, 30, 31, 33, 36, 40, 44, 49, 54, 58, 61, 63, 65, 66, 66, 65, 65, 63, 61, 59, 57, 55, 53, 51, 50, 49,
            48, 47, 45, 44, 43, 42, 41, 40, 39, 38, 38, 37, 35, 34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28,
            29, 30, 32, 36, 40, 44, 49, 54, 58, 61, 63, 64, 65, 65, 65, 64, 63, 61, 59, 57, 54, 52, 51, 49, 48, 47, 45, 44, 43, 42, 41, 40,
            39, 38, 37, 36, 36, 37, 39, 40, 42, 45, 47, 49, 50, 50, 49, 48, 47, 45, 43, 41, 40, 39, 38, 37, 36, 36, 35, 35, 34, 34, 33, 33,
            33, 33, 33, 34, 34, 34, 34, 34, 33, 32, 31, 29, 29, 28, 28, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 26, 25,
            25, 25, 26, 27, 30, 33, 38, 42, 47, 52, 55, 57, 59, 59, 59, 58, 57, 56, 54, 52, 50, 48, 47, 46, 45, 44, 43, 43, 42, 41, 40, 40,
            40, 40, 40, 40, 41, 42, 43, 44, 44, 43, 42, 41, 40, 39, 38, 37, 36, 36, 36, 36, 35, 35, 35, 34, 34, 34, 33, 33, 32, 32, 32, 31,
            31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
            28, 28, 28, 28, 28, 28, 28, 28, 29, 30, 33, 37, 41, 47, 53, 59, 63, 67, 70, 71, 72, 72, 72, 71, 70, 68, 66, 64, 62, 60, 58, 56,
            55, 54, 52, 51, 50, 48, 47, 46, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 35, 34, 34, 33, 33, 33, 32, 32, 32, 31, 31, 31,
            30, 30, 31, 32, 35, 38, 41, 46, 50, 54, 57, 59, 61, 61, 62, 61, 61, 59, 58, 56, 54, 52, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41,
            40, 40, 39, 38, 37, 36, 36, 35, 34, 33, 33, 32, 32, 31, 31, 30, 29, 29, 28, 28, 27, 27, 26, 25, 25, 25, 26, 28, 31, 35, 40, 44,
            49, 53, 57, 59, 61, 62, 62, 62, 61, 60, 59, 57, 56, 54, 53, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 40, 39, 38, 38,
            37, 37, 36, 35, 35, 34, 34, 34, 33, 33, 33, 32, 32, 32, 31, 32, 33, 35, 38, 40, 45, 49, 53, 56, 58, 60, 61, 62, 62, 62, 62, 61,
            59, 57, 56, 54, 52, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 39, 39, 40, 40, 42, 45, 47, 49, 51, 52, 53, 53, 52, 51,
            50, 48, 46, 45, 43, 42, 40, 40, 40, 39, 39, 38, 38, 37, 36, 36, 35, 34, 34, 33, 32, 32, 31, 30, 29, 29, 28, 28, 27, 27, 27, 27,
            27, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 28, 31, 34, 39, 42, 46, 50, 52, 54, 55, 55, 54, 53, 63, 60,
            57, 53, 50, 48, 46, 45, 44, 43, 42, 41, 40, 40, 38, 36, 35, 33, 32, 30, 29, 28, 27, 28, 31, 35, 40, 46, 53, 58, 62, 65, 66, 66,
            64, 61, 58, 54, 51, 47, 44, 41, 40, 39, 38, 38, 37, 36, 35, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, 22, 22, 24, 28, 34, 40, 48,
            56, 63, 69, 72, 75, 75, 75, 74, 69, 65, 62, 58, 56, 53, 51, 50, 49, 48, 47, 46, 45, 43, 42, 40, 39, 38, 36, 35, 33, 32, 31, 29,
            29, 30, 32, 37, 41, 49, 57, 64, 71, 76, 79, 81, 82, 82, 80, 78, 75, 71, 67, 64, 60, 57, 55, 53, 51, 50, 48, 47, 45, 43, 41, 40,
            38, 37, 37, 39, 41, 46, 51, 58, 63, 68, 72, 74, 75, 74, 73, 71, 68, 60, 56, 53, 49, 47, 45, 43, 42, 40, 40, 38, 37, 35, 33, 31,
            30, 28, 27, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 16, 15, 15, 14, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11, 11,
            11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 17, 22, 29, 39, 49, 61, 72, 80, 87, 92, 95, 96, 97, 96, 94, 91,
            88, 62, 60, 58, 56, 54, 53, 52, 50, 49, 48, 47, 46, 45, 44, 43, 41, 40, 40, 39, 38, 38, 37, 36, 36, 35, 34, 34, 33, 33, 32, 32,
            32, 31, 31, 31, 31, 30, 30, 30, 30, 29, 30, 31, 33, 36, 40, 44, 48, 52, 56, 58, 60, 61, 61, 61, 59, 58, 56, 54, 52, 50, 49, 47,
            46, 45, 44, 43, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 33, 33, 32, 31, 31, 30, 30, 30, 29, 29, 28, 28, 28, 28, 27, 27, 27,
            27, 27, 29, 31, 35, 39, 42, 47, 51, 53, 55, 57, 57, 57, 57, 56, 55, 53, 51, 49, 48, 46, 45, 44, 43, 42, 42, 41, 40, 40, 39, 39,
            38, 36, 35, 35, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 28, 28, 29, 31, 34, 38, 42, 46, 50, 54, 56, 58,
            59, 59, 59, 59, 58, 56, 55, 53, 51, 50, 49, 47, 47, 46, 45, 45, 44, 43, 43, 42, 41, 40, 40, 39, 39, 38, 37, 37, 36, 36, 35, 35,
            34, 34, 34, 34, 33, 33, 33, 32, 32, 32, 32, 34, 36, 39, 42, 46, 51, 55, 58, 61, 62, 63, 64, 64, 64, 63, 62, 60, 58, 56, 54, 52,
            50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 32, 31, 31, 31, 31, 30, 30, 30, 29,
            30, 31, 36, 40, 44, 48, 52, 56, 58, 60, 61, 62, 62, 62, 61, 60, 58, 56, 54, 52, 50, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39,
            38, 37, 36, 35, 34, 34, 33, 32, 32, 31, 31, 30, 30, 29, 29, 29, 29, 28, 28, 28, 27, 27, 27, 28, 29, 32, 36, 40, 44, 48, 52, 55,
            57, 59, 59, 60, 59, 59, 58, 56, 54, 52, 50, 48, 47, 45, 44, 43, 42, 41, 40, 40, 40, 39, 37, 36, 35, 34, 33, 33, 32, 32, 31, 31,
            30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 27, 27, 27, 27, 27, 29, 31, 35, 39, 43, 47, 51, 55, 57, 59, 59, 60, 60, 59, 58, 57, 55,
            53, 51, 49, 48, 47, 46, 45, 44, 44, 43, 42, 41, 41, 40, 40, 39, 38, 37, 37, 36, 36, 35, 34, 34, 34, 33, 33, 33, 32, 32, 32, 32,
            31, 31, 30, 30, 30, 33, 36, 40, 43, 48, 52, 56, 59, 62, 63, 64, 65, 65, 64, 63, 62, 60, 58, 56, 54, 52, 50, 49, 47, 46, 45, 44,
            43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 28, 28, 29, 30, 33,
            37, 40, 45, 50, 54, 57, 60, 62, 63, 63, 63, 63, 62, 61, 59, 57, 55, 53, 51, 49, 48, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37,
            36, 36, 35, 34, 34, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 27, 27, 27, 28, 30, 33, 37, 41, 46, 50, 54,
            57, 60, 61, 62, 62, 62, 61, 60, 58, 56, 54, 52, 50, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 33, 33, 32, 31, 31,
            30, 30, 29, 29, 28, 28, 28, 28, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 27, 29, 32, 37, 40, 45, 50, 54, 57, 59, 60, 60, 60, 60,
            59, 57, 56, 54, 51, 49, 48, 46, 45, 44, 43, 42, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30,
            30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 29, 31, 37, 41, 46, 51, 55, 58, 61, 63, 64, 64, 64, 64, 63, 61, 60, 58, 56, 54, 52, 50,
            49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 34, 34, 33, 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29,
            29, 29, 29, 30, 32, 35, 40, 43, 49, 53, 57, 61, 63, 64, 65, 66, 66, 65, 64, 63, 61, 59, 57, 54, 52, 49, 48, 47, 46, 44, 43, 42,
            41, 40, 40, 39, 37, 36, 35, 34, 34, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 27, 27, 28, 29, 31, 34,
            39, 43, 48, 52, 56, 59, 61, 63, 63, 64, 64, 63, 62, 61, 59, 56, 54, 52, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37,
            36, 35, 34, 33, 33, 32, 32, 31, 30, 30, 30, 29, 29, 29, 28, 28, 28, 27, 27, 27, 27, 26, 26, 26, 26, 27, 28, 31, 35, 40, 44, 49,
            53, 57, 59, 61, 62, 62, 62, 61, 61, 59, 57, 55, 53, 51, 49, 47, 44, 43, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 33, 32, 32,
            31, 31, 30, 30, 29, 29, 29, 28, 28, 28, 27, 27, 27, 27, 27, 26, 26, 26, 25, 26, 26, 28, 35, 40, 44, 49, 53, 56, 58, 60, 60, 61,
            60, 60, 59, 57, 55, 53, 51, 49, 47, 46, 45, 45, 44, 43, 42, 41, 41, 40, 40, 39, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 33,
            33, 32, 32, 32, 32, 32, 31, 31, 31, 31, 30, 30, 30, 31, 32, 35, 38, 42, 47, 51, 56, 60, 62, 64, 66, 66, 67, 66, 66, 65, 63, 61,
            59, 57, 54, 53, 51, 50, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30,
            30, 29, 29, 29, 29, 28, 28, 28, 28, 29, 32, 35, 39, 43, 48, 53, 57, 60, 63, 65, 66, 66, 65, 64, 63, 61, 59, 57, 54, 52, 50, 49,
            48, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 37, 36, 35, 34, 34, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28,
            28, 28, 27, 27, 28, 29, 31, 35, 39, 43, 48, 53, 57, 60, 62, 63, 64, 65, 64, 64, 63, 61, 59, 57, 55, 53, 51, 49, 48, 47, 45, 44,
            43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27,
            27, 28, 30, 34, 38, 42, 47, 52, 56, 59, 61, 63, 64, 64, 64, 63, 62, 61, 59, 57, 54, 52, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40,
            39, 38, 37, 36, 36, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 28, 30,
            33, 37, 41, 46, 51, 55, 58, 60, 62, 63, 63, 63, 63, 62, 60, 58, 56, 54, 52, 50, 48, 47, 46, 45, 44, 43, 42, 41, 40, 38, 37, 36,
            35, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 28, 31, 34, 38, 42,
            48, 52, 56, 59, 61, 62, 63, 64, 63, 63, 61, 60, 58, 56, 53, 51, 49, 48, 47, 46, 45, 44, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34,
            34, 33, 32, 32, 31, 31, 30, 30, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27, 27, 26, 26, 26, 27, 31, 35, 40, 44, 49, 53, 57, 60,
            62, 63, 63, 63, 63, 62, 61, 59, 57, 55, 52, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 32, 32,
            31, 31, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 27, 27, 27, 27, 27, 26, 26, 26, 26, 28, 30, 33, 37, 41, 46, 51, 55, 58, 61, 62,
            63, 63, 63, 63, 61, 60, 58, 56, 54, 52, 50, 48, 47, 46, 45, 44, 43, 42, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 31,
            30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 27, 28, 30, 33, 37, 41, 46, 51, 55, 59, 61, 62, 63, 64, 64,
            63, 62, 61, 59, 56, 54, 52, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30,
            30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 28, 29, 32, 36, 40, 45, 50, 54, 61, 62, 63, 64, 64, 64, 63, 61, 60,
            58, 55, 53, 51, 50, 48, 47, 46, 45, 44, 43, 42, 40, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29,
            29, 29, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 28, 30, 33, 37, 41, 46, 51, 55, 58, 61, 62, 63, 63, 63, 63, 62, 61, 59, 57, 54,
            52, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 28,
            28, 29, 30, 33, 36, 40, 44, 49, 53, 56, 58, 59, 60, 61, 61, 60, 59, 57, 56, 53, 51, 49, 47, 46, 45, 44, 43, 42, 42, 41, 40, 40,
            39, 38, 37, 36, 35, 34, 33, 32, 32, 31, 30, 29, 29, 29, 28, 28, 28, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 26, 25, 25, 25, 25,
            27, 29, 32, 37, 41, 46, 50, 54, 57, 58, 59, 59, 59, 58, 56, 54, 52, 50, 48, 46, 45, 44, 43, 42, 41, 41, 40, 40, 39, 38, 37, 36,
            35, 34, 34, 33, 32, 32, 32, 32, 33, 34, 37, 40, 44, 48, 52, 55, 57, 58, 58, 58, 57, 56, 54, 52, 50, 48, 46, 45, 44, 43, 43, 41,
            41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 34, 34, 36, 38, 41, 44, 48, 51, 53, 54, 55, 55, 54, 66, 62, 58, 54, 50, 46, 43, 41, 40,
            39, 38, 37, 36, 34, 33, 31, 29, 27, 25, 24, 23, 24, 27, 32, 39, 45, 60, 65, 69, 71, 72, 71, 69, 66, 61, 56, 51, 43, 40, 39, 38,
            36, 35, 33, 32, 30, 28, 26, 25, 23, 21, 20, 18, 17, 15, 14, 13, 13, 15, 18, 24, 31, 40, 48, 56, 63, 68, 72, 74, 75, 76, 75, 73,
            71, 67, 63, 59, 55, 51, 48, 46, 44, 42, 40, 39, 37, 34, 32, 29, 26, 24, 22, 21, 23, 26, 31, 37, 43, 49, 54, 58, 60, 62, 62, 61,
            59, 56, 52, 48, 40, 38, 36, 35, 34, 33, 31, 30, 28, 27, 25, 25, 25, 27, 29, 33, 36, 39, 41, 41, 41, 39, 36, 33, 29, 26, 24, 22,
            21, 21, 21, 20, 20, 20, 19, 18, 17, 17, 16, 15, 14, 13, 13, 12, 11, 10, 10, 9, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4,
            3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 7, 11, 18, 27, 37,
            47, 56, 63, 67, 70, 71, 71, 71, 70, 69, 67, 64, 61, 57, 54, 50, 48, 45, 43, 42, 40, 40, 38, 37, 35, 34, 32, 30, 29, 28, 26, 25,
            24, 23, 22, 21, 21, 20, 20, 19, 19, 18, 18, 17, 17, 16, 15, 15, 14, 15, 21, 26, 32, 39, 44, 49, 53, 55, 55, 56, 55, 55, 54, 52,
            50, 47, 44, 41, 39, 37, 36, 34, 33, 33, 32, 31, 30, 29, 28, 27, 26, 24, 23, 22, 21, 21, 20, 19, 19, 18, 17, 17, 17, 16, 16, 16,
            15, 15, 15, 14, 14, 13, 13, 13, 14, 16, 20, 26, 32, 39, 44, 48, 52, 53, 54, 54, 54, 53, 52, 50, 48, 45, 43, 40, 39, 37, 36, 35,
            34, 33, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 23, 22, 21, 21, 20, 20, 19, 19, 19, 18, 18, 18, 18, 18, 17, 17, 17, 16, 16,
            16, 16, 16, 19, 23, 28, 34, 40, 46, 51, 55, 57, 58, 59, 58, 58, 55, 53, 50, 48, 45, 43, 41, 40, 40, 39, 38, 38, 37, 36, 35, 33,
            32, 31, 30, 29, 28, 27, 26, 26, 25, 24, 24, 23, 23, 22, 22, 22, 21, 21, 21, 21, 20, 20, 19, 19, 19, 18, 19, 20, 24, 29, 35, 41,
            47, 53, 58, 61, 63, 64, 65, 64, 64, 62, 60, 58, 55, 52, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 38, 37, 36, 34, 33, 32, 30, 29,
            28, 27, 26, 26, 25, 24, 24, 23, 23, 22, 22, 21, 21, 21, 20, 20, 19, 19, 19, 19, 20, 22, 27, 33, 40, 46, 53, 59, 64, 66, 68, 69,
            69, 68, 67, 65, 62, 59, 56, 53, 51, 49, 47, 46, 45, 44, 43, 42, 40, 40, 38, 36, 35, 33, 32, 31, 29, 28, 27, 26, 25, 25, 24, 23,
            23, 22, 21, 21, 20, 20, 20, 19, 19, 18, 18, 18, 17, 17, 16, 16, 16, 18, 21, 27, 33, 40, 48, 55, 60, 64, 66, 67, 67, 66, 65, 63,
            60, 56, 53, 50, 47, 45, 44, 43, 42, 41, 40, 40, 39, 37, 36, 34, 32, 31, 30, 28, 27, 26, 25, 24, 23, 23, 22, 22, 21, 21, 20, 20,
            20, 20, 19, 19, 19, 18, 18, 17, 17, 16, 16, 16, 18, 21, 27, 34, 41, 50, 58, 65, 69, 72, 74, 75, 74, 73, 71, 68, 65, 61, 57, 54,
            51, 49, 46, 46, 45, 44, 42, 41, 40, 38, 37, 35, 34, 33, 32, 31, 30, 29, 29, 28, 28, 27, 27, 27, 27, 26, 26, 26, 26, 25, 25, 24,
            24, 23, 23, 25, 27, 32, 39, 46, 55, 63, 71, 77, 81, 83, 85, 85, 85, 83, 81, 78, 74, 70, 66, 62, 59, 56, 54, 53, 51, 50, 48, 46,
            44, 42, 40, 40, 38, 36, 35, 33, 32, 31, 30, 29, 28, 27, 26, 25, 25, 24, 24, 23, 23, 22, 22, 22, 21, 21, 20, 20, 19, 19, 19, 20,
            22, 27, 33, 40, 49, 58, 67, 79, 83, 85, 86, 87, 86, 85, 82, 79, 75, 71, 66, 62, 59, 56, 54, 52, 50, 48, 47, 45, 42, 40, 39, 37,
            35, 33, 32, 30, 29, 28, 26, 25, 25, 24, 23, 22, 22, 21, 21, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 17, 20, 25, 31,
            39, 48, 57, 66, 73, 78, 81, 83, 84, 84, 83, 81, 79, 75, 71, 67, 62, 59, 56, 54, 52, 50, 48, 47, 45, 43, 41, 39, 37, 35, 34, 32,
            31, 29, 28, 27, 26, 25, 24, 23, 23, 22, 21, 21, 20, 20, 19, 19, 19, 18, 18, 18, 18, 17, 17, 17, 16, 16, 17, 18, 21, 26, 33, 41,
            51, 60, 68, 74, 79, 82, 84, 84, 84, 80, 77, 73, 69, 65, 61, 58, 55, 53, 51, 50, 48, 46, 44, 42, 40, 39, 37, 35, 33, 32, 30, 29,
            28, 27, 26, 25, 24, 23, 22, 21, 21, 20, 20, 19, 19, 19, 18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 16, 18, 22, 28, 36, 44, 54, 62,
            70, 75, 79, 81, 82, 82, 81, 79, 76, 73, 68, 64, 60, 57, 54, 52, 50, 49, 47, 45, 44, 42, 40, 38, 37, 35, 33, 31, 30, 28, 27, 26,
            25, 24, 23, 23, 22, 21, 21, 20, 20, 19, 19, 19, 18, 18, 18, 18, 17, 17, 16, 16, 15, 16, 17, 21, 27, 34, 42, 51, 60, 67, 73, 76,
            78, 79, 78, 77, 74, 71, 67, 63, 58, 55, 52, 50, 49, 47, 46, 45, 43, 42, 40, 39, 37, 35, 33, 32, 30, 29, 28, 25, 25, 24, 23, 22,
            21, 21, 21, 20, 20, 20, 19, 19, 19, 19, 18, 18, 17, 17, 18, 20, 24, 30, 38, 46, 56, 65, 73, 78, 82, 84, 85, 85, 84, 82, 79, 76,
            72, 67, 63, 59, 56, 54, 52, 51, 49, 48, 46, 44, 42, 40, 39, 37, 35, 34, 32, 31, 30, 29, 28, 27, 27, 26, 25, 25, 25, 25, 24, 24,
            24, 24, 24, 24, 23, 23, 23, 22, 22, 23, 26, 31, 37, 45, 55, 64, 73, 81, 86, 90, 93, 95, 95, 95, 94, 92, 88, 62, 60, 57, 55, 53,
            52, 51, 50, 49, 47, 46, 45, 44, 43, 42, 40, 40, 39, 38, 37, 37, 36, 35, 35, 34, 34, 33, 33, 32, 32, 32, 31, 31, 31, 31, 30, 30,
            30, 29, 29, 29, 30, 31, 33, 36, 40, 44, 50, 54, 58, 61, 64, 65, 66, 67, 67, 67, 66, 65, 63, 61, 56, 54, 52, 51, 50, 48, 47, 46,
            45, 43, 42, 41, 40, 39, 38, 37, 36, 36, 35, 34, 33, 33, 32, 32, 32, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28,
            28, 28, 29, 31, 35, 39, 43, 48, 52, 56, 60, 62, 63, 64, 65, 65, 64, 62, 60, 58, 56, 54, 52, 50, 49, 48, 46, 45, 44, 43, 41, 40,
            40, 39, 38, 37, 36, 35, 34, 34, 33, 32, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 28,
            29, 30, 34, 38, 41, 46, 51, 55, 59, 61, 63, 64, 64, 65, 64, 63, 62, 60, 58, 55, 53, 51, 50, 48, 47, 46, 45, 44, 43, 41, 40, 40,
            39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 28, 27, 27, 28,
            28, 30, 33, 37, 41, 46, 51, 55, 58, 61, 62, 64, 64, 64, 64, 63, 61, 60, 57, 55, 53, 51, 49, 48, 47, 46, 45, 44, 43, 41, 40, 40,
            39, 38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 28, 28, 27, 27, 27,
            28, 28, 30, 33, 37, 41, 46, 51, 55, 58, 61, 62, 63, 64, 64, 63, 62, 61, 59, 57, 54, 52, 51, 49, 48, 47, 46, 45, 44, 42, 41, 40,
            40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 28, 27,
            27, 28, 29, 31, 34, 38, 42, 47, 51, 56, 59, 61, 63, 63, 64, 64, 63, 62, 61, 59, 56, 54, 52, 50, 49, 47, 46, 45, 44, 43, 42, 41,
            40, 39, 38, 37, 36, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 27,
            26, 27, 27, 29, 32, 35, 40, 44, 49, 53, 56, 59, 60, 61, 61, 61, 60, 59, 58, 56, 53, 51, 49, 48, 46, 45, 45, 44, 43, 42, 41, 40,
            40, 38, 37, 36, 36, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27,
            27, 28, 29, 32, 36, 40, 44, 49, 53, 56, 58, 60, 60, 60, 60, 59, 58, 56, 54, 52, 50, 48, 47, 45, 45, 44, 43, 42, 41, 40, 40, 39,
            38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 29, 29, 29, 28, 28, 28, 28, 29, 30,
            33, 37, 40, 45, 51, 55, 59, 61, 63, 64, 65, 65, 65, 64, 63, 61, 59, 57, 55, 53, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40,
            40, 39, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32, 32, 32, 32, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 32, 34, 38, 41, 46,
            52, 56, 60, 63, 65, 67, 68, 68, 68, 68, 67, 66, 64, 62, 59, 57, 54, 53, 51, 49, 48, 47, 46, 44, 43, 42, 40, 40, 39, 38, 37, 36,
            35, 34, 34, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 28, 31, 34, 38, 42, 47,
            52, 57, 60, 62, 64, 65, 66, 66, 66, 65, 64, 62, 60, 57, 55, 53, 51, 50, 48, 47, 46, 45, 43, 42, 41, 40, 39, 38, 37, 36, 35, 35,
            34, 33, 33, 32, 32, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 28, 29, 32, 35, 40, 44, 49, 54,
            58, 60, 62, 64, 64, 65, 65, 64, 63, 61, 59, 57, 55, 53, 51, 49, 48, 47, 46, 45, 43, 42, 41, 40, 40, 38, 37, 36, 36, 35, 34, 33,
            33, 32, 32, 31, 31, 31, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 28, 29, 32, 36, 40, 45, 50, 54, 57,
            60, 62, 63, 64, 64, 63, 63, 61, 60, 57, 55, 53, 51, 50, 48, 47, 46, 45, 44, 43, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 32, 32,
            31, 31, 30, 30, 29, 29, 29, 28, 28, 28, 28, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 26, 27, 29, 31, 35, 40, 44, 49, 53, 56, 58,
            60, 61, 61, 61, 60, 59, 57, 55, 53, 51, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 32, 32, 31, 31,
            30, 30, 30, 29, 29, 29, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 28, 30, 33, 38, 42, 51, 54, 57, 58, 59, 59,
            59, 58, 57, 55, 53, 51, 49, 47, 46, 45, 44, 43, 42, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30,
            29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 29, 31, 35, 39, 43, 48, 53, 57, 61, 62, 63, 63, 62, 62, 60, 59,
            57, 55, 53, 51, 49, 48, 47, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 38, 37, 36, 36, 35, 35, 35, 34, 34, 33, 33, 33, 33, 32,
            32, 32, 32, 31, 31, 31, 30, 30, 30, 30, 31, 33, 37, 40, 44, 50, 54, 58, 62, 64, 66, 67, 67, 67, 67, 66, 65, 63, 61, 59, 57, 55,
            53, 51, 50, 49, 48, 46, 45, 44, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29,
            29, 28, 28, 28, 28, 28, 29, 32, 35, 40, 44, 49, 53, 57, 60, 62, 63, 64, 65, 65, 64, 64, 62, 60, 58, 56, 54, 52, 50, 49, 48, 47,
            45, 44, 43, 42, 40, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 31, 31, 31, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27,
            27, 27, 27, 28, 30, 33, 37, 41, 46, 51, 55, 58, 60, 61, 62, 63, 63, 63, 62, 61, 59, 57, 55, 53, 51, 49, 48, 47, 46, 45, 44, 42,
            41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27,
            28, 29, 31, 34, 38, 41, 46, 51, 55, 58, 60, 61, 62, 63, 63, 62, 61, 60, 58, 56, 54, 52, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40,
            40, 39, 38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 28,
            30, 33, 37, 40, 45, 50, 54, 57, 59, 61, 62, 62, 62, 62, 61, 59, 57, 55, 53, 52, 50, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39,
            38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 28, 28, 30, 36,
            40, 44, 49, 53, 57, 59, 61, 62, 62, 62, 62, 61, 59, 58, 56, 54, 52, 50, 49, 47, 46, 46, 45, 44, 42, 41, 40, 40, 39, 38, 37, 36,
            35, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 28, 29, 32, 35, 39, 43, 48,
            52, 56, 58, 60, 61, 62, 62, 62, 61, 60, 58, 56, 54, 52, 50, 49, 48, 47, 46, 45, 44, 43, 42, 40, 40, 39, 38, 37, 36, 36, 35, 34,
            34, 33, 33, 32, 32, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 28, 29, 31, 34, 38, 42, 46, 51, 55,
            58, 60, 61, 62, 62, 62, 61, 60, 59, 57, 55, 53, 51, 49, 48, 47, 46, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33,
            32, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 27, 27, 28, 29, 31, 35, 39, 43, 47, 52, 56, 58, 60, 61, 62,
            62, 62, 61, 60, 58, 56, 54, 52, 51, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 32,
            31, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 30, 32, 36, 40, 44, 49, 53, 57, 59, 61, 62, 62, 62, 62, 61,
            60, 58, 56, 54, 52, 51, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 37, 36, 35, 35, 34, 33, 33, 32, 32, 32, 31, 31, 30,
            30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 29, 30, 33, 37, 40, 45, 50, 54, 57, 59, 61, 62, 62, 62, 62, 61, 60, 58,
            56, 54, 52, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 35, 34, 34, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30,
            29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 28, 32, 35, 39, 43, 48, 52, 55, 58, 60, 61, 61, 61, 61, 60, 59, 57, 55, 53, 52, 50, 48,
            47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28,
            28, 28, 27, 27, 27, 27, 27, 28, 29, 32, 36, 40, 44, 48, 52, 55, 58, 59, 60, 60, 60, 59, 58, 56, 54, 52, 50, 48, 47, 46, 45, 44,
            42, 41, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 26,
            26, 26, 27, 29, 32, 36, 40, 44, 48, 52, 55, 57, 58, 58, 58, 58, 57, 56, 54, 52, 50, 48, 46, 45, 44, 43, 42, 42, 41, 40, 40, 39,
            38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 29,
            31, 34, 38, 42, 46, 51, 54, 57, 58, 59, 60, 60, 60, 59, 58, 56, 54, 52, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38,
            38, 37, 36, 36, 35, 34, 34, 33, 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 28, 28, 28, 28, 28, 29, 31, 34, 38, 41,
            46, 51, 54, 57, 59, 61, 61, 62, 62, 61, 60, 59, 57, 55, 53, 51, 49, 48, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 36, 36, 35, 34,
            33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27, 27, 28, 29, 32, 36, 40, 44, 49, 53,
            56, 58, 60, 61, 61, 61, 61, 60, 57, 55, 53, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 32, 32,
            31, 31, 31, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 29, 31, 35, 39, 43, 47, 52, 55, 57, 59, 59,
            60, 60, 59, 59, 57, 56, 54, 51, 49, 48, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 34, 34, 33, 32, 32, 32, 31, 31,
            31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 28, 28, 29, 32, 35, 39, 43, 48, 52, 55, 57, 59, 60, 60, 60, 60,
            59, 57, 56, 54, 52, 50, 48, 46, 45, 44, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 32, 31, 31, 31, 30,
            30, 30, 30, 29, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 29, 31, 34, 38, 41, 46, 51, 54, 57, 59, 60, 60, 60, 60, 59, 58, 57, 55,
            53, 51, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29,
            29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 28, 30, 32, 35, 40, 43, 48, 52, 55, 58, 59, 60, 61, 61, 60, 59, 56, 54, 52, 50, 48,
            47, 46, 45, 44, 43, 42, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 32, 32, 31, 31, 30, 30, 30, 29, 29, 29, 28, 28, 28, 28, 28, 27,
            27, 27, 27, 26, 26, 26, 26, 26, 28, 30, 34, 38, 42, 47, 51, 54, 56, 58, 58, 59, 58, 58, 57, 55, 54, 52, 50, 48, 46, 45, 44, 43,
            43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 34, 34, 33, 33, 33, 32, 32, 32, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 28, 28,
            28, 27, 28, 28, 30, 33, 37, 40, 45, 49, 53, 55, 57, 58, 58, 58, 58, 57, 56, 54, 53, 51, 49, 47, 46, 45, 44, 44, 43, 42, 42, 41,
            40, 40, 39, 38, 37, 37, 36, 35, 35, 34, 34, 33, 33, 32, 32, 32, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 29,
            31, 33, 37, 40, 44, 49, 53, 56, 58, 60, 60, 61, 61, 60, 60, 58, 57, 55, 53, 51, 49, 48, 46, 45, 44, 43, 42, 42, 41, 40, 39, 37,
            36, 35, 35, 34, 33, 33, 32, 32, 32, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 29, 31, 34, 37, 41, 45,
            50, 54, 57, 59, 60, 61, 61, 61, 60, 59, 58, 56, 54, 52, 50, 48, 47, 44, 43, 43, 42, 41, 40, 39, 38, 37, 37, 36, 35, 34, 34, 33,
            33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 27, 27, 27, 28, 29, 34, 38, 41, 46, 50, 53, 56, 57, 58,
            59, 59, 59, 58, 57, 55, 53, 51, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31,
            30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 28, 30, 32, 36, 40, 44, 48, 52, 55, 56, 57, 58, 58, 57, 56,
            55, 54, 52, 50, 48, 46, 45, 44, 43, 43, 42, 41, 40, 40, 40, 39, 38, 37, 36, 36, 35, 35, 34, 33, 33, 33, 32, 32, 32, 31, 31, 31,
            31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 30, 32, 35, 39, 43, 47, 52, 55, 58, 59, 61, 61, 61, 61, 60, 59, 57, 56, 54, 52,
            50, 48, 47, 46, 45, 44, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 31, 31, 30, 30, 30, 30,
            30, 29, 29, 29, 29, 29, 29, 30, 31, 34, 38, 42, 46, 51, 55, 58, 60, 61, 61, 62, 62, 61, 60, 59, 57, 55, 53, 51, 48, 47, 46, 45,
            44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28,
            28, 29, 33, 37, 40, 45, 49, 54, 57, 59, 61, 61, 62, 61, 60, 59, 57, 55, 53, 51, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39,
            38, 37, 36, 36, 35, 34, 34, 33, 33, 32, 32, 32, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 28, 28, 29, 30, 32, 35, 39,
            43, 48, 52, 56, 58, 60, 61, 62, 62, 61, 61, 59, 58, 56, 54, 51, 50, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 38, 37, 36, 36, 35,
            34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 29, 30, 33, 36, 40, 44, 49, 53, 56,
            59, 60, 61, 62, 62, 61, 60, 59, 57, 55, 53, 51, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 35, 34, 34, 33,
            32, 32, 32, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 29, 31, 33, 37, 41, 45, 50, 54, 57, 59, 61, 61, 62,
            62, 61, 60, 59, 57, 55, 52, 50, 49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31,
            31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 29, 31, 33, 37, 41, 46, 50, 54, 57, 60, 61, 62, 62, 62, 61, 60,
            59, 57, 55, 53, 51, 49, 48, 47, 46, 45, 44, 42, 41, 40, 39, 38, 37, 36, 35, 35, 34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30,
            30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 29, 30, 32, 35, 39, 42, 47, 52, 56, 58, 60, 61, 62, 62, 62, 61, 60, 58, 56, 54, 52, 50,
            49, 47, 46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 35, 34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29,
            29, 29, 29, 28, 28, 28, 28, 28, 29, 31, 34, 38, 42, 47, 51, 55, 58, 60, 61, 62, 62, 62, 61, 60, 59, 57, 54, 52, 50, 49, 47, 46,
            45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 36, 34, 34, 33, 33, 32, 32, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 28, 28,
            28, 28, 28, 28, 29, 31, 34, 38, 42, 47, 51, 55, 58, 60, 61, 62, 62, 62, 61, 60, 58, 56, 54, 52, 50, 48, 47, 46, 45, 44, 43, 42,
            41, 40, 40, 39, 38, 37, 36, 35, 35, 34, 33, 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29,
          ]
    pp  = a_peak_finder()
    ra  = []
    for s in sa :
        rv  = pp.append(s)
        # print "%5u %3u [ %s, %s, %s ]" % ( pp.cnt - 1, s, ((rv[0] is None) and "None") or ("%4u" % rv[0]), ((rv[1] is None) and "None") or ("%4u" % rv[1]), ((rv[2] is None) and "None") or ("%4u" % rv[2]), ), " " * s, "*",
        if  rv[0] is not None :
            ra.append(rv)
            # print "--------", pp.llow, pp.aamp,
        # print
        pass
    # print repr(ra)
    if  ra != [
                (23, 0, 23),
                (72, 14, 23),
                (29, 45, 72),
                (70, 57, 29),
                (20, 94, 70),
                (70, 106, 20),
                (21, 156, 70),
                (79, 172, 21),
                (26, 203, 79),
                (74, 215, 26),
                (16, 252, 74),
                (73, 265, 16),
                (22, 309, 73),
                (49, 318, 22),
                (26, 344, 49),
                (87, 357, 26),
                (14, 407, 87),
                (93, 433, 14),
                (29, 476, 93),
                (60, 490, 29),
                (28, 548, 60),
                (59, 561, 28),
                (30, 613, 59),
                (66, 627, 30),
                (28, 671, 66),
                (65, 684, 28),
                (25, 767, 65),
                (59, 780, 25),
                (28, 845, 59),
                (72, 884, 28),
                (30, 928, 72),
                (62, 942, 30),
                (25, 983, 62),
                (62, 997, 25),
                (31, 1038, 62),
                (62, 1051, 31),
                (26, 1126, 62),
                (63, 1150, 26),
                (27, 1172, 63),
                (66, 1182, 27),
                (22, 1209, 66),
                (75, 1220, 22),
                (29, 1247, 75),
                (82, 1260, 29),
                (37, 1281, 82),
                (75, 1292, 37),
                (11, 1342, 75),
                (97, 1372, 11),
                (29, 1417, 97),
                (61, 1429, 29),
                (27, 1469, 61),
                (57, 1483, 27),
                (28, 1524, 57),
                (59, 1536, 28),
                (32, 1575, 59),
                (64, 1590, 32),
                (29, 1631, 64),
                (62, 1643, 29),
                (27, 1684, 62),
                (60, 1699, 27),
                (27, 1738, 60),
                (60, 1754, 27),
                (30, 1794, 60),
                (65, 1808, 30),
                (28, 1851, 65),
                (63, 1864, 28),
                (27, 1909, 63),
                (62, 1923, 27),
                (26, 1965, 62),
                (60, 1980, 26),
                (28, 2023, 60),
                (64, 2036, 28),
                (29, 2079, 64),
                (66, 2095, 29),
                (27, 2138, 66),
                (64, 2153, 27),
                (26, 2197, 64),
                (62, 2212, 26),
                (25, 2258, 62),
                (61, 2271, 25),
                (30, 2314, 61),
                (67, 2330, 30),
                (28, 2373, 67),
                (66, 2388, 28),
                (27, 2434, 66),
                (65, 2449, 27),
                (27, 2493, 65),
                (64, 2508, 27),
                (27, 2552, 64),
                (63, 2569, 27),
                (27, 2613, 63),
                (64, 2631, 27),
                (26, 2676, 64),
                (63, 2689, 26),
                (26, 2737, 63),
                (63, 2752, 26),
                (27, 2795, 63),
                (64, 2814, 27),
                (27, 2859, 64),
                (64, 2874, 27),
                (27, 2919, 64),
                (63, 2935, 27),
                (28, 2975, 63),
                (61, 2989, 28),
                (25, 3036, 61),
                (59, 3050, 25),
                (32, 3076, 59),
                (58, 3089, 32),
                (34, 3112, 58),
                (66, 3127, 34),
                (23, 3147, 66),
                (72, 3157, 23),
                (13, 3183, 72),
                (76, 3197, 13),
                (21, 3219, 76),
                (62, 3229, 21),
                (2, 3301, 62),
                (71, 3333, 2),
                (14, 3376, 71),
                (56, 3387, 14),
                (13, 3429, 56),
                (54, 3442, 13),
                (16, 3486, 54),
                (59, 3501, 16),
                (18, 3545, 59),
                (65, 3558, 18),
                (19, 3600, 65),
                (69, 3615, 19),
                (16, 3662, 69),
                (67, 3675, 16),
                (16, 3721, 67),
                (75, 3735, 16),
                (23, 3777, 75),
                (85, 3790, 23),
                (19, 3836, 85),
                (87, 3851, 19),
                (16, 3898, 87),
                (84, 3912, 16),
                (16, 3960, 84),
                (84, 3974, 16),
                (16, 4021, 84),
                (82, 4036, 16),
                (15, 4084, 82),
                (79, 4097, 15),
                (17, 4140, 79),
                (85, 4154, 17),
                (22, 4199, 85),
                (95, 4213, 22),
                (29, 4257, 95),
                (67, 4273, 29),
                (28, 4316, 67),
                (65, 4334, 28),
                (27, 4380, 65),
                (65, 4397, 27),
                (27, 4445, 65),
                (64, 4459, 27),
                (27, 4509, 64),
                (64, 4525, 27),
                (27, 4575, 64),
                (64, 4590, 27),
                (26, 4640, 64),
                (61, 4653, 26),
                (27, 4700, 61),
                (60, 4715, 27),
                (28, 4762, 60),
                (65, 4778, 28),
                (30, 4822, 65),
                (68, 4838, 30),
                (27, 4885, 68),
                (66, 4902, 27),
                (27, 4947, 66),
                (65, 4965, 27),
                (27, 5010, 65),
                (64, 5027, 27),
                (26, 5072, 64),
                (61, 5089, 26),
                (26, 5136, 61),
                (59, 5150, 26),
                (27, 5195, 59),
                (63, 5210, 27),
                (30, 5254, 63),
                (67, 5269, 30),
                (28, 5313, 67),
                (65, 5330, 28),
                (27, 5375, 65),
                (63, 5391, 27),
                (27, 5437, 63),
                (63, 5453, 27),
                (27, 5499, 63),
                (62, 5514, 27),
                (27, 5562, 62),
                (62, 5575, 27),
                (27, 5624, 62),
                (62, 5637, 27),
                (27, 5685, 62),
                (62, 5699, 27),
                (27, 5745, 62),
                (62, 5759, 27),
                (28, 5803, 62),
                (62, 5819, 28),
                (28, 5864, 62),
                (62, 5881, 28),
                (28, 5924, 62),
                (61, 5940, 28),
                (27, 5986, 61),
                (60, 6002, 27),
                (26, 6047, 60),
                (58, 6060, 26),
                (27, 6105, 58),
                (60, 6122, 27),
                (28, 6166, 60),
                (62, 6183, 28),
                (27, 6228, 62),
                (61, 6243, 27),
                (27, 6288, 61),
                (60, 6304, 27),
                (27, 6351, 60),
                (60, 6364, 27),
                (28, 6409, 60),
                (60, 6424, 28),
                (28, 6469, 60),
                (61, 6487, 28),
                (26, 6531, 61),
                (59, 6547, 26),
                (27, 6593, 59),
                (58, 6605, 27),
                (29, 6650, 58),
                (61, 6667, 29),
                (29, 6709, 61),
                (61, 6725, 29),
                (27, 6770, 61),
                (59, 6784, 27),
                (27, 6827, 59),
                (58, 6844, 27),
                (29, 6887, 58),
                (61, 6902, 29),
                (29, 6945, 61),
                (62, 6963, 29),
                (28, 7007, 62),
                (62, 7020, 28),
                (28, 7065, 62),
                (62, 7079, 28),
                (28, 7123, 62),
                (62, 7139, 28),
                (28, 7183, 62),
                (62, 7199, 28),
                (28, 7243, 62),
                (62, 7259, 28),
                (28, 7303, 62),
                (62, 7318, 28),
                (28, 7363, 62),
                (62, 7379, 28),
                (28, 7422, 62),
                (62, 7439, 28)
              ] :
        raise ValueError("a_peak_finder error")


    for tt in xrange(1) :
        wa  = [ 500, 1, 1, 1000, 1, 0, 150, 1 ]
        c   = weighted_choice(wa)
        a   = [ c.next() for i in xrange(100) ]
        ca  = [ 0 ] * len(wa)
        for v in a :
            ca[v] += 1
        if  max(ca) != ca[3] :
            raise ValueError("Probably just a random thing: 1000 isn't chosen the most! %u %s" % ( tt, str(ca) ) )
        if  ca[0] <= ca[6] :
            raise ValueError("Probably just a random thing: 500 isn't chosen more than 150! %u %s" % ( tt, str(ca) ) )
        if  ca[6] <= ca[1] + ca[2] + ca[4] + ca[5] + ca[7] :
            raise ValueError("Probably just a random thing: 150 isn't chosen more than the 1's! %u %s" % ( tt, str(ca) ) )

    for tt in xrange(10) :
        cnt = 10000.0
        wa  = [ -1000, 0, 0, 0, 0 ]
        c   = weighted_choice(wa)
        a   = [ c.next() for i in xrange(int(cnt)) ]
        ca  = [ 0 ] * len(wa)
        for v in a :
            ca[v] += 1
        gv  = cnt / len(wa)
        for v in ca :
            if  abs(v - gv) > gv / 10 :
                raise ValueError("weighted_choice of zero'd array with a negative weight is unbalanced %u %s!" % ( tt, str(ca) ) )
            pass
        pass

    for tt in xrange(1) :
        cnt = 10000.0
        wa  = [ 0, 0, 0, 0, 0 ]
        c   = weighted_choice(wa)
        a   = [ c.next() for i in xrange(int(cnt)) ]
        ca  = [ 0 ] * len(wa)
        for v in a :
            ca[v] += 1
        gv  = cnt / len(wa)
        for v in ca :
            if  abs(v - gv) > gv / 10 :
                raise ValueError("weighted_choice of zero'd array is unbalanced %u %s!" % ( tt, str(ca) ) )
            pass
        pass


    for ti in xrange(100) :
        k   = random.randint(5, 35)
        pa  = [ random.randint(100, 135) for i in range(35) ]
        ka  = kmeans_cluster(pa, k, pass_count = 100)
        kaa = list(ka)
        sort_kmeans_clusters(kaa, pa)
        kak = list(ka)
        sort_kmeans_clusters(kak, pa, k)
        if  False :
            print [ "%3u" % v for v in pa  ]
            print [ "%3u" % v for v in ka  ]
            print [ "%3u" % v for v in kaa ]
            print [ "%3u" % v for v in kak ]
        m   = 0
        for i in xrange(k) :
            a   = [ pa[pi] for pi, v in enumerate(kaa) if v == i ]
            if  min(a + [ m + 1 ]) <= m :
                s   = "Out of order kmeans %u for value %u!" % ( k, m, )
                raise ValueError(s)
            if  len(a) :
                m   = max(a)
            pass
        m   = 0
        for i in xrange(k) :
            a   = [ pa[pi] for pi, v in enumerate(kak) if v == i ]
            if  min(a + [ m + 1 ]) <= m :
                s   = "Out of order kmeans %u for value %u!" % ( k, m, )
                raise ValueError(s)
            if  len(a) :
                m   = max(a)
            pass
        if  kak != kaa :
            raise ValueError("Two sorted kmeans cluster mappings were not the same!")
        pass



    if  restricted_eval("pi") != math.pi :
        raise ValueError("restricted_eval of pi failed!")
    if  restricted_eval(" sin(2)") != math.sin(2) :
        raise ValueError("restricted_eval of sin() failed!")
    if  restricted_eval(" fac(8)") != restricted_fac(8) :
        raise ValueError("restricted_eval of fac() failed!")
    if  restricted_eval(" len('a')") != 1 :
        raise ValueError("restricted_eval of len('a') failed!")
    try :
        restricted_eval("blow('a')")
        s   = "restricted_eval of blow('a') failed!"
    except ValueError :
        s   = None
    if  s   :
        raise ValueError(s)

    ld  = restricted_exec("x = 5\ny = x\nz = 'xyzzy'\n")
    if  ld.get('x', None) != 5 :
        raise ValueError("restricted_exec x wrong [%s]!" % str(ld.get('x', None)))
    if  ld.get('y', None) != 5 :
        raise ValueError("restricted_exec y wrong [%s]!" % str(ld.get('y', None)))
    if  ld.get('z', None) != 'xyzzy' :
        raise ValueError("restricted_exec z wrong [%s]!" % str(ld.get('z', None)))


    tfn = "tzlib.tmp"
    d   = { 'x' : [ 2, 3, 4 ], 5 : { 'sdf' : 4.5, }, }
    if  not pickle_file(tfn, d) :
        raise ValueError("Pickle error")

    dd  = unpickle_file(tfn)
    os.remove(tfn)
    if  d != dd :
        raise ValueError("Pickle/unpickle error: %s != %s" % ( str(d), str(dd) ))

    tfn = "tzlib.tmp"
    d   = { 'x' : [ 2, 3, 4 ], 5 : { 'sdf' : 4.5, }, }
    if  not pickle_file(tfn, d, sys.maxsize) :
        raise ValueError("Pickle error")

    dd  = unpickle_file(tfn)
    os.remove(tfn)
    if  d != dd :
        raise ValueError("Pickle/unpickle error: %s != %s" % ( str(d), str(dd) ))

    tfn = "tzlib.tmp"
    d   = { 'x' : [ 2, 3, 4 ], 5 : { 'sdf' : 4.5, }, }
    if  not pickle_file(tfn, d, -1) :
        raise ValueError("Pickle error")

    dd  = unpickle_file(tfn)
    os.remove(tfn)
    if  d != dd :
        raise ValueError("Pickle/unpickle error: %s != %s" % ( str(d), str(dd) ))


    tfn = "tzlib.tmp"
    write_whole_binary_file(tfn, "blah blah")
    if  not os.path.isfile(tfn) :
        raise ValueError("Cannot write 'tzlib.tmp' for whacking test!")
    whack_file(tfn)
    if  os.path.exists(tfn) :
        raise ValueError("Cannot whack 'tzlib.tmp'!")

    dn      = 'tmpamb'
    safe_makedirs(dn)
    for fn in [ 'tmp.tmp', 'tmp.tmp.bak', 'tmp.tmp.000.bak', 'tmp.tmp.001.bak', ] :
        fo  = open(os.path.join(dn, fn), 'w')
        fo.write(fn)
        fo.close()
    if  not whack_files(os.path.join(dn, 'tmp.tmp*')) :
        raise ValueError("Cannot whack %s files!" % dn)
    os.rmdir(dn)


    print "Full user name:", get_full_user_name()

    print "Small ID: %08x   UID: %s" % ( create_small_id(), create_uid(), )

    if  not get_system_wide_lock('tzlib') :
        raise ValueError("Got wrong system wide lock didn't succeed!")
    if  get_system_wide_lock() :
        raise ValueError("Got double system wide lock should not have been possible!")

    if  release_system_wide_lock(swlock) :
        raise ValueError("Release system wide lock failed!")

    swlock  = get_system_wide_lock()
    if  not swlock :
        raise ValueError("Get system wide lock failed!")


    fn  = 'tzlib.py'
    r   = split_path(fn)
    if  r != [ fn ] :
        raise ValueError("split_path('%s') is wrongly %s!" % ( fn, r, ))
    fn  = ''
    r   = split_path(fn)
    if  r != [] :
        raise ValueError("split_path('%s') is wrongly %s!" % ( fn, r, ))
    for fns in [
                [ '..', 'tzlib', 'tzlib.py' ],
                [ '/',  'etc',              ],
                [ '/',                      ],
                [ 'C:', 'Users',            ],
                [ 'C:',                     ],
               ] :
        fn  = os.path.join(*fns)
        r   = split_path(fn)
        if  r != fns :
            raise ValueError("split_path('%s') is wrongly %s!" % ( fn, r, ))
        pass

    fns     = [
                [ "blah", "ding", "bong", "flap", ],
                [ "blah", "ding", "bong", "zlap", ],
                [ "blah", "ding", "zong", "zlap", ],
                [ "blah", "ding", "zong", "zlap", ],
              ]
    r   = common_path([ os.path.join(*sfn) for sfn in fns ])
    if  r != os.path.join("blah", "ding") + os.path.sep :
        raise ValueError("common_path is wrong %s!" % str(r))
    r   = common_path([])
    if  r != "" :
        raise ValueError("common_path of [] is wrong %s!" % r)
    fn  = os.path.join('one', 'two')
    r   = common_path([ fn ])
    if  r != fn :
        raise ValueError("common_path of %s is wrong %s!" % ( fn, r, ))


    dnn     = 'sha_dir1/blah/badabing/'
    if  not safe_makedirs(dnn) :
        raise ValueError("Could not safe_make_dir('%s')!" % dnn)
    dnn     = 'sha_dir2/blah/badabing'
    if  not safe_makedirs(dnn) :
        raise ValueError("Could not safe_make_dir('%s')!" % dnn)


    dns     = [ 'sha_dir1', 'sha_dir2' ]

    for dn in dns : whack_full_dir(dn)

    fns     = [ 'tmp1.tmp', 'tmp2.tmp', 'tmp3.tmp', 'tmx1.tmp', 'tmx2.tmp', 'tmx3.tmp', 'tmy1.tmp', ]
    nws     = [ 'new1.tmp', 'new2.tmp', ]
    sdrs    = []
    for dn in dns :
        os.mkdir(dn)
        for fn in fns :
            write_whole_text_file(os.path.join(dn, fn), "%08x\n" % blkcrc32(INITIAL_CRC32_VALUE, fn[:3] + (((fn[2:4] == 'x2') and dn) or '')))
        for fn in nws :
            write_whole_text_file(os.path.join(dn, fn), "%08x\n" % blkcrc32(INITIAL_CRC32_VALUE, fn[:3] + (((fn[2:4] == 'x2') and dn) or '')))
        nws = []
        sdrs.append(sha_directory(dn))

    df, cf  = sha_dir_compare(sdrs[0], sdrs[1])

    print "Sha dir new files:", df
    print "Sha collide files:", cf

    if  sorted([ os.path.basename(fn) for fn in df ]) != sorted([ 'new1.tmp', 'new2.tmp', ]) :
        raise ValueError("sha dir new files: %s !" % str(df))
    if  sorted([ os.path.basename(fn) for fn in cf ]) != sorted([ 'tmx2.tmp', ]) :
        raise ValueError("sha dir new files: %s !" % str(cf))

    # print sorted(sdrs[0].keys())
    # print sorted(sdrs[1].keys())

    if  safe_file_datetime('lblahlkjsdlfkjlsdf') is not None :
        raise ValueError("safe_file_datetime not None for non-existent file!")
    if  safe_file_datetime('tzlib.py') <= 0 :
        raise ValueError("safe_file_datetime for tzlib.py not positive!")

    if  safe_file_size('lkjsdlfkjskjdfl') is not None :
        raise ValueError("safe_file_size not None for non-existent file!")
    if  safe_file_size('tzlib.py') <= 0 :
        raise ValueError("safe_file_size for tzlib.py not positive!")

    ffn = os.path.join(dns[0], "make_numbered_file_test_1.txt")
    nfn = make_numbered_file(ffn)
    if  nfn != ffn  :
        raise ValueError("make_numbered_file file %s is not same as %s!" % ( nfn, ffn, ))
    safe_write_whole_binary_file(nfn, "")
    nfn = make_numbered_file(ffn)
    if  nfn == ffn  :
        raise ValueError("make_numbered_file file %s is same as %s!" % ( nfn, ffn, ))
    safe_write_whole_binary_file(nfn, "")
    nff, i  = make_numbered_file(ffn, 0)
    if  nff == ffn  :
        raise ValueError("make_numbered_file(0) file %s is same as %s!" % ( nff, ffn, ))
    if  nff == nfn  :
        raise ValueError("make_numbered_file(0) file %s is same as %s!" % ( nff, nfn, ))
    if  i != 2      :
        raise ValueError("make_numbered_file(0) file %s should be sub-numbered 2 but is %u!" % ( nff, i, ))
    nff, i  = make_numbered_file(ffn, 1)
    if  nff == ffn  :
        raise ValueError("make_numbered_file(1) file %s is same as %s!" % ( nff, ffn, ))
    if  nff == nfn  :
        raise ValueError("make_numbered_file(1) file %s is same as %s!" % ( nff, nfn, ))
    if  i != 2      :
        raise ValueError("make_numbered_file(1) file %s should be sub-numbered 2 but is %u!" % ( nff, i, ))

    msc, osc    = ladder_score(1000, 1180)
    if  (msc != 1095) or (osc != 1132.5) :
        raise ValueError("ladder score 1000 beat 1180 should be 1095:1132.5 not %6.1f:%6.1f!" % ( msc, osc, ))
    msc, osc    = ladder_score(1180, 1000)
    if  (msc != 1190) or (osc != 995) :
        raise ValueError("ladder score 1180 beat 1000 should be 1190:995 not %6.1f:%6.1f!" % ( msc, osc, ))

    for dn in dns : whack_full_dir(dn)

    for dn in dns :
        if  os.path.exists(dn) :
            raise ValueError("SHA directory should have been whacked %s!" % dn)
        pass

    if  int(str_base(123456789012345, 35), 35) != 123456789012345 :
        raise ValueError("str_base(123456789012345)")
    if  int(str_base(9876543210987, 36), 36) != 9876543210987 :
        raise ValueError("str_base(123456789012345)")
    print str_base(12345678909876543210, 36), 'base36 ==', str_base(12345678909876543210), "base10"

    print svn_info() and dict(svn_info())
    d   = svn_simple()
    if  d :
        d['password']   =   "what_kind_of_fool_am_i?"
        print d

    print "Split our own path on spring      ", split_path('tzlib.py')
    print "Split our own path on spring      ", split_path(os.path.join('~', 'flight', 'tzpython', 'tzlib.py'))
    print "All drive paths:                  ", get_all_drive_paths()
    print "Disk space for system drive:      ", get_disk_space()
    print "User home mount point:            ", get_mount_point(expand_user_vars("~/"))
    print "User home no-such-dir mount point:", get_mount_point(expand_user_vars("~/blah_blah_blah/"))



if  __name__ == '__main__' :
    test()

#
#
# eof
