(Contact | Bug Reports | Home | Copyright 2025 Q.Wang)

Formula for the clipping

The original signal given by

$C_T(t) = I + i * Q = A ( cos(x) + i * sin(x) )$

$A > 0$

If the magnitude of the signal exceeds 1, then it will be clipped. In this words, $A$ satisfies

$A = 1 / (1 - cutOff)$

In [6]:
import numpy as np
import matplotlib.pyplot as plt

f0 = 10       # the frequency of the signal
cutOff = 0.3  # cut-off precentage
A = 1 / (1 - cutOff)
t = np.arange(0,0.1,0.001)

def clippling(signal):
    clipSignal = np.zeros_like(signal)
    for n in range(len(signal)):
        if signal[n] < -1:
            clipSignal[n] = -1
        elif signal[n] < 1:
            clipSignal[n] = signal[n]
        else:
            clipSignal[n] = 1
    return clipSignal

plt.figure(figsize=(10,6))
plt.subplot(221)
plt.grid()
plt.plot(t, A * np.cos(2 * np.pi * f0 * t), color = 'orange', linestyle = '--', lw=2)
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.cos(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.subplot(222)
plt.grid()
plt.plot(t, A * np.sin(2 * np.pi * f0 * t), color = 'orange', linestyle = '--', lw=2)
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.sin(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.show()

The red line is the result of clipping signal.

Spectra of the clipping

Using the formula of Fourier Transformation

$\frak{F}$$ \lbrace C_T(t)\rbrace = TC_{1/T}(f) = \int_{0}^{T}{A * (cos(2\pi f_0 t) + i* sin(2\pi f_0 t)) e^{- i 2\pi f t} dt}$

The final result is

\begin{align} TC_{1/T} \left( f \right) & = \frac{2}{\pi} sin \left(\frac{\pi f}{2f_0}\right) \left(-sin \left(2\pi f t_0 - \frac{\pi f}{2 f_0}\right) + cos \left(2\pi f t_0 \right)\right) \frac{f_{0}^{2}}{f \left( f_{0}^{2}-f^2 \right)}\\ & - \frac{2}{\pi} sin \left(\frac{\pi f}{2 f_0}\right) \left( cos \left( 2 \pi f t_0 - \frac{\pi f}{2 f_0}\right) + sin \left( 2\pi f t_0\right) \right) \frac{f_0}{f_{0}^{2}-f^2} cot \left( 2 \pi f_0 t_0 \right)\\ & + \frac{1}{\pi} sin \left( \frac{\pi f}{f_0}\right) \left( \frac{f_0}{\left( f_{0}^{2} - f^2 \right) sin \left( 2 \pi f_0 t_0\right)} - \frac{1}{f} \right) \end{align}
\begin{equation*} t_0 = \frac{\left| arcsin \left( 1 - cutOff\right)\right|}{2\pi f_0} \end{equation*}

Since $f$ the multiples of the $f_0$ ($f = n * f_0$, $n$ is integer), the magntitude of the spectra is not zero only when $n = 4 k + 1$ ($k$ is the integer). The value of non-zero peak is

$TC_{1/T}\left(f = (4 k + 1) f_0 \right) = \frac{4}{\pi} \frac{f_0}{f_{0}^{2} - f^2} \left( cos \left( 2\pi f t_0 \right) \frac{f_0}{f} - sin \left( 2\pi f t_0\right) cot \left( 2 \pi f_0 t_0\right) \right) $

$TC_{1/T}\left(f \neq (4 k + 1) f_0 \right) = 0$

In [7]:
def SpectraFormula(span,cutOff, showResult, showConti):
    '''
    return the spectra from the formula
    ------------------
    span:        display from - span /2 to span/2
    cutOff:      precentage of the clipping in the whole magnitude
    showResult:  display the spectra of the peaks
    showConti:   display the continuous spectra of the formula
    -------------------
    freq:        display frequency range
    sigSum:      peaks
    sigConti:    continuous spectra
    '''
    span = np.around(span / f0) * f0
    freq = np.arange(- span / 2, span / 2, 0.001 * f0)
    sigSum = np.zeros_like(freq)
    sigConti = f0 * np.absolute(2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * (- np.sin(freq / f0 * np.absolute(np.arcsin(1 - cutOff)) - np.pi * freq / (2 * f0)) + np.cos(freq / f0 * np.absolute(np.arcsin(1 - cutOff)))) * f0 ** 2 / (freq * (f0 ** 2 - freq ** 2)) - 2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * (np.cos(freq / f0 * np.absolute(np.arcsin(1 - cutOff)) - np.pi * freq / (2 * f0)) + np.sin(freq / f0 * np.absolute(np.arcsin(1 - cutOff)))) * f0 / (f0 ** 2 - freq ** 2) * 1 / np.tan(np.absolute(np.arcsin(1 - cutOff))) + 1 / np.pi * np.sin(np.pi * freq / f0) * ( f0 / ((f0 ** 2 - freq ** 2) * np.sin(np.absolute(np.arcsin(1 - cutOff)))) - 1/ freq )) 
    if (span / 2 < 3 * f0):
        startFreq = f0
    else:
        startFreq = (- np.floor((- 3 * f0 + span / 2) / (4 * f0)) * 4 - 3) * f0 
    while (startFreq <= span/2):
        point = np.int((startFreq + span / 2) / (0.001 * f0))-1
        if np.int(startFreq) == f0:
            sigSum[point] = f0 * np.absolute(1 / (f0 * np.pi) * 1 / np.sin(f0 * np.absolute(np.arcsin(1 - cutOff) / f0))) * (2 * f0 * np.absolute(np.arcsin(1 - cutOff) / f0) + np.sin(2 * f0 * np.absolute(np.arcsin(1 - cutOff) / f0)))
        else:
            sigSum[point] = f0 * np.absolute(4 / np.pi * f0 / (startFreq ** 2 - f0 ** 2) * (np.cos( startFreq * np.absolute(np.arcsin(1 - cutOff) / f0)) * f0 / startFreq - np.sin(startFreq * np.absolute(np.arcsin(1 - cutOff) / f0)) / np.tan(np.absolute(np.arcsin(1 - cutOff)))))
        startFreq += 4 * f0
    if showResult:
        plt.grid()
        plt.plot(freq, sigSum, color='dodgerblue', linestyle='-', lw=2, zorder=-1)
        if showConti:
            plt.plot(freq, sigConti, color='tomato', linestyle='-', lw=2, zorder=-1)
        plt.show()
    return freq, sigSum, sigConti

freq, sigSum, sigConti = SpectraFormula(200,0.9, True, True)

cutOff vs spectra

In [8]:
from matplotlib import animation, rc
from IPython.display import HTML

span = 200
cutOff = 0.01


fig, ax = plt.subplots()
ax.grid()
freq, sigSum, sigConti = SpectraFormula(span,cutOff, False, False)
line1, = ax.plot(freq, sigSum, color='dodgerblue', linestyle='-', lw=2, zorder=-1)
line2, = ax.plot(freq, sigConti, color='tomato', linestyle='-', lw=2, zorder=-1)
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)

def animate(cutOFF):
    freq, sigSum, sigConti = SpectraFormula(span,cutOFF/100, False, False)
    line1.set_ydata(sigSum)
    line2.set_ydata(sigConti)
    time_text.set_text("cutOff = {}".format(cutOFF/100))
    return line1, line2, time_text,
def initial():
    line1.set_ydata(sigSum)
    line2.set_ydata(sigConti)
    time_text.set_text('')
    return line1, line2, time_text,

ani = animation.FuncAnimation(fig=fig, func=animate, frames=100, init_func=initial, interval=200, blit=True)
plt.close()
plt.rcParams['animation.ffmpeg_path'] = 'C:\\ffmpeg-4.0.2-win64-static\\bin\\ffmpeg.exe'
HTML(ani.to_html5_video())
Out[8]:

cutOff vs peaks value

In [9]:
cutOffRange = np.arange(0.01,0.99,0.01)

def peakValue(k):
    startFreq = k * f0
    if startFreq != f0:
        peakMag = f0 * np.absolute(4 / np.pi * f0 / (- startFreq ** 2 + f0 ** 2) * (np.cos( startFreq * np.absolute(np.arcsin(1 - cutOffRange) / f0)) * f0 / startFreq - np.sin(startFreq * np.absolute(np.arcsin(1 - cutOffRange) / f0)) / np.tan(np.absolute(np.arcsin(1 - cutOffRange)))))
        peakPha = np.angle(4 / np.pi * f0 / (- startFreq ** 2 + f0 ** 2) * (np.cos( startFreq * np.absolute(np.arcsin(1 - cutOffRange) / f0)) * f0 / startFreq - np.sin(startFreq * np.absolute(np.arcsin(1 - cutOffRange) / f0)) / np.tan(np.absolute(np.arcsin(1 - cutOffRange)))))
    else:
        peakMag = f0 * np.absolute(1 / (f0 * np.pi) * 1 / np.sin(f0 * np.absolute(np.arcsin(1 - cutOffRange) / f0))) * (2 * f0 * np.absolute(np.arcsin(1 - cutOffRange) / f0) + np.sin(2 * f0 * np.absolute(np.arcsin(1 - cutOffRange) / f0)))
        peakPha = np.angle(1 / (f0 * np.pi) * 1 / np.sin(f0 * np.absolute(np.arcsin(1 - cutOffRange) / f0))) * (2 * f0 * np.absolute(np.arcsin(1 - cutOffRange) / f0) + np.sin(2 * f0 * np.absolute(np.arcsin(1 - cutOffRange) / f0)))
    return cutOffRange, peakMag, peakPha
    
plt.style.use(['seaborn-notebook'])
plt.figure(figsize=(10,8))
plt.subplot(221)
plt.grid()
for k in range(-2,3):
    k0 = 4 * k + 1
    cutOffRange, peakMag, peakPha = peakValue(k0)
    plt.plot(cutOffRange, peakMag, label="k = {}".format(k0), lw=2)
plt.legend(loc=2)
plt.subplot(222)
plt.grid()
for k in range(-2,3):
    k0 = 4 * k + 1
    if k0 != 1:
        cutOffRange, peakMag, peakPha = peakValue(k0)
        plt.plot(cutOffRange, peakMag, label="k = {}".format(k0), lw=2)
plt.legend(loc=2)
plt.subplot(223)
plt.grid()
for k in range(-2,3):
    k0 = 4 * k + 1
    cutOffRange, peakMag, peakPha = peakValue(k0)
    plt.plot(cutOffRange, peakPha, label="k = {}".format(k0), lw=2)
plt.ylim((-0.2,3.5))
plt.legend(loc=2)
plt.subplot(224)
plt.grid()
for k in range(-2,3):
    k0 = 4 * k + 1
    if k0 != 1:
        cutOffRange, peakMag, peakPha = peakValue(k0)
        plt.plot(cutOffRange, peakPha, label="k = {}".format(k0), lw=2)
plt.ylim((-0.2,3.5))
plt.legend(loc=2)
plt.show()

Simulation of the clipping

The spectrum of the clipping signal

In [10]:
def clip_sampling(N, f0, cutOff, fs):
    '''
    the function to produce the clipping IQ-signal
    ---------------------
    N:      the number of sampling points
    f0:     the frequency for the original signal
    cutOff: the percentage for clipping part
    fs:     the sampling rate
    ---------------------
    time:   the time range of the whole signal
    signal: the magnitude of the IQ-signal
    '''
    A = 1 / (1 - cutOff)
    time = np.arange(0, N / fs, 1 / fs)
    signal = clippling(A * np.cos(2 * np.pi * f0 * time)) + 1j * clippling(A * np.sin(2 * np.pi * f0 * time))
    return time, signal

def ft(signal, fs):
    '''
    the function to calculate the Fourier Transformation of the discrete IQ-signal
    ----------------------
    signal:   the uncalculated IQ-signal
    fs:       the sampling rate
    ----------------------
    freq:     the frequency range of the result
    ftMag:    the magnitude of the FT result
    ftPha:    the phase of the FT result
    '''
    ftMag = np.absolute(np.fft.fftshift(np.fft.fft(signal)) / len(signal))
    ftPha = np.angle(np.fft.fftshift(np.fft.fft(signal)))
    freq = np.linspace(- fs / 2, fs / 2, len(signal), endpoint=False)
    return freq, ftMag, ftPha

fs     = 200        # the sampling rate
cutOff = 0.3        # the percentage for clipping part
f0     = 10         # the frequency for the original signal
N      = 1000       # the number of sampling points

time, signal = clip_sampling(N, f0, cutOff, fs)
freq, ftMag, ftPha = ft(signal, fs)

plt.figure(figsize=(10,18))
plt.subplot(311)
plt.grid()
plt.plot(time, np.imag(signal), color='tomato', label = 'imag', lw=2)
plt.plot(time, np.real(signal), color='dodgerblue', label = 'real', lw=2)
plt.legend(loc=2)
plt.ylim((-1.2,1.2))
plt.xlim((0,0.2))
plt.subplot(312)
plt.grid()
plt.plot(freq, ftMag, color='orange', label ='Magnitude', lw=2)
plt.legend(loc=1)
plt.subplot(313)
plt.grid()
plt.plot(freq, ftPha, color='orange', label='Phase', lw=2)
plt.legend(loc=1)
plt.show()

cutOff vs peaks value

In [11]:
fs     = 2000        # the sampling rate
f0     = 10         # the frequency for the original signal
N      = 1000       # the number of sampling points

if fs / 2 < 3 * f0:
    k = 1
    startPoint = np.int((f0 + fs / 2) / fs * N)
else:
    k = -np.floor((fs / 2 - 3 * f0) / (4 * f0)) * 4 - 3
    startPoint = np.int((k * f0 + fs / 2) / fs * N)
    
# customic startPoint
startFrequency = -7 * f0
endFrequency = 9 * f0 # default None
k = np.int(startFrequency / f0)
startPoint = np.int((startFrequency + fs / 2) / fs * N)
endPoint = np.int((endFrequency + fs / 2) / fs * N)
    
peaksMag = []
peaksPha = []
for cutOffpart in range(1, 99, 1):
    time, signal = clip_sampling(N, f0, cutOffpart/100, fs)
    freq, ftMag, ftPha = ft(signal, fs)
    if endFrequency != 0:
        peakMag = ftMag[startPoint:endPoint:np.int(4*f0/fs*N)]
        peakPha = ftPha[startPoint:endPoint:np.int(4*f0/fs*N)]
    else:
        peakMag = ftMag[startPoint::np.int(4*f0/fs*N)]
        peakPha = ftPha[startPoint::np.int(4*f0/fs*N)]
    peaksMag.append(peakMag)
    peaksPha.append(peakPha)

peaksMag = np.transpose(peaksMag)
peaksPha = np.transpose(peaksPha)

plt.style.use(['seaborn-muted'])
plt.figure(figsize=(10,8))
plt.subplot(221)
plt.grid()
for n in range(len(peaksMag)):
    plt.plot(np.arange(0.01,0.99,0.01),peaksMag[n], label='k = {}'.format(np.int(k + 4 * n)), lw=2)
plt.legend(loc=1)
plt.subplot(222)
plt.grid()
for n in range(len(peaksPha)):
    plt.plot(np.arange(0.01,0.99,0.01),peaksPha[n], label='k = {}'.format(np.int(k + 4 * n)), lw=2)
plt.legend(loc=1)

plt.subplot(223)
plt.grid()
for n in range(len(peaksMag)):
    if np.int(k + 4 * n) != 1:
        plt.plot(np.arange(0.01,0.99,0.01),peaksMag[n], label='k = {}'.format(np.int(k + 4 * n)), lw=2)
plt.legend(loc=1)
plt.subplot(224)
plt.grid()
for n in range(len(peaksPha)):
    if np.int(k + 4 * n) != 1:
        plt.plot(np.arange(0.01,0.99,0.01),peaksPha[n], label='k = {}'.format(np.int(k + 4 * n)), lw=2)
plt.legend(loc=1)
plt.show()

With a glance of the above result, both magnitude and phase spectra of the simulation is well fitted to the formula result.

The following code will show a detailed comparison.

In [12]:
k0 = -7
plt.style.use(['seaborn-muted'])
plt.figure(figsize=(10,16))
plt.subplot(421)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksMag[0], label='k = {}, simulation'.format(np.int(k0 + 4 * 0)), color='dodgerblue', linestyle='--', lw=5)
cutOffRange, peakMag, peakPha = peakValue(k0)
plt.plot(cutOffRange, peakMag, label="k = {}, formula".format(k0), color='tomato', linestyle='-', lw=2)
plt.legend(loc=9)
plt.subplot(422)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksPha[0], label='k = {}, simulation'.format(np.int(k0 + 4 * 0)), color='dodgerblue', linestyle='--', lw=5)
plt.plot(cutOffRange, peakPha, label="k = {}, formula".format(k0), color='tomato', linestyle='-', lw=2)
plt.ylim((-0.5,3.5))
plt.legend(loc=9)
plt.subplot(423)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksMag[1], label='k = {}, simulation'.format(np.int(k0 + 4 * 1)), color='dodgerblue', linestyle='--', lw=5)
cutOffRange, peakMag, peakPha = peakValue(k0 + 4)
plt.plot(cutOffRange, peakMag, label="k = {}, formula".format(k0 + 4), color='tomato', linestyle='-', lw=2)
plt.legend(loc=9)
plt.subplot(424)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksPha[1], label='k = {}, simulation'.format(np.int(k0 + 4 * 1)), color='dodgerblue', linestyle='--', lw=5)
plt.plot(cutOffRange, peakPha, label="k = {}, formula".format(k0 + 4), color='tomato', linestyle='-', lw=2)
plt.ylim((-0.5,3.5))
plt.legend(loc=9)
plt.subplot(425)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksMag[2], label='k = {}, simulation'.format(np.int(k0 + 4 * 2)), color='dodgerblue', linestyle='--', lw=5)
cutOffRange, peakMag, peakPha = peakValue(k0 + 4 * 2)
plt.plot(cutOffRange, peakMag, label="k = {}, formula".format(k0 + 4 * 2), color='tomato', linestyle='-', lw=2)
plt.legend(loc=9)
plt.subplot(426)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksPha[2], label='k = {}, simulation'.format(np.int(k0 + 4 * 2)), color='dodgerblue', linestyle='--', lw=5)
plt.plot(cutOffRange, peakPha, label="k = {}, formula".format(k0 + 4 * 2), color='tomato', linestyle='-', lw=2)
plt.ylim((-0.5,3.5))
plt.legend(loc=9)
plt.subplot(427)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksMag[3], label='k = {}, simulation'.format(np.int(k0 + 4 * 3)), color='dodgerblue', linestyle='--', lw=5)
cutOffRange, peakMag, peakPha = peakValue(k0 + 4 * 3)
plt.plot(cutOffRange, peakMag, label="k = {}, formula".format(k0 + 4 * 3), color='tomato', linestyle='-', lw=2)
plt.legend(loc=9)
plt.subplot(428)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksPha[3], label='k = {}, simulation'.format(np.int(k0 + 4 * 3)), color='dodgerblue', linestyle='--', lw=5)
plt.plot(cutOffRange, peakPha, label="k = {}, formula".format(k0 + 4 * 3), color='tomato', linestyle='-', lw=2)
plt.ylim((-0.5,3.5))
plt.legend(loc=9)
plt.show()

