Source code for numeric

from cell import *
import numpy as np

[docs]class IntervalCell(Cell): """ Implements an interval cell along with interval algebra """ def __init__(self, low=None, high=None): """ Creates a new IntervalCell with values restricted to between *low* and *high*. :param low,high: The boundaries of the interval :type low,high: float,int :raises: CellConstructionFailure """ self.low = -np.inf self.high = np.inf if low is not None: self.low = low if high is not None: self.high = high if self.high < self.low: raise CellConstructionFailure @staticmethod
[docs] def coerce(value): """ Takes in either a single number (float, int), a list, or a tuple and returns the [low, high] in the standard interval form. In the case of a single *value*, the low and high will both be the single value. In the case of a list or tuple, the low and high will be the min and max of the collection, respectively. :param value: The the number or collection of numbers to be used for the low and high boundaries :type value: int, float, complex, list, tuple :returns: IntervalCell :raises: Exception >>> a = IntervalCell.coerce(5) >>> a 5.00 >>> b = IntervalCell.coerce([3,7]) >>> b [3.00,7.00] >>> c = IntervalCell.coerce((1,6,5,9,2)) >>> c [1.00,9.00] """ is_number = lambda x: isinstance(x, (int, float, complex)) #is x an instance of those things if isinstance(value, IntervalCell) or issubclass(value.__class__,IntervalCell): #if intervalcell or subclass, return the subclass return value elif is_number(value): return IntervalCell(value, value) elif hasattr(value, 'low') and hasattr(value, 'high'): # duck type assert value.low <= value.high, "Invalid low/high in %s" % str(value) return value elif isinstance(value, (list, tuple)) and all(map(is_number, value)): if len(value) == 1: low, high = value[0], value[0] elif len(value) == 2: low, high = value else: low, high = min(value), max(value) if high < low: raise Contradiction("Low must be lte High") return IntervalCell(low, high) else: raise Exception("Don't know how to coerce %s" % (type(value)))
[docs] def stem(self): """ Creates a new instance with bounds of 0 to infinity :returns: IntervalCell >>> x = IntervalCell(1,1) >>> y = x.stem() >>> y [0.00,inf] """ return self.__class__(0, np.inf)
[docs] def size(self): """ Number of possible values in the interval. Boundaries are inclusive :returns: int -- Length of interval >>> a = IntervalCell(1,2) >>> b = IntervalCell(12,19) >>> a.size() 2 >>> b.size() 8 """ return (self.high - self.low) + 1
[docs] def is_contradictory(self, other): """ Determines whether other and self can coexist. Two intervals are contradictory if they are disjoint, i.e. if their intersection is empty :param other: IntervalCell or coercible value :returns: bool :raises: Exception >>> w = IntervalCell(1,2) >>> x = IntervalCell(1,1) >>> y = IntervalCell(2,2) >>> z = IntervalCell(0,3) >>> x.is_contradictory(y) True >>> w.is_contradictory(z) False >>> x.is_contradictory(w) False """ other = IntervalCell.coerce(other) assert other.low <= other.high, "Low must be <= high" if max(other.low, self.low) <= min(other.high, self.high): return False else: return True
[docs] def is_entailed_by(self, other): """ Returns true if other is more specific than self, i.e. other is bounded within self. :param other: IntervalCell or coercible value :returns: bool :raises: Exception >>> x = IntervalCell(1,1) >>> y = IntervalCell(2,2) >>> z = IntervalCell(0,3) >>> z.is_entailed_by(x) True >>> z.is_entailed_by(y) True >>> x.is_entailed_by(y) False """ other = IntervalCell.coerce(other) return other.low >= self.low and other.high <= self.high
[docs] def is_equal(self, other): """ Returns true if two intervals are the same :param other: IntervalCell or coercible value :returns: bool :raises: Exception >>> x = IntervalCell(1,1) >>> z = IntervalCell(0,3) >>> x.is_equal(1) True >>> z.is_equal([0,3]) True >>> x.is_equal(z) False """ other = IntervalCell.coerce(other) return other.low == self.low and other.high == self.high
[docs] def merge(self, other): """ Merges the two values. The *self* argument is modified, while *other* is not. :param other: IntervalCell or coercible value :returns: bool :raises: Exception >>> x = IntervalCell(0,3) >>> y = IntervalCell(2,2) >>> a = x.merge(y) >>> a 2.00 .. note:: ``merge`` modifies the *self* argument as a side effect. >>> w = IntervalCell(1,7) >>> z = IntervalCell(3,9) >>> b = w.merge(z) >>> b [3.00,7.00] w has been modified. >>> w [3.00,7.00] z has not. >>> z [3.00,9.00] """ other = IntervalCell.coerce(other) if self.is_equal(other): # pick among dependencies return self elif other.is_entailed_by(self): return self elif self.is_entailed_by(other): self.low, self.high = other.low, other.high elif self.is_contradictory(other): #import traceback #for line in traceback.format_stack(): print line.strip() raise Contradiction("Cannot merge [%0.2f, %0.2f] with [%0.2f, %0.2f]" \ % (self.low, self.high, other.low, other.high)) else: # information in both self.low = max(self.low, other.low) self.high = min(self.high, other.high) return self
def __cmp__(self, other): """ Uses 'self.low' to make the comparison Returns -1 if self < other 0 if self == other 1 if other > self """ other = IntervalCell.coerce(other) if other.low == self.low and other.high == self.high: return 0 elif self.low < other.low: return -1 else: return 1 def __repr__(self): """ Displays the interval """ if self.low == self.high: return "%0.2f" % (self.low,) else: return "[%0.2f, %0.2f]" % (self.low, self.high) def __add__(self, other): """ Interval addition """ other = IntervalCell.coerce(other) return IntervalCell(self.low + other.low, self.high + other.high) def __sub__(self, other): """ Subtraction """ return IntervalCell(self.low - other.high, self.high - other.low) def __mul__(self, other): """ Interval Multiplication """ other = IntervalCell.coerce(other) combos = [self.low * other.low, self.low * other.high, self.high * other.low, self.high * other.high] return IntervalCell(min(combos), max(combos)) def __le__(self, new_upper): """ Overload =< Note: we're overloading <= and >=, not < and >, because IntervalCell considers its boundaries as inclusive. Removing the inequality requires setting the inclusive boundary to 'one less' than the current boundary """ if isinstance(new_upper, IntervalCell): new_upper = new_upper.low return self.merge(IntervalCell(self.low, new_upper)) def __ge__(self, new_lower): """ Overload >= """ if isinstance(new_lower, IntervalCell): new_lower = new_lower.high return self.merge(IntervalCell(new_lower, self.high)) def __int__(self): """ Integer type conversion """ assert self.low == self.high, "Cannot be converted to int" return int(self.low) def __float__(self): """ Float type conversion """ assert self.low == self.high, "Cannot be converted to float" return float(self.low) def __div__(self, other): """ Division (checks for 0s) """ other = IntervalCell.coerce(other) if other.high == 0: if other.low == 0: raise ZeroDivisionError("Cannot divide by interval [0,0]") return IntervalCell(-np.inf, 1.0 / other.low) elif other.low == 0: return IntervalCell(1.0 / other.high, np.inf) return self * IntervalCell(1.0 / other.low, 1.0 / other.high)
[docs] def map(self, other, function): """ Applies a mathematic function to the interval arithmetic template: generate all combinations of low/high values and then take the widest bounds (min/max) for the result. :param other: IntervalCell or coercible value :type other: IntervalCell :param function: Mathematical function to be applied to the bounds of the interval. Must take two arguments and return a number :type function: function :returns: IntervalCell :raises: Exception >>> a = IntervalCell(1,4) >>> b = IntervalCell(3,5) >>> result = a.map(b,lambda x,y: x+y) >>> result [4.00,9.00] >>> result = a.map(b,lambda x,y: x-y) >>> result [-4.00,1.00] """ other = IntervalCell.coerce(other) # check the arity of the function import inspect arity = len(inspect.getargspec(function).args) if arity == 2: combos = [function(self.low, other.low), function(self.low, other.high), function(self.high, other.low), function(self.high, other.high)] return IntervalCell(min(combos), max(combos)) else: raise Exception("Cannot apply function of arity %i " % (arity))
def __contains__(self, other): """ Allows the use of Python's 'in' syntax """ return self.low <= other <= self.high def __hash__(self): """ Unique hash for interval """ hval = 0 hval = hash(self.low) * 2 + hash(self.high) * 3 + hash(self.high-self.low) * 5 return hval
[docs] def get_tuple(self): """ Returns (low, high) tuple >>> x = IntervalCell(1,2) >>> xTuple = x.get_tuple() >>> xTuple (1,2) """ return (self.low, self.high)
'''def to_dot(self): """ Returns a scalar or None """ if self.low == self.high: return self.low else: return None'''
[docs] def to_latex(self): """ Returns an interval representation """ if self.low == self.high: if self.low * 10 % 10 == 0: return "{0:d}".format(int(self.low)) else: return "{0:0.2f}".format(self.low) else: t = "" if self.low == -np.inf: t += r"(-\infty, " elif self.low * 10 % 10 == 0: t += r"[{0:d}, ".format(int(self.low)) else: t += r"[{0:0.2f}, ".format(self.low) if self.high == np.inf: t += r"\infty)" elif self.high * 10 % 10 == 0: t += r"{0:d}]".format(int(self.high)) else: t += r"{0:0.2f}]".format(self.high) return t
def __abs__(self): """ Absolute value """ if self.low == self.high: return abs(self.low) else: return IntervalCell(min(abs(self.low), abs(self.high)), max(abs(self.low), abs(self.high))) __eq__ = is_equal __len__ = size
if __name__ == '__main__': def test_absolute_values(): # for intervals x = IntervalCell(1,20) y = IntervalCell(19,100) assert abs(x-y) == abs(y-x) def test_absolute_values(): # for intervals x = IntervalCell(1,20) y = IntervalCell(19,100) assert abs(x-y) == abs(y-x) # for single numbers x = IntervalCell(19,19) y = IntervalCell(20,20) assert abs(x-y) == abs(y-x)
[docs]def test_hashes(): """ Tests for cases where LOW and HIGH values of the interval have the same sum, the same diff, etc. """ def alldiff(iterable, f=lambda x: x): mapped = map(f, iterable) return len(mapped) == len(set(mapped)) test_cases = [] test_cases += [IntervalCell(2,2)] test_cases += [IntervalCell()] test_cases += [IntervalCell(1,3)] test_cases += [IntervalCell(0,4)] test_cases += [IntervalCell(0,2)] test_cases += [IntervalCell(2,5)] test_cases += [IntervalCell(-3,-1)] assert alldiff(test_cases, hash) # float / int equivalence assert hash(IntervalCell(0,2)) == hash(IntervalCell(0.0,2.0))
if __name__ == '__main__': x = IntervalCell(3, 38.2) print x.to_latex() x = IntervalCell(0, 38) print x.to_latex()