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')

Reply via email to