If the sampling rate is large enough, the simulation result is well fit to the formula result.

problem 1. insufficient sampling rate for sharp spectrum

In [13]:
fs     = 200        # the sampling rate
f0     = 10         # the frequency for the original signal
N      = 1000       # the number of sampling points

if fs / 2 < 3 * f0:
    k = 1
    startPoint = np.int((f0 + fs / 2) / fs * N)
else:
    k = -np.floor((fs / 2 - 3 * f0) / (4 * f0)) * 4 - 3
    startPoint = np.int((k * f0 + fs / 2) / fs * N)
    
# customic startPoint
startFrequency = -7 * f0
endFrequency = 9 * f0 # default None
k = np.int(startFrequency / f0)
startPoint = np.int((startFrequency + fs / 2) / fs * N)
endPoint = np.int((endFrequency + fs / 2) / fs * N)
    
peaksMag = []
peaksPha = []
for cutOffpart in range(1, 99, 1):
    time, signal = clip_sampling(N, f0, cutOffpart/100, fs)
    freq, ftMag, ftPha = ft(signal, fs)
    if endFrequency != 0:
        peakMag = ftMag[startPoint:endPoint:np.int(4*f0/fs*N)]
        peakPha = ftPha[startPoint:endPoint:np.int(4*f0/fs*N)]
    else:
        peakMag = ftMag[startPoint::np.int(4*f0/fs*N)]
        peakPha = ftPha[startPoint::np.int(4*f0/fs*N)]
    peaksMag.append(peakMag)
    peaksPha.append(peakPha)

