(Contact | Bug Reports | Home | Copyright 2025 Q.Wang)
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)$
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.
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
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$
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)
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())
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()
The spectrum of the clipping signal
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()
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.
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.
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.
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$
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$
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$
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$
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.
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.
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
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$
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
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$,
(Contact | Bug Reports | Home | Copyright 2025 Q.Wang)