mirror of
https://gitlab.science.ru.nl/mthesis-edeboone/m.internship-documentation.git
synced 2024-12-22 04:53:37 +01:00
Figure: Fourier Waveform and DTFT/DFT
This commit is contained in:
parent
1abd4cdd27
commit
26295dc5ba
4 changed files with 239 additions and 0 deletions
11
figures/methods/fourier/Makefile
Normal file
11
figures/methods/fourier/Makefile
Normal file
|
@ -0,0 +1,11 @@
|
|||
.PHONY: all
|
||||
|
||||
all: waveforms.pdf
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@rm -v waveforms.*
|
||||
@rm -v spectrum.*
|
||||
|
||||
waveforms.% spectrum.% : src/fourier_figure.py
|
||||
$< .
|
BIN
figures/methods/fourier/spectrum.pdf
Normal file
BIN
figures/methods/fourier/spectrum.pdf
Normal file
Binary file not shown.
228
figures/methods/fourier/src/fourier_figure.py
Executable file
228
figures/methods/fourier/src/fourier_figure.py
Executable file
|
@ -0,0 +1,228 @@
|
|||
#!/usr/bin/env python3
|
||||
# vim: fdm=marker fmr=<<<,>>>
|
||||
|
||||
__doc__ = \
|
||||
"""
|
||||
Create one/two figures exemplifiying the fourier transform of noisy sine wave.
|
||||
"""
|
||||
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.gridspec as gridspec
|
||||
import scipy.fft as ft
|
||||
|
||||
rng = np.random.default_rng()
|
||||
|
||||
def fft_spectrum( signal, sample_rate, fft=None, freq=None):
|
||||
N_samples = len(signal)
|
||||
real_signal = np.isrealobj(signal)
|
||||
|
||||
if fft is None:
|
||||
if real_signal:
|
||||
fft = ft.rfft
|
||||
freq = ft.rfftfreq
|
||||
else:
|
||||
fft = ft.fft
|
||||
freq = ft.fftfreq
|
||||
|
||||
if freq is None:
|
||||
freq = ft.fftfreq
|
||||
|
||||
spectrum = fft(signal) / len(signal)
|
||||
if real_signal:
|
||||
spectrum *= 2
|
||||
freqs = freq(N_samples, 1/sample_rate)
|
||||
|
||||
return spectrum, freqs
|
||||
|
||||
def dtft_spectrum(signal, time, freqs):
|
||||
freqtime = np.outer(freqs, time)
|
||||
|
||||
# determine all coefficients in one go
|
||||
c_k = 2*np.cos(2*np.pi*freqtime) / len(signal)
|
||||
s_k = -2*np.sin(2*np.pi*freqtime) / len(signal)
|
||||
|
||||
# dot product gives the dtft
|
||||
result = np.dot(c_k, signal) + 1j*np.dot(s_k, signal)
|
||||
|
||||
return result, freqs
|
||||
|
||||
## From https:
|
||||
def multiple_formatter(denominator=2, number=np.pi, latex='\pi'):
|
||||
def gcd(a, b):
|
||||
while b:
|
||||
a, b = b, a%b
|
||||
return a
|
||||
|
||||
def _multiple_formatter(x, pos):
|
||||
den = denominator
|
||||
num = int(np.rint(den*x/number))
|
||||
com = gcd(num,den)
|
||||
(num,den) = (int(num/com),int(den/com))
|
||||
if den==1:
|
||||
if num==0:
|
||||
return r'$0$'
|
||||
if num==1:
|
||||
return r'$%s$'%latex
|
||||
elif num==-1:
|
||||
return r'$-%s$'%latex
|
||||
else:
|
||||
return r'$%s%s$'%(num,latex)
|
||||
else:
|
||||
if num==1:
|
||||
return r'$\frac{%s}{%s}$'%(latex,den)
|
||||
elif num==-1:
|
||||
return r'$\frac{-%s}{%s}$'%(latex,den)
|
||||
else:
|
||||
return r'$\frac{%s%s}{%s}$'%(num,latex,den)
|
||||
return _multiple_formatter
|
||||
#
|
||||
|
||||
def phase_plot(ax, phase_on_xaxis=False, limits=(-1*np.pi, +1*np.pi), major_ticks_div=1, minor_ticks_div=4):
|
||||
set_label = ax.set_ylabel
|
||||
set_lims = ax.set_ylim
|
||||
axis = ax.yaxis
|
||||
if phase_on_xaxis:
|
||||
set_label = ax.set_xlabel
|
||||
set_lims = ax.set_ylim
|
||||
axis = ax.xaxis
|
||||
|
||||
set_label("Phase")
|
||||
if limits is not None:
|
||||
set_lims(*limits)
|
||||
ax.margins(y=0.2)
|
||||
|
||||
if major_ticks_div:
|
||||
axis.set_major_locator(plt.MultipleLocator(np.pi / major_ticks_div))
|
||||
if minor_ticks_div:
|
||||
axis.set_minor_locator(plt.MultipleLocator(np.pi / minor_ticks_div))
|
||||
axis.set_major_formatter(plt.FuncFormatter(multiple_formatter()))
|
||||
|
||||
return ax
|
||||
|
||||
def main(
|
||||
f_sine=0.05153, # GHz
|
||||
noise_sigma = 0.5,
|
||||
sine_amplitude = 1,
|
||||
phase=0.4,
|
||||
):
|
||||
|
||||
t_fine = np.linspace(0, 200, 500) # ns
|
||||
t_sampled = np.linspace(0, 200, 50) # ns
|
||||
sine_fine = sine_amplitude * np.cos( 2*np.pi * f_sine * t_fine + phase)
|
||||
|
||||
sine_sampled = sine_amplitude * np.cos( 2*np.pi * f_sine * t_sampled + phase)
|
||||
combined = noise_sigma*rng.normal(size=len(sine_sampled)) + sine_sampled
|
||||
|
||||
figs = []
|
||||
# time domain
|
||||
if True:
|
||||
fig, ax = plt.subplots()
|
||||
ax.set_xlabel("Time [ns]")
|
||||
ax.set_ylabel("Amplitude")
|
||||
|
||||
ax.plot(t_fine, sine_fine, label="Clean Signal")
|
||||
ax.plot(t_sampled, combined, label="+ Noise", marker='o')
|
||||
|
||||
ax.legend()
|
||||
ax.grid()
|
||||
|
||||
figs.append(ax.get_figure())
|
||||
|
||||
# frequency domain
|
||||
if True:
|
||||
fft_spec, fft_freqs = fft_spectrum(combined, 1/(t_sampled[1] - t_sampled[0]))
|
||||
|
||||
dtft_freqs = np.linspace(fft_freqs[0], fft_freqs[-1], 500)
|
||||
|
||||
dtft_spec, dtft_freqs = dtft_spectrum(combined, t_sampled, dtft_freqs)
|
||||
|
||||
fig2 = plt.figure()
|
||||
gs = gridspec.GridSpec(2, 1, figure=fig2, height_ratios=[3,1], hspace=0)
|
||||
|
||||
ax1 = fig2.add_subplot(gs[:-1, -1])
|
||||
ax2 = fig2.add_subplot(gs[-1, -1], sharex=ax1)
|
||||
|
||||
axes = np.array([ax1, ax2])
|
||||
|
||||
ax2.set_xlabel("Frequency [GHz]")
|
||||
if True:
|
||||
dtft_freqs *= 1e3
|
||||
fft_freqs *= 1e3
|
||||
f_sine *= 1e3
|
||||
ax2.set_xlabel("Frequency [MHz]")
|
||||
|
||||
ax1.xaxis.tick_top()
|
||||
[label.set_visible(False) for label in ax1.get_xticklabels()]
|
||||
|
||||
|
||||
# indicate f_sine
|
||||
for ax in axes:
|
||||
ax.axvline(f_sine, label="$f_\mathrm{sin}$", ls='dashed', color='red')
|
||||
|
||||
# plot amplitudes
|
||||
ax1.set_ylabel("Amplitude")
|
||||
ax1.set_ylim(0, None)
|
||||
ax1.yaxis.get_major_ticks()[0].set_visible(False)# suppress 0.0
|
||||
ax1.grid()
|
||||
ax1.plot(fft_freqs, np.abs(fft_spec), marker='o', label='DFT')
|
||||
ax1.plot(dtft_freqs, np.sqrt(np.abs(dtft_spec)**2), label='DTFT')
|
||||
|
||||
ax1.legend()
|
||||
|
||||
# plot phases
|
||||
phase_plot(ax2, limits=[-1.1*np.pi, +1.1*np.pi])
|
||||
ax2.grid()
|
||||
|
||||
ax2.plot(fft_freqs, np.angle(fft_spec), marker='o', label='DFT')
|
||||
ax2.plot(dtft_freqs, np.angle(dtft_spec), label='DTFT')
|
||||
|
||||
figs.append(fig2)
|
||||
|
||||
return figs
|
||||
|
||||
if __name__ == "__main__":
|
||||
from argparse import ArgumentParser
|
||||
import os.path as path
|
||||
|
||||
import os
|
||||
import sys
|
||||
# Append parent directory to import path so pyfiglib can be found
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
import pyfiglib as pfl
|
||||
|
||||
|
||||
parser = ArgumentParser(description=__doc__)
|
||||
parser.add_argument("fname", metavar="path/to/figure[/]", nargs="*", help="Location for generated figure, will append __file__ if a directory. If not supplied, figure is shown.", default=os.getcwd())
|
||||
|
||||
args = parser.parse_args()
|
||||
default_extensions = ['pdf', 'png']
|
||||
default_names = ['waveforms', 'spectrum']
|
||||
|
||||
if args.fname == 'none':
|
||||
args.fname = None
|
||||
|
||||
pfl.rcParams['font.size'] = 20
|
||||
pfl.rcParams['figure.figsize'] = (8,6)
|
||||
pfl.rcParams['figure.constrained_layout.use'] = True
|
||||
|
||||
###
|
||||
figs = main()
|
||||
|
||||
### Save or show figures
|
||||
if not args.fname:
|
||||
# empty list, False, None
|
||||
plt.show()
|
||||
else:
|
||||
|
||||
for i, f in enumerate(figs):
|
||||
|
||||
if len(args.fname) == 1 and len(figs) != 1:
|
||||
for ext in default_extensions:
|
||||
f.savefig(path.join(args.fname[0], default_names[i]) + "." + ext, transparent=True)
|
||||
else:
|
||||
f.savefig(args.fname[i], transparent=True)
|
||||
|
||||
|
||||
|
BIN
figures/methods/fourier/waveforms.pdf
Normal file
BIN
figures/methods/fourier/waveforms.pdf
Normal file
Binary file not shown.
Loading…
Reference in a new issue