peaksMag = np.transpose(peaksMag)
peaksPha = np.transpose(peaksPha)

k0 = -7
plt.style.use(['seaborn-muted'])
plt.figure(figsize=(10,16))
plt.subplot(421)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksMag[0], label='k = {}, simulation'.format(np.int(k0 + 4 * 0)), color='dodgerblue', linestyle='--', lw=5)
cutOffRange, peakMag, peakPha = peakValue(k0)
plt.plot(cutOffRange, peakMag, label="k = {}, formula".format(k0), color='tomato', linestyle='-', lw=2)
plt.legend(loc=9)
plt.subplot(422)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksPha[0], label='k = {}, simulation'.format(np.int(k0 + 4 * 0)), color='dodgerblue', linestyle='--', lw=5)
plt.plot(cutOffRange, peakPha, label="k = {}, formula".format(k0), color='tomato', linestyle='-', lw=2)
plt.ylim((-3.4,3.4))
plt.legend(loc=9)
plt.subplot(423)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksMag[1], label='k = {}, simulation'.format(np.int(k0 + 4 * 1)), color='dodgerblue', linestyle='--', lw=5)
cutOffRange, peakMag, peakPha = peakValue(k0 + 4)
plt.plot(cutOffRange, peakMag, label="k = {}, formula".format(k0 + 4), color='tomato', linestyle='-', lw=2)
plt.legend(loc=9)
plt.subplot(424)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksPha[1], label='k = {}, simulation'.format(np.int(k0 + 4 * 1)), color='dodgerblue', linestyle='--', lw=5)
plt.plot(cutOffRange, peakPha, label="k = {}, formula".format(k0 + 4), color='tomato', linestyle='-', lw=2)
plt.ylim((-3.4,3.4))
plt.legend(loc=9)
plt.subplot(425)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksMag[2], label='k = {}, simulation'.format(np.int(k0 + 4 * 2)), color='dodgerblue', linestyle='--', lw=5)
cutOffRange, peakMag, peakPha = peakValue(k0 + 4 * 2)
plt.plot(cutOffRange, peakMag, label="k = {}, formula".format(k0 + 4 * 2), color='tomato', linestyle='-', lw=2)
plt.legend(loc=9)
plt.subplot(426)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksPha[2], label='k = {}, simulation'.format(np.int(k0 + 4 * 2)), color='dodgerblue', linestyle='--', lw=5)
plt.plot(cutOffRange, peakPha, label="k = {}, formula".format(k0 + 4 * 2), color='tomato', linestyle='-', lw=2)
plt.ylim((-3.4,3.4))
plt.legend(loc=9)
plt.subplot(427)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksMag[3], label='k = {}, simulation'.format(np.int(k0 + 4 * 3)), color='dodgerblue', linestyle='--', lw=5)
cutOffRange, peakMag, peakPha = peakValue(k0 + 4 * 3)
plt.plot(cutOffRange, peakMag, label="k = {}, formula".format(k0 + 4 * 3), color='tomato', linestyle='-', lw=2)
plt.legend(loc=9)
plt.subplot(428)
plt.grid()
plt.plot(np.arange(0.01,0.99,0.01),peaksPha[3], label='k = {}, simulation'.format(np.int(k0 + 4 * 3)), color='dodgerblue', linestyle='--', lw=5)
plt.plot(cutOffRange, peakPha, label="k = {}, formula".format(k0 + 4 * 3), color='tomato', linestyle='-', lw=2)
plt.ylim((-3.4,3.4))
plt.legend(loc=9)
plt.show()

