Dragon
Dragon

Reputation: 1284

Trapezoidal wave in Python

How do I generate a trapezoidal wave in Python?

I looked into the modules such as SciPy and NumPy, but in vain. Is there a module such as the scipy.signal.gaussian which returns an array of values representing the Gaussian function wave?

Enter image description here

I generated this using the trapezoidal kernel of Astropy, Trapezoid1DKernel(30,slope=1.0) . I want to implement this in Python without using Astropy.

Upvotes: 4

Views: 4994

Answers (5)

Cian Roche
Cian Roche

Reputation: 11

I'll throw a very late hat into this ring, namely, a function using only numpy that produces a single (symmetric) trapezoid at a desired location, with all the usual parameters. Also posted here

import numpy as np

def trapezoid(x, center=0, slope=1, width=1, height=1, offset=0):
    """
    For given array x, returns a (symmetric) trapezoid with plateau at y=h (or -h if 
    slope is negative), centered at center value of "x".
    Note: Negative widths and heights just converted to 0

    Parameters
    ----------
    x : array_like
        array of x values at which the trapezoid should be evaluated
    center : float
        x coordinate of the center of the (symmetric) trapezoid
    slope : float
        slope of the sides of the trapezoid
    width : float
        width of the plateau of the trapezoid
    height : float
        (positive) vertical distance between the base and plateau of the trapezoid
    offset : array_like
        vertical shift (either single value or the same shape as x) to add to y before returning

    Returns
    -------
    y : array_like
        y value(s) of trapezoid with above parameters, evaluated at x

    """
    
    # ---------- input checking ----------
    if width < 0: width = 0
    if height < 0: height = 0

    x = np.asarray(x)

    slope_negative = slope < 0
    slope = np.abs(slope)  #  Do all calculations with positive slope, invert at end if necessary

    # ---------- Calculation ----------
    y = np.zeros_like(x)
    mask_left = x - center < -width/2.0
    mask_right = x - center > width/2.0

    y[mask_left] = slope*(x[mask_left] - center + width/2.0)
    y[mask_right] = -slope*(x[mask_right] - center - width/2.0)

    y += height     # Shift plateau up to y=h
    y[y < 0] = 0    # cut off below zero (so that trapezoid flattens off at "offset")

    if slope_negative: y = -y          # invert non-plateau

    return y + offset

Which outputs something like

import matplotlib.pyplot as plt
plt.style.use("seaborn-colorblind")

x = np.linspace(-5,5,1000)

for i in range(1,4):
    plt.plot(x,trapezoid(x, center=0, slope=1, width=i, height=i, offset = 0), label=f"width = height = {i}\nslope=1")

plt.plot(x,trapezoid(x, center=0, slope=-1, width=2.5, height=1, offset = 0), label=f"width = height = 1.5,\nslope=-1")
plt.ylim((-2.5,3.5))
plt.legend(frameon=False, loc='lower center', ncol=2)

Example output:

enter image description here

Upvotes: 1

Dragon
Dragon

Reputation: 1284

The whole credit goes to @ImportanceOfBeingErnest . I am just revising some edits to his code which just made my day.

from scipy import signal
import matplotlib.pyplot as plt
from matplotlib import style
import numpy as np

def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
    a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
    a += slope*width/4.
    a[a>amp] = amp
    return a + offs

for w,s,a in zip([32],[1],[0.0322]):
    t = np.linspace(0, w, 34)

    plt.plot(t,trapzoid_signal(t, width=w, slope=s, amp=a))


plt.show()

The result:enter image description here

Upvotes: 1

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339062

While the width and the slope are sufficient to define a triangular signal, you would need a third parameter for a trapezoidal signal: the amplitude.

Using those three parameters, you can easily adjust the scipy.signal.sawtooth function to give you a trapeziodal shape by truncating and offsetting the triangular shaped function.

from scipy import signal
import matplotlib.pyplot as plt
import numpy as np


