John A. Marohn (jam99@cornell.edu)
Department of Chemistry and Chemical Biology
Cornell University
Ithaca, NY USA; 14853-1301
2014/07/01 -- 2014/07/02
We use freqdemod
's fit()
function to recover transient frequency chenges in two frequency-modulated sine-wave signals (1) a signal that we generate here and (2) a signal generated experimentally by Sarah Nathan in a scanning Kelvin probe force microscope experiment.
In [1]:
import os, sys
module_path = os.path.abspath(os.path.join(os.getcwd(), '..'))
sys.path.append(module_path)
from freqdemod import Signal
Set the digitization frequency to 100 kHz, corresponding to a time step of 10 us. We want a signal with quite a few points, say 512k = 512 x 1024 = 524288 points. The duration of the signal will thus be 524288 x 10E-6 = 5.24288 seconds.
The sine wave has an initial frequency of 4 kHz. Between 1.5 s and 3.5 s, the frequency is ramped linearly to 6 kHz. It remains 6 kHz until the end of the signal.
In [2]:
import math
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# set values
fd = 100E3
nt = 512*1024
f_start = 4.000E3
f_end = 6.000E3
t_start = 1.5
t_end = 3.5
# time array
dt = 1/fd
t1 = dt*np.arange(int(round(t_start/dt)))
t2 = dt*np.arange(int(round((t_end-t_start)/dt)))
t3 = dt*np.arange(nt-t1.size-t2.size)
t2plus = t2+t1[-1]+dt
t3plus = t3+t2plus[-1]+dt
t = np.append(np.append(t1,t2plus),t3plus)
# frequency array
f1 = f_start*np.ones(t1.size)
f2 = f_start + t2*(f_end-f_start)/(t2[-1]-t2[0])
f3 = f_end*np.ones(t3.size)
f = np.append(np.append(f1,f2),f3)
# phase accumulator
p = np.zeros(t.size)
p[0] = 0.0
for k in np.arange(1,t.size):
p[k] = p[k-1] + dt*f[k-1]
p = 2*np.pi*p
# the signal
s = np.cos(p)
... to recover the oscillation frequency. We have a few choices to make:
We choose window rise/fall time of 1 ms
We choose a filter bandwidth of 5 kHz, larger than the 2 kHz width of the frequency sweep.
Finally we need to choose the "chunk" time. We want to choose this so that the associated Nyquist frequency is larger than the filter bandwidth. This is so that we do not alias any noise into the low-frequency part of the frequency power spectrum. Choose 1/(10 kHz) = 100 us
In [3]:
S = Signal(s,"x","nm",1/fd)
S.binarate("middle")
S.window(tw=1.0E-3)
S.fft()
S.filter(bw=5.0E3)
S.ifft()
S.trim()
S.fit(dt_chunk_target=100E-6)
print(S)
In [4]:
S.plot_phase_fit()
In [5]:
import h5py
with h5py.File('JAM_9p90__copy.h5', 'r') as h5f:
s = h5f['s'][:]
... to recover the oscillation frequency. We have a few choices to make:
We choose window rise/fall time of 10 ms.
We choose a filter bandwidth of 1 kHz -- large enough to capture a ms transient but small enough to give us some signal averaging.
Choose the "chunk" time to be 0.5 ms; the associated Nyquist frequency is 2 kHz -- larger than the filter bandwidth.
In [6]:
S = Signal(s,"x","nm",1/1.15E6)
S.binarate("middle")
S.window(tw=10.0E-3)
S.fft()
S.filter(bw=1.0E3)
S.ifft()
S.trim()
S.fit(dt_chunk_target=0.50E-3)
print(S)
In [7]:
print "number of elements = {}".format(S.signal['fit_freq'].size)
print "number of nan = {}".format(np.count_nonzero(np.isnan(S.signal['fit_freq'])))
S.plot_phase_fit()
In [9]:
import matplotlib.pyplot as plt
import copy
# make a copy of the fitten data
fit_freq_temp = copy.deepcopy(S.signal['fit_freq'])
fit_time_temp = copy.deepcopy(S.signal['fit_time'])
# caculate a frequency shift by subtrating off
# the average over the first third of the data
fit_freq_size = fit_freq_temp.size
fit_freq_size_avg = fit_freq_temp[0:fit_freq_size/3].mean()
fit_freq_temp = fit_freq_temp - fit_freq_size_avg
# define a zoom window -- define as a perecentage
# the the window still works if we change the chunk time
# for curve fitting
start = 0.47*fit_freq_size
stop = 0.52*fit_freq_size
# plot the frequency shift [Hz] versus time [ms]
# reset the time at the start of the plot to zero
x = 1E3*(fit_time_temp[start:stop] - fit_time_temp[start])
y = fit_freq_temp[start:stop]
plt.rcParams['text.usetex'] = True
plt.plot(x,y,'ko-')
plt.ylabel(r"$\Delta f \: \mathrm{[Hz]}$")
plt.xlabel(r"$\Delta t \: \mathrm{[ms]}$")
plt.show()