| | |
Piecewise linear continuous functions.
Joining together ordered sample points (xi, yi) when ths xi's are different yields a piecewise linear continuous (P1) function. This snippet defines a handy class to manipulate these functions. It allows
computing the value and the slope of the function at each point, arithmetic operations, absolute value, truncation and linear combinations of such functions, maximum, minimum and integral between bounds. If wxPython is installed on your system, the class also allows plotting the function in a wx plot widget.
computing the value and the slope of the function at each point, arithmetic operations, absolute value, truncation and linear combinations of such functions, maximum, minimum and integral between bounds. If wxPython is installed on your system, the class also allows plotting the function in a wx plot widget.
#!/usr/bin/env python # Gribouillis at www.daniweb.com # module P1Function.py # type "help(P1Function)" in a python shell for class documentation """A module implementing piecewise linear continuous functions. """ from bisect import bisect_left ,bisect_right from collections import defaultdict class P1Function (object ): """P1Function(seq) -> new piecewise linear continuous function object 'seq' is an iterable over pairs of numbers (x, y). Raises ValueError if one of the numbers is not convertible to float or when 2 points have the same x but not the same y. """ _bisect =(bisect_left ,bisect_right ) _eps =1.0e-9 def __init__ (self ,seq ): pairs =sorted (set (seq )) pairs =[(float (x ),float (y ))for x ,y in pairs ] if len (pairs )<2 : if pairs : pairs .append ((1.0 +2 *abs (pairs [0 ][0 ]),pairs [0 ][1 ])) else : pairs =[(x ,0.0 )for x in self .float_range (0.0 ,1.0 ,2 )] tmp =[pairs [0 ]] for i in xrange (1 ,len (pairs )): x ,y =tmp [-1 ] if pairs [i ][0 ]==x : if pairs [i ][1 ]!=y : raise ValueError ("Sample points aligned vertically") else : tmp .append (pairs [i ]) pairs =[tmp [0 ]] for i in xrange (1 ,len (tmp )-1 ): a ,c ,b =pairs [-1 ][0 ],tmp [i ][0 ],tmp [i +1 ][0 ] ya ,yc ,yb =pairs [-1 ][1 ],tmp [i ][1 ],tmp [i +1 ][1 ] ycc =ya *((c -a )/(b -a ))+yb *((b -c )/(b -a )) if abs (yc -ycc )>=self ._eps *max (abs (yc ),abs (ycc )): pairs .append (tmp [i ]) pairs .append (tmp [-1 ]) self ._x ,self ._y =zip (*pairs ) self ._x =[float (t )for t in self ._x ] def __iter__ (self ): """iter(func) -> iterator over pairs of numbers (x, y). This allows copying by passing a P1Function to the class constructor """ return iter (zip (self ._x ,self ._y )) def __len__ (self ): """len(func) -> number of pairs (x, y) contained in the P1Function. This number may be different from the number of pairs passed to the constructor. """ return len (self ._x ) def pair (self ,i ): "func.pair(i) -> a sample point (xi, yi) (0 <= i < len(func))" return self ._x [i ],self ._y [i ] def x (self ,i ): "func.x(i) -> the coordinate xi of the sample point at index i" return self ._x [i ] def y (self ,i ): "func.y(i) -> the coordinate yi of the sample point at index i" return self ._y [i ] def index (self ,x ,left =False ): """func.index(x) -> an index i such that i = 0 if x < func.x(1) i = n-2 if func.x(n-2) <= x func.x(i) <= x < func.x(i+1) otherwise. Here, x is a float and n = len(func) is the number of pairs in func. When left is True, <= and < are exchanged in these definitions. """ return min (len (self ._x )-2 , max (0 ,self ._bisect [not left ](self ._x ,x )-1 )) def index_bounds (self ,a =None ,b =None ): """self->index_bounds(a, b) -> pair of indexes (i, j) such that a <= self.x(k) <= b for all k in [i, j[. Returns a pair (i, i) when there is no such k. a and b default to None, meaning -infinity and +infinity""" a =a if a is not None else self ._x [0 ] b =b if b is not None else self ._x [-1 ] i =self ._bisect [0 ](self ._x ,a )# bisect left j =self ._bisect [1 ](self ._x ,b )# bisect_right return (i ,j )if i <j else (i ,i ) def __call__ (self ,x ): """func(x) -> the value of the piecewise linear func at point x. x can be any float number, the function is extended by straight lines outside it's initial x-bounds""" i =self .index (x ) return ((self ._x [i +1 ]-x )*self ._y [i ]+ (x -self ._x [i ])*self ._y [i +1 ])/(self ._x [i +1 ]-self ._x [i ]) @staticmethod def float_range (start ,stop ,n ): """P1Function.float_range(start, stop, n) -> list of n float A range function which returns n equally spaced floating numbers from start to stop""" L =[0.0 ]*n nm1 =n -1 nm1inv =1.0 /nm1 start ,stop =start *nm1inv ,stop *nm1inv for i in range (n ): L [i ]=(start *(nm1 -i )+stop *i ) return L def slope (self ,x ,left =False ): """func.slope(x) -> the slope of the function at x For sample points, this is the slope at the right of the point, unless left is True. """ i =self .index (x ,left ) return (self ._y [i +1 ]-self ._y [i ])/(self ._x [i +1 ]-self ._x [i ]) def max (self ,a =None ,b =None ): """func.max(a,b) -> the maximum value of func between a and b a and b default to None, meaning -infinity and +infinity. In this case, the maximum can be None, meaning +infinity.""" return self ._extreme (a ,b ,1 ) def min (self ,a =None ,b =None ): """func.min(a,b) -> the minimum value of func between a and b a and b default to None, meaning -infinity and +infinity. In this case, the minimum can be None, meaning -infinity.""" return self ._extreme (a ,b ,-1 ) def _extreme (self ,a ,b ,eps ): """func._extreme(a, b, eps) -> the max or min of func between a and b depending on eps being 1 or -1""" opt =(min ,None ,max )[1 +eps ] if a is None : if (self ._y [1 ]-self ._y [0 ])*eps <0 : return None a =self ._x [0 ] if b is None : if (self ._y [-1 ]-self ._y [-2 ])*eps >0 : return None b =self ._x [-1 ] if a >b : a ,b =b ,a i ,j =self .index_bounds (a ,b ) m =opt (self (a ),self (b )) return opt (m ,opt (self ._y [i :j ])if i <j else m ) def integral (self ,a =None ,b =None ): """func.integral(a, b) -> the value of the integral of func on [a,b]. a and b default to None, meaning -infinity and +infinity. May return None when a or b is None, meaning undefined integral.""" if a is None : if self ._y [0 ]!=0.0 or self ._y [1 ]!=0.0 : return None a =self ._x [1 ] if b is None : if self ._y [-1 ]!=0.0 or self ._y [-2 ]!=0.0 : return None b =self ._x [-2 ] swap =False if a >b : if a ==b : return 0.0 else : swap =True a ,b =b ,a i ,j =self .index_bounds (a ,b ) j -=1 if i >j : return ((a -b )if swap else (b -a ))*self (0.5 *(b +a )) x ,y =self ._x ,self ._y res =(x [i ]-a )*self (0.5 *(x [i ]+a ))+(b -x [j ])*self (0.5 *(b +x [j ])) res +=sum ( (x [k +1 ]-x [k ])*(y [k +1 ]+y [k ])*0.5 for k in xrange (i ,j )) return -res if swap else res @staticmethod def combine (pairs ): """P1Function.combine(pairs) -> a linear combination of P1Functions pairs is a sequence (ai, fi) where ai is a number and fi a P1Function. the sum of the ai * fi is returned as a P1Function""" d =defaultdict (float ) for c ,f in pairs : d [f ]+=c for f ,c in d .items (): if c ==0.0 : del d [f ] xs =sorted (set (t for f in d for t in f ._x )) data =[(x ,sum (d [f ]*f (x )for f in d ))for x in xs ] return P1Function (data ) def rescaled (self ,center =(0.0 ,0.0 ),zoom =(1.0 ,1.0 )): """func.rescaled(center, zoom) -> a rescaled P1Function. center is a pair (cx, cy), zoom is a pair (zx, zy). A P1Function g is returned, such that g(zx * (x - cx)) = zy * (func(x) - cy) This allows changing units in x or y (eg celsius to farenheit)""" cx ,cy =center zx ,zy =zoom x ,y =self ._x ,self ._y return P1Function ( (zx *(x [i ]-cx ),zy *(y [i ]-cy ))for i in xrange (len (x ))) def truncated (self ,a =None ,b =None ): """func.truncated(a, b) -> a new P1Function truncated to [a,b]. Truncation means forget the points (xi, yi) when xi is not in [a,b]. a and b default to None, meaning -infinity and +infinity""" L =[(self ._x [i ],self ._y [i ])for i in self .index_bounds (a ,b )] L .extend ((x ,self (x ))for x in (a ,b )if x is not None ) return P1Function (L ) def __abs__ (self ): """abs(func) -> the absolute value of func (as a P1Function)""" L =[] x ,y =self ._x ,self ._y if (y [0 ]>0.0 and y [1 ]>y [0 ])or ( y [0 ]<0.0 and y [1 ]<y [0 ]): u =y [0 ]*(x [0 ]-x [1 ])/(y [0 ]-y [1 ]) L .append ((x [0 ]-1.5 *u ,0.5 *abs (y [0 ]))) L .append ((x [0 ]-u ,0.0 )) for i in range (len (x )-1 ): L .append ((x [i ],abs (y [i ]))) if (y [i ]<0.0 and y [i +1 ]>0.0 )or ( y [i ]>0.0 and y [i +1 ]<0.0 ): u =y [i ]*(x [i ]-x [i +1 ])/(y [i ]-y [i +1 ]) L .append ((x [i ]-u ,0.0 )) L .append ((x [-1 ],abs (y [-1 ]))) if (y [-1 ]>0 and y [-2 ]>y [-1 ])or ( y [-1 ]<0 and y [-2 ]<y [-1 ]): u =y [-1 ]*(x [-2 ]-x [-1 ])/(y [-1 ]-y [-2 ]) L .append ((x [-1 ]-u ,0.0 )) L .append ((x [-1 ]-1.5 *u ,0.5 *abs (y [-1 ]))) return P1Function (L ) def __add__ (self ,other ): """f1 + f2 -> the sum of 2 P1Functions as a P1Function""" return self .combine ([(1.0 ,self ),(1.0 ,other )]) def __sub__ (self ,other ): """f1 - f2 -> the difference of 2 P1Functions as a P1Function""" return self .combine ([(1.0 ,self ),(-1.0 ,other )]) def __neg__ (self ): """-func -> the opposite of func as a P1Function""" return self .combine ([(-1.0 ,self )]) def __mul__ (self ,factor ): """a * func -> a P1Function, func muliplied by the number a""" return self .combine ([float (factor ),self ]) def plot (self ,**kwd ): """func.plot(**options) -> subprocess.Popen object Starts a new process which plots func using wxPython. options (with default values) are: title="P1Function", size=(400,300), line_colour='blue', line_width=1, marker='plus', caption='', x_axis='x axis', y_axis='y axis' Possible marker values: 'circle' 'cross' 'square' 'dot' 'plus' 'triangle'""" import subprocess options =dict (self ._plot_defaults ) options .update (dict ((k .upper (),v )for k ,v in kwd .items ())) options ["POINTS"]=list (self ) program =self ._plot_template %options child =subprocess .Popen ('python -c "exec(eval(raw_input()))"', shell =True ,stdin =subprocess .PIPE ) child .stdin .write (repr (program )) child .stdin .write ("\n") child .stdin .close () return child _plot_defaults =dict ( TITLE ="P1 Function", SIZE =(400 ,300 ), LINE_COLOUR ='blue', LINE_WIDTH =1 , MARKER ='plus', CAPTION ='', X_AXIS ='x axis', Y_AXIS ='y axis' ) _plot_template =""" # thanks to vegaseat at www.daniweb.com import wx import wx.lib.plot as plot data = %(POINTS)s class MyFrame(object): def __init__(self): self.frame = wx.Frame( None, title='%(TITLE)s', id=-1, size=%(SIZE)s) self.panel = wx.Panel(self.frame) plotter = plot.PlotCanvas(self.panel) plotter.SetSize(%(SIZE)s) plotter.SetEnableZoom(True) line = plot.PolyLine(data, colour='%(LINE_COLOUR)s', width=%(LINE_WIDTH)s) marker = plot.PolyMarker(data, marker='%(MARKER)s') gc = plot.PlotGraphics([line, marker], '%(CAPTION)s', '%(X_AXIS)s', '%(Y_AXIS)s') plotter.Draw(gc) self.frame.Show(True) app = wx.PySimpleApp() f = MyFrame() app.MainLoop() """
Similar Threads
- applying continuous sounds in vb (Visual Basic 4 / 5 / 6)
- Low Continuous PC beep (Troubleshooting Dead Machines)
- Continuous bluescreen error, please help! (Troubleshooting Dead Machines)
- Continuous Reboot during Xp Installation (Windows NT / 2000 / XP)
- continuous string (C)
| Thread Tools | Search this Thread |
address anydbm app beginner changecolor cipher class clear conversion coordinates corners curves definedlines development dictionary dynamic events examples excel feet file float format function generator getvalue gui handling homework images import input ip java keycontrol line linux list lists loan loop maintain matching maze millimeter mouse mysqldb number numbers output parsing path port prime programming projects py2exe pygame pymailer python queue random rational raw_input recursion recursive scrolledtext searchingfile shebang singleton slicenotation split string strings table tails terminal text thread threading time tlapse tooltip tuple tutorial type ubuntu unicode url urllib urllib2 valueerror variable variables vigenere web word wx.wizard wxpython xlwt



