# -*- coding: utf-8 -*-
"""
:Id: piecewise.py
:Author: Philippe Guglielmetti <drgoulu@gmail.com>
:Copyright: 2013 Philippe Guglielmetti
:Licence: GNU Lesser General Public Licence (LGPL)
:Synposis: Piecewise defined function
"""
import bisect
import operator
from itertools import izip
[docs]class Piecewise(object):
"""
piecewise function defined by a sorted list of (startpoint,value)
"""
[docs] def __init__(self,init=[],default=0,start=-float('Inf')):
#Note : started by deriving a list of (point,value), but this leads to a problem:
# the value is taken into account in sort order by bisect
# so instead of defining one more class with a __cmp__ method, I split both lists
try: #copy constructor ?
self.x=list(init.x)
self.y=list(init.y)
except:
self.x=[start]
self.y=[default]
self.extend(init)
[docs] def __call__(self,x):
"""returns value of function at point x """
i=bisect.bisect_right(self.x,x)-1
if i<1 : #ignore the first x value
return self.y[0] #this is the default value right
return self.y[i]
[docs] def index(self,x,v=None):
"""finds an existing point or insert one and returns index"""
i=bisect.bisect_left(self.x,x)
if i<len(self) and x==self.x[i]:
return i
#insert either the v value, or copy the current value at x
self.y.insert(i,v if v is not None else self(x))
self.x.insert(i,x)
return i
[docs] def append(self, item):
"""appends a (x,y) item. In fact inserts it at correct position and returns the corresponding index"""
return self.index(item[0],item[1])
[docs] def extend(self,iterable):
"""appends an iterable of (x,y) values"""
for p in iterable:
self.append(p)
[docs] def __len__(self):
return len(self.x)
[docs] def __getitem__(self, i):
return (self.x[i],self.y[i])
[docs] def __iter__(self):
return izip(self.x,self.y)
[docs] def list(self):
return [x for x in self]
[docs] def __str__(self):
return str(self.list())
def _combine(self,other,op):
if isinstance(other,Piecewise):
for i,p in enumerate(other):
try:
self._combine((p[0],p[1],other[i+1][0]),op)
except:
self._combine((p[0],p[1]),op)
else: #assume a triplet (start,value,end) as called above
i=self.index(other[0])
try:
j=self.index(other[2])
except:
j=len(self)
for k in range(i,j):
self.y[k]=op(self.y[k],other[1])
return self
[docs] def __add__(self,other):
return Piecewise(self)._combine(other,operator.add)
[docs] def __sub__(self,other):
return Piecewise(self)._combine(other,operator.sub)
[docs] def __mul__(self,other):
return Piecewise(self)._combine(other,operator.mul)
[docs] def __div__(self,other):
return Piecewise(self)._combine(other,operator.div)
[docs] def __and__(self,other):
return Piecewise(self)._combine(other,operator.and_)
[docs] def __or__(self,other):
return Piecewise(self)._combine(other,operator.or_)
[docs] def __xor__(self,other):
return Piecewise(self)._combine(other,operator.xor)
[docs] def apply(self,f):
""" apply a function to each piece """
self.y=[f(v) for v in self.y]
return self
[docs] def __neg__(self):
return Piecewise(self).apply(operator.neg)
[docs] def __not__(self):
return Piecewise(self).apply(operator.not_)
def _opt(self):
"""removes redundant data"""
i=1
while i<len(self.x):
if self.y[i]==self.y[i-1]:
self.y.pop(i)
self.x.pop(i)
else:
i+=1
return
[docs] def applx(self,f):
""" apply a function to each x value """
self.x=[f(x) for x in self.x]
return self
[docs] def __lshift__(self,dx):
return Piecewise(self).applx(lambda x:x-dx)
[docs] def __rshift__(self,dx):
return Piecewise(self).applx(lambda x:x+dx)
[docs] def lines(self,min=0,max=None,eps=0):
"""@return x and y for a line plot"""
self._opt()
resx=[]
resy=[]
try:
if min<self.x[1]:
resx.append(min)
resy.append(self(min))
except: pass
for i in range(1,len(self.x)):
resx.append(self.x[i]-eps)
resy.append(self.y[i-1])
resx.append(self.x[i])
resy.append(self.y[i])
if max and max>self.x[-1]:
resx.append(max)
resy.append(self(max))
return resx,resy
import unittest
[docs]class TestCase(unittest.TestCase):
[docs] def setUp(self):
import markup
self.page=markup.page()
self.page.init(
doctype="Content-Type: text/html; charset=utf-8\r\n\r\n<!DOCTYPE html>",
script=["http://nvd3.org/lib/d3.v2.js","http://nvd3.org/nv.d3.js"], #must be a list to preserve order
css=['http://nvd3.org/src/nv.d3.css']
)
[docs] def runTest(self):
from nvd3 import LineChart
from colors import color_range
fig=LineChart(height=400,colors=color_range(6,'red','blue'))
def add(p,name,min=0,max=10,disabled=False):
print name,'=',p,'<br/>'
x,y=p.lines(min=min,max=max)
fig.add(x=x,y=y,name=name,disabled=disabled)
p1=Piecewise([(4,4),(3,3),(1,1),(5,0)])
self.assertEqual(str(p1),'[(-inf, 0), (1, 1), (3, 3), (4, 4), (5, 0)]')
add(p1,'p1')
p2=Piecewise(default=1)
p2+=(2.5,1,6.5)
self.assertEqual(str(p2),'[(-inf, 1), (2.5, 2), (6.5, 1)]')
add(p2,'p2')
add(p1+p2,'p1+p2',disabled=True)
add(p1-p2,'p1-p2',disabled=True)
add(p1*p2,'p1*p2',disabled=True)
p1.apply(float) #to make division correct
add(p1/p2,'p1/p2',disabled=True)
self.page.add(str(fig))
dx=0.05 #small shift to see curves better
fig=LineChart(colors=fig.colors)
b1=Piecewise([(2,True)],False)
add(b1,'b1')
b2=Piecewise([(1,True),(2,False),(3,True)],False)
add(b2<<dx,'b2')
add((b1 | b2)>>dx,'b1 or b2',disabled=True)
add((b1 & b2)>>2*dx,'b1 and b2',disabled=True)
add((b1 ^ b2)<<3*dx,'b1 xor b2',disabled=True)
self.page.add(str(fig))
return
from datetime import datetime,timedelta
self.ptime=Piecewise([(timedelta(hours=4),4),(timedelta(hours=3),3),(timedelta(hours=1),1),(timedelta(hours=2),2),(timedelta(hours=5),0)])
[docs] def tearDown(self):
print self.page
if __name__ == '__main__':
unittest.main()