def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
    a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
    a[a>amp/2.] = amp/2.
    a[a<-amp/2.] = -amp/2.
    return a + amp/2. + offs

t = np.linspace(0, 6, 501)
plt.plot(t,trapzoid_signal(t, width=2, slope=2, amp=1.), label="width=2, slope=2, amp=1")
plt.plot(t,trapzoid_signal(t, width=4, slope=1, amp=0.6), label="width=4, slope=1, amp=0.6")

plt.legend( loc=(0.25,1.015))
plt.show()

enter image description here

Note that you may also like to define a phase, depeding on the use case.

In order to define a single pulse, you might want to modify the function a bit and supply an array which ranges over [0,width].

from scipy import signal
import matplotlib.pyplot as plt
import numpy as np

def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
    a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
    a += slope*width/4.
    a[a>amp] = amp
    return a + offs

for w,s,a in zip([2,5], [2,1], [1,0.6]):
    t = np.linspace(0, w, 501)
    l = "width={}, slope={}, amp={}".format(w,s,a)
    plt.plot(t,trapzoid_signal(t, width=w, slope=s, amp=a), label=l)

plt.legend( loc="upper right")
plt.show()

enter image description here

Upvotes: 2

Zebromalz
Zebromalz

Reputation: 31

The below example shows how to do that to get points and show scope.

Equation based on reply: Equation for trapezoidal wave equation

import math
import numpy as np
import matplotlib.pyplot as plt


def get_wave_point(x, a, m, l, c):
    # Equation from: https://stackoverflow.com/questions/11041498/equation-for-trapezoidal-wave-equation
    # a/pi(arcsin(sin((pi/m)x+l))+arccos(cos((pi/m)x+l)))-a/2+c
    # a is the amplitude
    # m is the period
    # l is the horizontal transition
    # c is the vertical transition

    point = a/math.pi*(math.asin(math.sin((math.pi/m)*x+l))+math.acos(math.cos((math.pi/m)*x+l)))-a/2+c
    return point

print('Testing wave')

x = np.linspace(0., 10, 1000)
listofpoints = []
for i in x:
    plt.plot(i, get_wave_point(i, 5, 2, 50, 20), 'k.')
    listofpoints.append(get_wave_point(i, 5, 2, 50, 20))
print('List of points : {} '.format(listofpoints))
plt.show()

Upvotes: 2

Ed Smith
Ed Smith

Reputation: 13196

From the SciPy website it looks like this isn't included (they currently have sawtooth and square, but not trapezoid). As a generalised version of the C example the following will do what you want,

import numpy as np
import matplotlib.pyplot as plt

def trapezoidalWave(xin, width=1., slope=1.):
    x = xin%(4*width)
    if (x <= width):
        # Ascending line
        return x*slope;
    elif (x <= 2.*width):
        # Top horizontal line
        return width*slope
    elif (x <= 3.*width):
        # Descending line
        return 3.*width*slope - x*slope
    elif (x <= 4*width):
        # Bottom horizontal line
        return 0.


x = np.linspace(0.,20,1000)
for i in x:
    plt.plot(i, trapezoidalWave(i), 'k.')
    plt.plot(i, trapezoidalWave(i, 1.5, 2.), 'r.')
plt.show()

which looks like,

Enter image description here

This can be done more elegantly with Heaviside functions which allow you to use NumPy arrays,

import numpy as np
import matplotlib.pyplot as plt

def H(x):
    return 0.5 * (np.sign(x) + 1)

def trapWave(xin, width=1., slope=1.):
    x = xin%(4*width)
    y = ((H(x)-H(x-width))*x*slope +
         (H(x-width)-H(x-2.*width))*width*slope +
         (H(x-2.*width)-H(x-3.*width))*(3.*width*slope - x*slope))
    return y

x = np.linspace(0.,20,1000)
plt.plot(x, trapWave(x))
plt.plot(x, trapWave(x, 1.5, 2.))
plt.show()

For this example, the Heaviside version is about 20 times faster!

Upvotes: 2

Related Questions