The sampling rate is 20 multiples of the original frequency, which is far beyond the Nyquest frequency (always around 2.5 multiples). However, the magnitude spectrum has some sharp corners, while the phase spectrum has some jumpping points.

The reason of these distortion exactly owes to the distribution of the sampling points. For example, the final platform of each spectrum is result from the distribution of sampling point on one original period of the squarewave. Other sharp corners are also result from a new distribution of the sampling point which is fit to the clippling signal.

distribution 1. $\frac{fs}{f_0} = 4k + 1$

In [14]:
f0 = 10       # the frequency of the signal
cutOff = 0.99  # cut-off precentage
A = 1 / (1 - cutOff)
t = np.arange(0,0.1,0.001)

def clippling(signal):
    clipSignal = np.zeros_like(signal)
    for n in range(len(signal)):
        if signal[n] < -1:
            clipSignal[n] = -1
        elif signal[n] < 1:
            clipSignal[n] = signal[n]
        else:
            clipSignal[n] = 1
    return clipSignal

plt.figure(figsize=(10,6))
plt.subplot(221)
plt.grid()
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.cos(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.scatter(np.linspace(0,0.1,17,endpoint=False),clippling(A * np.cos(2 * np.pi * f0 * np.linspace(0,0.1,17,endpoint=False))), color='indigo',lw=3)
plt.xlim((0,0.1))
plt.ylim((-1.2,1.2))
plt.subplot(222)
plt.grid()
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.sin(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.scatter(np.linspace(0,0.1,17,endpoint=False),clippling(A * np.sin(2 * np.pi * f0 * np.linspace(0,0.1,17,endpoint=False))), color='indigo',lw=3)
plt.xlim((0,0.1))
plt.ylim((-1.2,1.2))
plt.show()

The final platform requires

$1 - cutOff = cos \left( 2\pi \frac{f_0}{f_s} t\right)$

$cutOff - 1 = cos \left( 2\pi \frac{f_0}{f_s} \left( t + 0.5 \right) \right)$

If the final platform only occurs when the $cutOff > 0.9$, then

$f_s > 15.6817 \times f_0$

distribution 2. $\frac{fs}{f_0} = 4k + 2$

In [16]:
plt.figure(figsize=(10,6))
plt.subplot(221)
plt.grid()
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.cos(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.scatter(np.linspace(0,0.1,18,endpoint=False),clippling(A * np.cos(2 * np.pi * f0 * np.linspace(0,0.1,18,endpoint=False))), color='indigo',lw=3)
plt.xlim((0,0.1))
plt.ylim((-1.2,1.2))
plt.subplot(222)
plt.grid()
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.sin(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.scatter(np.linspace(0,0.1,18,endpoint=False),clippling(A * np.sin(2 * np.pi * f0 * np.linspace(0,0.1,18,endpoint=False))), color='indigo',lw=3)
plt.xlim((0,0.1))
plt.ylim((-1.2,1.2))
plt.show()

The final platform requires

$1 - cutOff = cos \left( 2\pi \frac{f_0}{f_s} t\right)$

$cutOff - 1 = cos \left( 2\pi \frac{f_0}{f_s} \left( t + 1 \right) \right)$

If the final platform only occurs when the $cutOff > 0.9$, then

$f_s > 31.3634 \times f_0$

distribution 3. $\frac{fs}{f_0} = 4k + 3$

In [17]:
plt.figure(figsize=(10,6))
plt.subplot(221)
plt.grid()
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.cos(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.scatter(np.linspace(0,0.1,19,endpoint=False),clippling(A * np.cos(2 * np.pi * f0 * np.linspace(0,0.1,19,endpoint=False))), color='indigo',lw=3)
plt.xlim((0,0.1))
plt.ylim((-1.2,1.2))
plt.subplot(222)
plt.grid()
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.sin(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.scatter(np.linspace(0,0.1,19,endpoint=False),clippling(A * np.sin(2 * np.pi * f0 * np.linspace(0,0.1,19,endpoint=False))), color='indigo',lw=3)
plt.xlim((0,0.1))
plt.ylim((-1.2,1.2))
plt.show()

The final platform requires

$1 - cutOff = cos \left( 2\pi \frac{f_0}{f_s} \left( t - 0.5 \right) \right)$

$cutOff - 1 = cos \left( 2\pi \frac{f_0}{f_s} t\right)$

If the final platform only occurs when the $cutOff > 0.9$, then

$f_s > 15.6817 \times f_0$

distribution 4. $\frac{fs}{f_0} = 4k + 4$

In [18]:
plt.figure(figsize=(10,6))
plt.subplot(221)
plt.grid()
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.cos(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.scatter(np.linspace(0,0.1,16,endpoint=False),clippling(A * np.cos(2 * np.pi * f0 * np.linspace(0,0.1,16,endpoint=False))), color='indigo',lw=3)
plt.xlim((0,0.1))
plt.ylim((-1.2,1.2))
plt.subplot(222)
plt.grid()
plt.plot(t, np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, - np.ones_like(t), color = 'dodgerblue', linestyle = '-.', lw=2)
plt.plot(t, clippling(A * np.sin(2 * np.pi * f0 * t)), color = 'tomato', linestyle = '-', lw=2)
plt.scatter(np.linspace(0,0.1,16,endpoint=False),clippling(A * np.sin(2 * np.pi * f0 * np.linspace(0,0.1,16,endpoint=False))), color='indigo',lw=3)
plt.xlim((0,0.1))
plt.ylim((-1.2,1.2))
plt.show()

The final platform requires

$1 - cutOff = cos \left( 2\pi \frac{f_0}{f_s} t\right)$

$cutOff - 1 = cos \left( 2\pi \frac{f_0}{f_s} \left( t + 2 \right) \right)$

If the final platform only occurs when the $cutOff > 0.9$, then

$f_s > 62,7268 \times f_0$

problem 2. insufficient sampling rate for overlapping

In [19]:
fs     = 250        # the sampling rate
cutOff = 0.9        # the percentage for clipping part
f0     = 10         # the frequency for the original signal
N      = 1000       # the number of sampling points

time, signal = clip_sampling(N, f0, cutOff, fs)
freq1, ftMag, ftPha = ft(signal, fs)
freq2, sigSum, sigConti = SpectraFormula(fs, cutOff, False, True)

plt.figure(figsize=(10,12))
plt.subplot(211)
plt.grid()
plt.plot(time, np.imag(signal), color='tomato', label = 'imag', lw=2)
plt.plot(time, np.real(signal), color='dodgerblue', label = 'real', lw=2)
plt.legend(loc=2)
plt.ylim((-1.2,1.2))
plt.xlim((0,0.2))
plt.subplot(212)
plt.grid()
plt.plot(freq1, ftMag, color='orange', label ='Magnitude, simulation', lw=2)
plt.plot(freq2, sigConti, color='tomato', label ='Magnitude, formula', lw=2)
plt.text(-90, 0.85,'overlap',size=20)
plt.annotate('', xy=(-120, 0.1), xytext=(-75, 0.8),arrowprops=dict(facecolor='black',shrink=0.01,connectionstyle="arc3"))
plt.annotate('', xy=(-80, 0.08), xytext=(-75, 0.8),arrowprops=dict(facecolor='black',shrink=0.01,connectionstyle="arc3"))
plt.annotate('', xy=(-40, 0.05), xytext=(-75, 0.8),arrowprops=dict(facecolor='black',shrink=0.01,connectionstyle="arc3"))
plt.text(60, 0.85,'overlap',size=20)
plt.annotate('', xy=(100, 0.08), xytext=(75, 0.8),arrowprops=dict(facecolor='black',shrink=0.01,connectionstyle="arc3"))
plt.annotate('', xy=(60, 0.05), xytext=(75, 0.8),arrowprops=dict(facecolor='black',shrink=0.01,connectionstyle="arc3"))
plt.xlim((-fs/2,fs/2))
plt.legend(loc=1)
plt.show()

From the figure above, there are some burrs, which are supposed not to appear, at the integral multiple of the original frequency. Since the span is not very wide, these burrs are owed to the effect of the overlapping from the neighboring spectra.

The following figure shows the mechanism of these overlapping.

In [20]:
def ContiFormula(span, f0):
    span = np.around(span / f0) * f0
    freq = np.arange(- span / 2, span / 2, 0.001 * f0)
    sigSum = np.zeros_like(freq)
    sigConti = f0 * (2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * (- np.sin(freq / f0 * np.absolute(np.arcsin(1 - cutOff)) - np.pi * freq / (2 * f0)) + np.cos(freq / f0 * np.absolute(np.arcsin(1 - cutOff)))) * f0 ** 2 / (freq * (f0 ** 2 - freq ** 2)) - 2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * (np.cos(freq / f0 * np.absolute(np.arcsin(1 - cutOff)) - np.pi * freq / (2 * f0)) + np.sin(freq / f0 * np.absolute(np.arcsin(1 - cutOff)))) * f0 / (f0 ** 2 - freq ** 2) * 1 / np.tan(np.absolute(np.arcsin(1 - cutOff))) + 1 / np.pi * np.sin(np.pi * freq / f0) * ( f0 / ((f0 ** 2 - freq ** 2) * np.sin(np.absolute(np.arcsin(1 - cutOff)))) - 1/ freq )) 
    return freq, sigConti
    
freq,sigConti = ContiFormula(fs * 2, f0)
    
plt.figure(figsize=(10,3))
plt.subplot(121)
plt.grid()
plt.plot(freq[:int(len(freq)/4)]+fs, np.real(sigConti[:int(len(sigConti)/4)]), color='dodgerblue', linestyle='--', lw=2)
plt.plot(freq[:int(len(freq)/4)], np.real(sigConti[:int(len(sigConti)/4)]), color='dodgerblue', linestyle='-', lw=2)
plt.plot(freq[int(len(freq)/4*3):]-fs, np.real(sigConti[int(len(sigConti)/4*3):]), color='violet', linestyle='--', lw=2)
plt.plot(freq[int(len(freq)/4*3):], np.real(sigConti[int(len(sigConti)/4*3):]), color='violet', linestyle='-', lw=2)
plt.plot(freq[int(len(freq)/4):int(len(freq)/4*3)], np.real(sigConti[int(len(freq)/4):int(len(freq)/4*3)]), color='tomato', lw=2)
plt.plot(-125*np.ones_like(np.arange(-1.5,1.5,0.05)), np.arange(-1.5,1.5,0.05),color='black')
plt.plot(125*np.ones_like(np.arange(-1.5,1.5,0.05)), np.arange(-1.5,1.5,0.05),color='black')
plt.plot(np.zeros_like(np.arange(-1.5,1.5,0.05)), np.arange(-1.5,1.5,0.05),color='silver')
plt.annotate("",xy=(-50, 0.1),xytext=(200, 0.1),arrowprops=dict(facecolor='gray',shrink=0.01,connectionstyle="arc3,rad=0.3"),)
plt.annotate("",xy=(50, 0.2),xytext=(-200, 0.2),arrowprops=dict(facecolor='gray',shrink=0.01,connectionstyle="arc3,rad=-0.3"),)
plt.text(0, -1, "real part superposition", size=12,ha="center", va="center",bbox=dict(boxstyle="round",ec=(1., 0.5, 0.5),fc=(1., 0.8, 0.8),)) 
plt.xlim((-fs,fs))
plt.subplot(122)
plt.grid()
plt.plot(freq[:int(len(freq)/4)]+fs, np.imag(sigConti[:int(len(sigConti)/4)]), color='dodgerblue', linestyle='--', lw=2)
plt.plot(freq[:int(len(freq)/4)], np.imag(sigConti[:int(len(sigConti)/4)]), color='dodgerblue', linestyle='-', lw=2)
plt.plot(freq[int(len(freq)/4*3):]-fs, np.imag(sigConti[int(len(sigConti)/4*3):]), color='violet', linestyle='--', lw=2)
plt.plot(freq[int(len(freq)/4*3):], np.imag(sigConti[int(len(sigConti)/4*3):]), color='violet', linestyle='-', lw=2)
plt.plot(freq[int(len(freq)/4):int(len(freq)/4*3)], np.imag(sigConti[int(len(freq)/4):int(len(freq)/4*3)]), color='tomato', lw=2)
plt.plot(-125*np.ones_like(np.arange(-1.5,1.5,0.05)), np.arange(-1.5,1.5,0.05),color='black')
plt.plot(125*np.ones_like(np.arange(-1.5,1.5,0.05)), np.arange(-1.5,1.5,0.05),color='black')
plt.plot(np.zeros_like(np.arange(-1.5,1.5,0.05)), np.arange(-1.5,1.5,0.05),color='silver')
plt.annotate("",xy=(-50, 0.01),xytext=(200, 0.01),arrowprops=dict(facecolor='gray',shrink=0.01,connectionstyle="arc3,rad=0.3"),)
plt.annotate("",xy=(50, 0.02),xytext=(-200, 0.02),arrowprops=dict(facecolor='gray',shrink=0.01,connectionstyle="arc3,rad=-0.3"),)
plt.text(0, -1, "imaginary part superposition", size=12,ha="center", va="center",bbox=dict(boxstyle="round",ec=(1., 0.5, 0.5),fc=(1., 0.8, 0.8),)) 
plt.xlim((-fs,fs))


plt.figure(figsize=(10,6))
plt.grid()
plt.plot(freq[int(len(freq)/4):int(len(freq)/4*3)], np.absolute(sigConti[int(len(freq)/4):int(len(freq)/4*3)] + np.append(sigConti[int(len(sigConti)/4*3):],np.absolute(sigConti[:int(len(sigConti)/4)]))), color='tomato',label='formula ', lw=2)
plt.plot(freq1, ftMag, color='orange', label='simulation', lw=2)
plt.text(-75, 1, "magnitude overlapping", size=14,ha="center", va="center",bbox=dict(boxstyle="round",ec=(1., 0.5, 0.5),fc=(1., 0.8, 0.8),)) 
plt.xlim((-fs/2,fs/2))
plt.legend(loc=7)
plt.show()

Supposing the original span is $f_s$. Then the resulting spectra is from $-\frac{f_s}{2}$ to $\frac{f_s}{2}$. Since the magnitude of the neighboring spectra, from $\frac{f_s}{2}$ to $f_s$ and $-f_s$ to $-\frac{f_s}{2}$, is at a similar magnitude level of the resulting spectra, which can be calculated from the formula. It can notably affect the displaying of the resulting area owing to the overlapping. The spectra of the span from $\frac{f_s}{2}$ to $f_s$ will superpose on the span from $-\frac{f_s}{2}$ to $0$, while the span from $-f_s$ to $-\frac{f_s}{2}$ will superpose on the span from $0$ to $\frac{f_s}{2}$.

The formula result with overlapping is well fitted to the the simulation.

Addition - how the I and Q part effect the spectra

The original data results an asymmetric spectra. In common sense, the spectra may be symmetric, since both the I and Q part are symmetric function.

To understand the reason for the asymmetric result, we change the original input data format from $I + i * Q$ to $I - i * Q$.

$C_T(t) = I - i * Q = A ( cos(x) - i * sin(x) )$

$A > 0$

Using the formula of Fourier Transformation

$\frak{F}$$ \lbrace C_T(t)\rbrace = TC_{1/T}(f) = \int_{0}^{T}{A * (cos(2\pi f_0 t) - i* sin(2\pi f_0 t)) e^{- i 2\pi f t} dt}$

The final result is

\begin{align} TC_{1/T} \left( f \right) & = \frac{2}{\pi} sin \left(\frac{\pi f}{2f_0}\right) \left(sin \left(2\pi f t_0 - \frac{\pi f}{2 f_0}\right) + cos \left(2\pi f t_0 \right)\right) \frac{f_{0}^{2}}{f \left( f_{0}^{2}-f^2 \right)}\\ & + \frac{2}{\pi} sin \left(\frac{\pi f}{2 f_0}\right) \left( cos \left( 2 \pi f t_0 - \frac{\pi f}{2 f_0}\right)- sin \left( 2\pi f t_0\right) \right) \frac{f_0}{f_{0}^{2}-f^2} cot \left( 2 \pi f_0 t_0 \right)\\ & - \frac{1}{\pi} sin \left( \frac{\pi f}{f_0}\right) \left( \frac{f_0}{\left( f_{0}^{2} - f^2 \right) sin \left( 2 \pi f_0 t_0\right)} + \frac{1}{f} \right) \end{align}
\begin{equation*} t_0 = \frac{\left| arcsin \left( 1 - cutOff\right)\right|}{2\pi f_0} \end{equation*}

Since $f$ the multiples of the $f_0$ ($f = n * f_0$, $n$ is integer), the magntitude of the spectra is not zero only when $n = 4 k + 1$ ($k$ is the integer). The value of non-zero peak is

$TC_{1/T}\left(f = (4 k + 3) f_0 \right) = \frac{4}{\pi} \frac{f_0}{f_{0}^{2} - f^2} \left( cos \left( 2\pi f t_0 \right) \frac{f_0}{f} - sin \left( 2\pi f t_0\right) cot \left( 2 \pi f_0 t_0\right) \right) $

$TC_{1/T}\left(f \neq (4 k + 3) f_0 \right) = 0$

In [21]:
def SpectraFormulaMod(span,cutOff, showResult, showConti):
    '''
    return the spectra from the modified formula
    the data format is I - i * Q
    ------------------
    span:        display from - span /2 to span/2
    cutOff:      precentage of the clipping in the whole magnitude
    showResult:  display the spectra of the peaks
    showConti:   display the continuous spectra of the formula
    -------------------
    freq:        display frequency range
    sigSum:      peaks
    sigConti:    continuous spectra
    '''
    span = np.around(span / f0) * f0
    freq = np.arange(- span / 2, span / 2, 0.001 * f0)
    sigSum = np.zeros_like(freq)
    sigConti = f0 * np.absolute(2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * (np.sin(freq / f0 * np.absolute(np.arcsin(1 - cutOff)) - np.pi * freq / (2 * f0)) + np.cos(freq / f0 * np.absolute(np.arcsin(1 - cutOff)))) * f0 ** 2 / (freq * (f0 ** 2 - freq ** 2)) + 2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * (np.cos(freq / f0 * np.absolute(np.arcsin(1 - cutOff)) - np.pi * freq / (2 * f0)) - np.sin(freq / f0 * np.absolute(np.arcsin(1 - cutOff)))) * f0 / (f0 ** 2 - freq ** 2) * 1 / np.tan(np.absolute(np.arcsin(1 - cutOff))) - 1 / np.pi * np.sin(np.pi * freq / f0) * ( f0 / ((f0 ** 2 - freq ** 2) * np.sin(np.absolute(np.arcsin(1 - cutOff)))) + 1/ freq )) 
    startFreq = (- np.floor((- f0 + span / 2) / (4 * f0)) * 4 - 1) * f0 
    while (startFreq <= span/2):
        point = np.int((startFreq + span / 2) / (0.001 * f0))-1
        if np.int(startFreq) == - f0:
            sigSum[point] = f0 * np.absolute(1 / (f0 * np.pi) * 1 / np.sin(f0 * np.absolute(np.arcsin(1 - cutOff) / f0))) * (2 * f0 * np.absolute(np.arcsin(1 - cutOff) / f0) + np.sin(2 * f0 * np.absolute(np.arcsin(1 - cutOff) / f0)))
        else:
            sigSum[point] = f0 * np.absolute(4 / np.pi * f0 / (startFreq ** 2 - f0 ** 2) * (np.cos( startFreq * np.absolute(np.arcsin(1 - cutOff) / f0)) * f0 / startFreq - np.sin(startFreq * np.absolute(np.arcsin(1 - cutOff) / f0)) / np.tan(np.absolute(np.arcsin(1 - cutOff)))))
        startFreq += 4 * f0
    if showResult:
        plt.grid()
        plt.plot(freq, sigSum, color='dodgerblue', linestyle='-', lw=2, zorder=-1)
        if showConti:
            plt.plot(freq, sigConti, color='tomato', linestyle='-', lw=2, zorder=-1)
        plt.show()
    return freq, sigSum, sigConti

freq, sigSum, sigConti = SpectraFormula(200,0.4, True, True)
freq, sigSum, sigConti = SpectraFormulaMod(200,0.4, True, True)

The two figure above show the result of $I + i * Q$ and $I - i * Q$ are symmetric with respect to the y-axis.

Using the two formula, we can get the result of I and Q

\begin{align} I & = \frac{2}{\pi} sin \left( \frac{\pi f}{2 f_0} \right) cos \left( 2 \pi f t_0 \right) \frac{f_0^2}{f \left( f_0^2 - f^2 \right)} \\ & - \frac{2}{\pi} sin \left( \frac{\pi f}{2 f_0} \right) sin \left( 2 \pi f t_0 \right) \frac{f_0}{f_0^2 - f^2} cot \left( 2 \pi f_0 t_0 \right) \\ & - \frac{1}{\pi} sin \left( \frac{\pi f}{f_0} \right) \frac{1}{f} \end{align}
\begin{align} Q & = i * \frac{2}{\pi} sin \left( \frac{\pi f}{2 f_0} \right) sin \left( 2 \pi f t_0 - \frac{\pi f}{2 f_0} \right) \frac{f_0^2}{f \left( f_0^2 - f^2 \right)} \\ & + i * \frac{2}{\pi} sin \left( \frac{\pi f}{2 f_0} \right) cos \left( 2 \pi f t_0 - \frac{\pi f}{2 f_0} \right) \frac{f_0}{f_0^2 - f^2} cot \left( 2 \pi f_0 t_0 \right) \\ & - i * \frac{1}{\pi} sin \left( \frac{\pi f}{f_0} \right) \frac{f_0}{\left( f_0^2 - f^2 \right) sin \left( 2 \pi f_0 t_0 \right)} \end{align}
In [22]:
span = np.around(span / f0) * f0
freq = np.arange(- span / 2, span / 2, 0.001 * f0)
sigI = f0 * (2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * np.cos(freq / f0 * np.absolute(np.arcsin(1 - cutOff))) * f0 ** 2 / (freq * (f0 ** 2 - freq ** 2)) - 2 / np.pi * np.sin(np.pi * freq / (2 * f0)) *   np.sin(freq / f0 * np.absolute(np.arcsin(1 - cutOff))) * f0 / (f0 ** 2 - freq ** 2) * 1 / np.tan(np.absolute(np.arcsin(1 - cutOff))) - 1 / np.pi * np.sin(np.pi * freq / f0) * 1/ freq )
sigQ = - f0 * (2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * np.sin(freq / f0 * np.absolute(np.arcsin(1 - cutOff)) - np.pi * freq / (2 * f0)) * f0 ** 2 / (freq * (f0 ** 2 - freq ** 2)) + 2 / np.pi * np.sin(np.pi * freq / (2 * f0)) * np.cos(freq / f0 * np.absolute(np.arcsin(1 - cutOff)) - np.pi * freq / (2 * f0)) * f0 / (f0 ** 2 - freq ** 2) * 1 / np.tan(np.absolute(np.arcsin(1 - cutOff))) - 1 / np.pi * np.sin(np.pi * freq / f0) * f0 / ((f0 ** 2 - freq ** 2) * np.sin(np.absolute(np.arcsin(1 - cutOff))))) 

plt.grid()
plt.plot(freq, sigI, color='dodgerblue', label='I', linestyle='--', lw=2, zorder=-1)
plt.plot(freq, sigQ, color='tomato', label='i * Q', linestyle='--', lw=2, zorder=-1)
plt.plot(freq, np.absolute(sigI + sigQ), color='orange', label='I + i * Q', linestyle='-', lw=2, zorder=-1)
plt.legend(loc=1)
plt.show()

From the above figure, we can find out that the asymmetric of the spectra is resulting from the combination of a y-axis symmetric spectra of I and a zero-point symmetric spectra of Q.

When $n = 2 m$ ($f = n * f_0$, $n$ and $m$ are integers), the magntitude of both spectra are zero.
When $n = 2 m + 1$, the absoulte value of the both spectra are the same. Then the combination is resulting from interference.

For $I + i * Q$,

  • when $n = 4 k + =3$ ($k$ is the integer), the combination of the spectra enhances.
  • When $n = 4 k + 1$ ($k$ is the integer), the combination of the spectra attenuates.

(Contact | Bug Reports | Home | Copyright 2025 Q.Wang)