Would this be of any interest to anyone (see and run attached). I have written a piecewise function class (not like the one already in sympy) where the piecewise function is defined on an ordered numerical grid (see attached). The class in not finished but I can calculate the convolved powers of gate functions and will extend the class to calculate the convolution of two piecewise functions if sympy can integrate the member functions of the piecewise function. My interest comes from knowing the Fourier transform of a gate function is a sinc function. Thus the Fourier transform of a gate repeatedly convolved with itself is the power of a sinc function. This is relevant to finding rf modulation envelopes that minimize sidebands. Please run the code an see if it of general interest and I will complete the class and documentation and submit it as an addition to sympy.
-- You received this message because you are subscribed to the Google Groups "sympy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sympy+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/c9634965-089a-4456-b27b-ff3f0211a042n%40googlegroups.com.
from sympy import * import bisect,copy #init_printing( use_latex='mathjax' ) def sympy_fct(f,x,t): return f.subs(x,t) def union(a,b): """ If a and b are ordered lists return an ordered list containing all of the elements of a and b with no duplicates """ return sorted(list(set(a)|set(b))) def interval(x,x0): """ If x is a sorted list of lenght n then x0 < x[0] -> 0 x[i] < x0 <= x[i+1] -> i+1 x0 >= x[-1] -> n """ if x0 < x[0]: return 0 if x0 >= x[-1]: return len(x) return bisect.bisect_left(x,x0) def Integrate(f,xlo,xhi): x = Symbol('x',real=True) #print('Integrate: ',f,xlo,xhi) if f == 0 or f == S(0): return 0 return integrate(f,(x,xlo,xhi)) def Subs(f,x,xp): """ Takes care of the case where f is a python constant and not a sympy expression. """ #print('Subs: ',f,x,xp) if isinstance(f,Expr): return f.subs(x,xp) return f class PieceWiseFunction: """ A piecewise function is defined by a ordered list of numbers self.x and a list of sympy expressions (functions) self.f of the symbol x. If the length of self.x is n then the length of self.f is n+1. Then the fuction is given by: x < self.x[0] -> self.f0[0] x < self.x[i] and x >= self.x[i+1] -> self.f[i+1] x >= self.x[-1] -> self.f[-1] The init options are to input self.f and self.x separately or to input one list [self.f[0],self.x[0],self.f[1],...,self.x[-1],self.f[-1]] x = Symbol('x',real=True) pwf1 = PieceWiseFunction([exp(x),-1,x**2,1,exp(-x)]) pwf2 = PieceWiseFunction([exp(x),x**2,exp(-x)],[-1,1]) pwf1 and pwf2 define the same piewcewise fuction: exp(x): x < -1 x**2: -1 <= x < 1 exp(-x): x >= 1 """ def __init__(self,pf,x=[]): self.x = [] self.f = [] if len(pf) > 0 and len(x) == 0: for i in range(len(pf)): if i % 2 == 0: if isinstance(pf[i],Expr): self.f.append(pf[i]) else: self.f.append(pf[i]*S(1)) else: self.x.append(pf[i]) elif len(x) > 0: self.x = x self.f = pf def fct(self,t): """ Return the value of the PieceWiseFunction for value t where t is a number not a symbol. """ if self.x == []: return(S(0)) if t < self.x[0]: if isinstance(self.f[0],Expr): return self.f[0].subs(x,t) else: return self.f[0] elif t > self.x[-1]: if isinstance(self.f[-1],Expr): return self.f[-1].subs(x,t) else: return self.f[-1] else: i = bisect.bisect_left(self.x,t)-1 if isinstance(self.f[i+1],Expr): return self.f[i+1].subs(x,t) else: return self.f[i+1] def fct_expr(self,t): """ Return the expression of the PieceWiseFunction for the interval that t is on where t is a number not a symbol. """ if self.x == []: return(S(0)) if t < self.x[0]: return self.f[0] elif t > self.x[-1]: return self.f[-1] else: i = bisect.bisect_left(self.x,t)-1 return self.f[i+1] def RemoveDuplcates(self): """ Simplify a PieceWiseFunction that has the same expression defined on adjacent intervals. """ i = 0 while i < len(self.x): if self.f[i] == self.f[i+1]: self.x.pop(i) self.f.pop(i+1) else: i +=1 print(self.x) print(self.f) return def expand(self): """ Expand the expression defined on every interval of a PieceWiseFunction. """ f = [] for fi in self.f: f.append(expand(fi)) self.f = f return PieceWiseFunction(f,self.x) def __mul__(self,a): """ Multiply two PieceWiseFunctions together. a can be another PieceWiseFunction, a sympy expression, or a number. If a is a expression or a number it multiplies all the functions defining the PieceWiseFunction. """ if isinstance(a,(Expr,int,float)): F = [] for f in self.f: F.append(a*f) b = PieceWiseFunction(F,self.x) return b if isinstance(a,PieceWiseFunction): x = union(self.x,a.x) f1 = self.fct_expr(x[0]-1) f2 = a.fct_expr(x[0]-1) f = [f1*f2] xnew = [x[0]] for i in range(0,len(x)-1): xmid = (x[i]+x[i+1])/2 f1 = self.fct_expr(xmid) f2 = a.fct_expr(xmid) f1f2 = f1*f2 f.append(f1f2) xnew.append(x[i]) xnew.append(x[i+1]) f1 = self.fct_expr(x[-1]+1) f2 = a.fct_expr(x[-1]+1) f.append(f1*f2) xnew.append(x[-1]) xnew = sorted(list(set(xnew))) pwf = PieceWiseFunction(f,xnew) pwf.RemoveDuplcates() return pwf def __add__(self,a): """ Add two PieceWiseFunctions together. a can be another PieceWiseFunction, a sympy expression, or a number. If a is a expression or a number it is added to all the functions defining the PieceWiseFunction. """ if isinstance(a,(Expr,int,float)): F = [] for f in self.f: F.append(a+f) b = PieceWiseFunction(self.x,F) return b if isinstance(a,PieceWiseFunction): x = union(self.x,a.x) f1 = self.fct_expr(x[0]-1) f2 = a.fct_expr(x[0]-1) f = [f1+f2] xnew = [x[0]] for i in range(0,len(x)-1): xmid = (x[i]+x[i+1])/2 f1 = self.fct_expr(xmid) f2 = a.fct_expr(xmid) f1f2 = f1+f2 f.append(f1f2) xnew.append(x[i]) xnew.append(x[i+1]) f1 = self.fct_expr(x[-1]+1) f2 = a.fct_expr(x[-1]+1) f.append(f1+f2) xnew.append(x[-1]) xnew = sorted(list(set(xnew))) pwf = PieceWiseFunction(f,xnew) pwf.RemoveDuplcates() return pwf def __neg__(self): negf = [] for f in self.f: negf.append(-f) return PieceWiseFunction(negf,self.x) def __rmul__(self,a): if isinstance(a,(Expr,int,float)): return self*a return self*a def shift(self,t0): """ shift shifts the PieceWiseFunction t0 to the right. t0 must be a number and not an expression. """ x = Symbol('x',real=True) x0 = [] for xi in self.x: x0.append(xi+t0) f0 = [] for fi in self.f: if isinstance(fi,Expr): f0.append(fi.subs(x,x-t0).expand()) else: f0.append(fi) return PieceWiseFunction(f0,x0) def __str__(self): """ Returns a string in list format where the string is [{self.f[0]},self.x[0],...,{self.f[i]},self.x[i],...,self.x[-1],{self.f[-1]}] and the braces {} distiguish fuctions from grid points. """ s = '[{'+str(self.f[0])+'},' for i in range(1,len(self.f)): s += str(self.x[i-1])+',' s += '{'+str(self.f[i])+'},' s = s[:-1]+']' return s def _repr_latex_(self): return str(self) def __sub__(self,a): return self + (-a) def integral(self,xlims=[]): """ If xlims=[], integral returns a PieceWiseFunction that is the integral of self between [-oo,x]. If xlims=[xlo,xhi] and xlo and xhi are numbers integral returns the integral of self between [xlo,xhi]. xlo and xhi must be numbers so integral can determine which intervals of self.x they are in. A number or an expression can be returned depending on the form of self.f. """ if xlims == []: """ Computes the running integral of a PieceWiseFunction on [-oo,x] returning a PieceWiseFunction. """ x0 = Symbol('x0',real=True); intf = integrate(self.f[0],(x,-oo,x0)) intf = intf.subs(x0,x) F = [intf] intf0 = intf.subs(x,self.x[0]) for i in range(1,len(self.x)): intf = integrate(self.f[i],(x,self.x[i-1],x0)) intf = intf.subs(x0,x)+intf0 intf0 = intf.subs(x,self.x[i]) F.append(intf) intf = integrate(self.f[-1],(x,self.x[-1],x0)) intf = intf.subs(x0,x)+intf0 F.append(intf) return PieceWiseFunction(F,self.x) else: """ Computes the integral of a PieceWiseFunction on the interval [xlo,xhi], both xlo and xhi must be numbers not symbols or expressions. """ xlo = xlims[0] xhi = xlims[1] nx = len(self.x) x = Symbol('x',real=True) """ Intervals are numbered 0 if x <= self.x[0] and len(self.x) if x > self.x[-1] and i if self.x[i-1] <= x < self.x[i]. """ ixlo = interval(self.x,xlo) # self.x[] interval xlo is in ixhi = interval(self.x,xhi) # self.x[] interval xhi is in if ixlo == ixhi: # xlo and xhi in the same interval in self.x S = integrate(self.f[ixlo],(x,xlo,xhi)) return S S = 0 if ixlo == 0: # xlo <= self.x[0] S += integrate(self.f[0],(x,xlo,self.x[0])) if ixhi == len(self.x): # xhi > self.x[-1] S += integrate(self.f[-1],(x,self.x[-1],xhi)) if ixlo == 0 and ixhi == nx: for i in range(nx-1): S += integrate(self.f[i+1],(x,self.x[i],self.x[i+1])) return S if ixlo > 0: S += integrate(self.f[ixlo],(x,xlo,self.x[ixlo])) if ixhi < nx: S += integrate(self.f[ixhi],(x,self.x[ixhi-1],xhi)) if ixhi-ixlo > 1: for i in range(ixlo+1,ixhi): S += integrate(self.f[i],(x,self.x[i-1],self.x[i])) return S def remapintervals(self,x0): """ For each interval [self.x[i],self.x[i+1]] determines interval self.x[i]-x0 snf self.x[i+1]-x0 is in (ixlo,ixhi). If both points are not in the same interval map all intermediate grid points back to [self.x[i],self.x[i+1]] by adding x0 and store values in a list. """ nx = len(self.x) X = [] for i in range(nx-1): ixlo = interval(self.x,self.x[i]-x0) ixhi = interval(self.x,self.x[i+1]-x0) if ixhi > ixlo: for j in range(ixlo,ixhi): xtmp = self.x[j]+x0 if xtmp not in self.x: X.append(xtmp) return X def subs(self,x,y): F = [] for f in self.f: if isinstance(f,Expr): F.append(f.subs(x,y)) else: F.append(f) return PieceWiseFunction(F,self.x) def refinegrid(self,X0): """ X0 is an ordered list of new grid points. Insert the grid points in X0 to a copy of self.x maintaining the order in the copy and insert in a copy of self.f in the position same position """ X = self.x.copy() F = self.f.copy() for x0 in X0: if x0 not in self.x: ix0 = interval(X,x0) f0 = F[ix0] X = X[:ix0]+[x0]+X[ix0:] F = F[:ix0]+[f0]+F[ix0:] return PieceWiseFunction(F,X) def gateintegral(self,L): """ Integrate PieceWiseFunction between limits [x-L,x] and return a PieceWiseFunction (convolution of self with gate of length L). L must be a number and not a sympy expression. """ (x,xt) = symbols(r'x \tilde{x}',real=True) # Add self.x[i]+L to PieceWiseFunction grid Xnew = [] for xi in self.x: xpL = xi+L if xpL not in self.x: Xnew.append(xpL) pwf = self.refinegrid(Xnew) # Gate integral for x < pwf.x[0] on refined grid f = Integrate(pwf.f[0],xt-L,xt) X = [pwf.x[0]] F = [f] # Gate integral for pwf.x[0] <= x < pwf.x[-1] on refined grid for i in range(1,len(pwf.x)): j = interval(pwf.x,pwf.x[i]-L) if j == i: # x and x-L in same interval f = Integrate(pwf.f[i],xt-L,xt) X.append(pwf.x[i]) F.append(f) if j+1 == i: # x and x-L in adjacent intervals f = Integrate(pwf.f[j],xt-L,pwf.x[j]) + \ Integrate(pwf.f[i],pwf.x[j],xt) X.append(pwf.x[i]) F.append(f) if j+1 < i: # x and x-L in non-adjacent intervals f = Integrate(pwf.f[j],xt-L,pwf.x[j]) + \ Integrate(pwf.f[i],pwf.x[i-1],xt) + \ pwf.integral([pwf.x[j],pwf.x[i-1]]) X.append(pwf.x[i]) F.append(f) # Gate integral for pwf.x[-1] <= x on refined grid f = Integrate(pwf.f[-1],xt-L,xt) F.append(f) pwf = PieceWiseFunction(F,X) return pwf.subs(xt,x) def asy(self,name): """ Exports an PieceWiseFunction as function writen in Asymptote code for the purpose of using the Asymptote software to plot the function. https://asymptote.sourceforge.io/ """ sp = ' ' i = 1 F = 'REALFCT[] F'+name+' = {' X = 'real[] X'+name+' = '+str(self.x).replace('[','{').replace(']','}')+';\n' s = '//Asymptote code for piecewise function '+name+'\n' s+= '//generated by piecewise.py for data\n' s+= '//'+str(self)+'\n' s+= X for f in self.f: col = 0 s += 'real F'+name+str(i)+'(real x)\n{\n' col += 2 s+= col*sp+'return '+str(f) +';\n' col -= 2 s+= col*sp+'}\n' F += 'rfct(F'+name+str(i)+'),' i += 1 F = F[:-1]+'};\n' return s+F+'PIECEWISE '+name+' = piecewise(X'+name+',F'+name+');\n' """ Test to calculate convolved powers of gate function h0. Look at doc string for __str__ to understand output. """ h0 = PieceWiseFunction([S(0),S(1),S(0)],[-S(1)/S(2),S(1)/S(2)]) print('h0 = ',h0) print('h0.integral([-oo,oo]) = ',h0.integral([-oo,oo])) print('FourierTransform{h0} = sinc(f)') h1 = h0.gateintegral(1) h1 = h1.shift(-S(1)/S(2)) print('h1 = ',h1) print('h1.integral([-oo,oo]) = ',h1.integral([-oo,oo])) print('FourierTransform{h1} = sinc(f)**2') h2 = h1.gateintegral(1) h2 = h2.shift(-S(1)/S(2)).expand() #Center h2 print('h2 = ',h2) print('h2.integral([-oo,oo]) = ',h2.integral([-oo,oo])) print('FourierTransform{h2} = sinc(f)**3') h3 = h2.gateintegral(1) h3 = h3.shift(-S(1)/S(2)) #Center h3 print('h3 = ',h3) print('h3.integral([-oo,oo]) = ',h3.integral([-oo,oo])) print('FourierTransform{h3} = sinc(f)**4') h4 = h3.gateintegral(1) h4 = h4.shift(-S(1)/S(2)).expand() #Center h4 print('h4 = ',h4) print('h4.integral([-oo,oo]) = ',h4.integral([-oo,oo])) print('FourierTransform{h4} = sinc(f)**5')