Category Archives: 工作笔记

科研和实验笔记

Fourier变换(三)

[mathjax]
通过上一篇我们已经学会怎样通过Fourier变换重构应变和应力的波形。至少In/1是已经可以直接用Fourier变换的结果计算了。本文先介结实际实验数据的导入,再继续上一篇的话题介绍如何计算In/1和其余的LAOS参数。

LAOS实验是怎么实施的

我们是在TA ARES-RFS流变仪上做LAOS实验。公司的名字:TA Instruments。这是一台应变控制型流变仪(strain-controlled rheometer),相比之下同公司的AR-G2则是应力控制型(stress-controlled)。这个小册子上介绍了ARES和AR-G2两台流变仪的特点,其中的原理图和具体参数可以用在毕业论文中。进行LAOS实验需要流变仪持续对样品施加正弦应变,我们是利用流变仪控制软件TA Orchestrator 7.2.1自带的测试模式dynamic time sweep来控制流变仪进行这一操作。Dynamic time sweep是固定振荡的应变幅度γ0和频率ω,测量G’G”随时间变化的测试,所以正适合我们。在Orchestrator软件中设定好dynamic time sweep的应变和频率,仪器就会以相应的正弦函数去控制马达了。此时,我们需要马上从仪器背后的模拟输出端口同时读出仪器的测量信号,并用一个数模转换器(analog-to-digital converter, ADC)转换成数字信号,输入到计算机。我们用的ADC是NI USB-9215A,要通过电脑来控制它何时开始和停止采样,以及以多大的采样频率进行采样。许多软件都可以通过它的驱动程序接口来实施控制(MATLAB本身就可以),在LAOS实验中我们用一个LabView开发的工具FT Meassurement来采集数据。因此,我们用一台电脑,用两个软件分而控制不同的过程。Orchestrator控制流变仪的运作,而FT Meassurement控制ADC。这一流程图可参考[1][2]中的用图。具体信号线接法和处理方式见下面的PPT(可下载)。

上篇文章也说过,从流变仪采集到的一个是马达位移的电压信号,另一个是力矩的电压信号,它们都要乘以一个系数,才是应变和应力。从ARES流变仪的说书中可以查到背板输出信号的具体情况:

  • TORQUE OUT:从0到满量程,对应电压是0 ~ 5 V。由于转矩可以有两个方向,所以总的电压范围就是-5 ~ +5 V
  • STRAIN OUT:虽然标记是STRAIN,但其实只是马达转动的角位移。从0 ~ 0.5 rad对应电压是0~5V,考虑两个转动方向,总电压范围是-5~+5 V

TORQUE OUT的“满量程”到底是多少呢?由于我们的ARES有两个档位的力矩传感器。Xducer 1的传感器叫做“20g FRT”。20g是指该档位的最大量程是20 g·cm,力矩的一个非SI单位,SI单位是N·m。1 g·cm = 98.066E-6 N·m。可以去此网站进行换算。FRT是force rebalance transducer的缩写,这是ARES的传感器采用的一种技术(参考)。所以20g FRT的最大量程是1.96E-3 N·m。另一个档位Xducer2是1k FRT,也就是最大量程是0.0981N·m。因此,到底TORQUE OUT输出的电压值对应多大的力矩,要看当时仪器在线的传感器档位是Xducer1还是Xducer2。以下是更详细的参数:

Transducer 1 2
Transducer Description 20g FRT 1k FRT
Maximum Torque (g·cm) 20 1000
Minimum Torque (g·cm) 0.004 1
Torque Calibration Value 21.34 1050.7
Torque Compliance 6.67E-06 6.67E-06
Inertia Constant (g·cm^2) 660 660
Phase Adjust Constant (rad·s/rad) -5.80E-05 -5.80E-05
Maximum Normal Force (g) 2000 2000
Minimum Normal Force (g) 2.00E+00 2.00E+00
Normal Calibration Value 2101.8 2101.8
Normal Compliance (um/kg) 0.00E+00 0.00E+00
Phase Offset (rad) 0 0
Motor Temp Reference (°C) 3.27E+01
Motor Temp Gain 2.9445
Compliance Gain 7.89E-01

记STRAIN OUT输出电压为Vs,TORQUE OUT输出电压为Vt,换算成相应的角位移θ转矩M的公式是:\theta=k_\text{s}V_\text{s}M=k_{\text{t},i}V_\text{t},其中,转换系数kt = 0.1 rad/V,kt,1 = 3.92E-4 N·m/V,kt,2 = 0.0196 N·m/V。从角位移(rad)到应变(量纲一量),以及从转矩(N·m)到应力(Pa),还各需要乘一个系数FγFσ,这个系数跟所选用的夹具种类和尺寸有关。流变仪的说明书会介绍各种夹具的系数如何从夹具的尺寸来换算。这里只举锥板夹具的例子:

Figure 1 Cone-plate geometry

Figure 1 Cone-plate geometry

$$F_\gamma=\frac{1}{\tan\alpha},\quad F_\sigma=\frac{3}{2\pi R^3}$$所以,最终的应变和应力应该是$$\gamma=F_\gamma k_\text{s}V_\text{s},\quad\sigma=F_\sigma k_{\text{s},i}V_\text{t}$$

实验数据处理

从仪器背板TORQUE OUT和STRAIN OUT连线到ADC的channel 1和channel 2输入,然后在计算机中的FT Measurement控制ADC以一定的采样频率fs进行采样,就会生成一个文本数据文件,此例的文件名是“DTimeSwp (13) 000 .txt”。第一行是一些说明性的数字,然后就是两列数据,分别对应着channel 1和channel 2的电压测量值,按照通常的接线方式,第一列是STRAIN OUT信号,第二列是TORQUE OUT。在MATALB中预览出下:

Figure 2 Data file from FT Measurement

Figure 2 Data file from FT Measurement

用importdata命令可以自动处理具有表头的数据文件,把表头文字和数据分开。


clear all
clc


A=importdata('DTimeSwp (13) 000 .txt')

观察A的返回信息。如果源文件没有表头,是纯数据,A就是直接由这些数据组成的矩阵;如果有表头,A就是一个struct类型,包含一个data单元和一个textdata单元。textdata是以表头为内容的字符串,data单元就是数据组成的矩阵。要调用data,语法是A.data。为了简单,就把数据重新放在一个新的变量中:


x=A.data;

DTimeSwp (13) 000 .txt这个文件,对应的是一个dynamic time sweep测试,命令的应变幅度γ0 = 160%,角频率是ω = 6.283 rad/s(f = 1.0 Hz)。FT Measurement控制ADC以fs = 200 Hz的采样频率进行采样。我们可以从fs重构一列时间数据,拼到变量x中:


fs=200; % the sampling frequency was fixed by the test condition

t=0:1/fs:(length(x)-1)/fs; % construct the time series
t=t(:);

x=[t x]; % combine the time series and the signals

这样,变量x第一列是时间,第2、3列才分别是STRAIN OUT和TORQUE OUT电压信号。这个实验是用一个锥板夹具来做的,直径50 mm,锥度0.04 rad。测试时,在线传感器是xducer 1。因此根据计算公式,Fγ = 24.99,Fσ = 30557.75,kskt,1的值前文已经过了。把x变量的数据转换成应力和应变:


% set all the coefficients
F_gamma=24.99;
F_sigma=30557.75;
ks=0.1;
kt1=3.92e-4;


% convert voltage to strain/stress
x(:,2)=F_gammaks.x(:,2);
x(:,3)=F_sigmakt1.x(:,3);

获取信号的频率

至此,对变量x的波形重构可以完全跟之前文章介绍的一样的,不再重复。但是本文再作一项改进。前面几篇文章说过,实际应变值跟软件设定的命令有可能有所偏差,加上仪器噪音的影响,所以我们要重构应变的信号来准确获得实际的应变幅度和频率。而准确的FFT结果要求待做FFT的信号是“相干取样”的。做相干取样又要求我们至少要准确知道信号的频率。解决这一矛盾的方法是,利用应变必然是正弦波的性质,先通过正弦函数拟合来获取信号的应变和频率。事实上,对于一个正弦波,正弦拟合和FFT都是重构的方法,但实际上正弦拟合的幅度和相位往往不准,所以只要能做好相干取样,我们都偏爱用FFT做重构;但是正弦拟合得到的频率是准的,而这时我们只需要知道一个频率,因此可以采用正弦拟合来完成。

我们调用一个别人开发的代码进行:sinfapm。其语法在代码开头的注释文件中有清楚的介绍。注意,sinfapm代码又调用了另一个用于进行非线性最小平方求解的第三方代码:LMFnlsq,需要都放在同一个文件夹。

sinfapm函数还有一个麻烦之处,就是处理取样频率太高的数据会失效。所以,在使用前要临时对一个未知信号“降采样”(down sampling)。经验表明,对每个周期不超过1000个数据点的数据,sinfapm能够稳定表现。因此就是在使用sinfapm前要加一段代码,如果信号每周期超过1000个点,就降采样为每周期1000个点;否则就不变。麻烦的是,为了知道原信号每周期有多少个点,我们还是要先知道信号的频率。所幸,这一步只需要粗略估算即可,因此,做一个不靠谱(没有相干采样)的FFT粗估一下频率就行了。总之,整个精确获得频率的步骤就是:

  1. 用FFT粗估频率
  2. 根据粗估频率计算信号每周期有多少个数据点,决定降取样比值
  3. 对降取样的数据进行正弦拟合得到精确的频率

以下是代码:


% Roughly estimate the input frequency by FFT
N=length(x);
Xraw=fft(x(:,2)); % needless to renormalized the amplitude coz we only want the frequency

% find the maximum peak and obtain the corresponding row index
% the first data is the DC component which is omitted because the value may
% be higher than the first harmonic and violate the peak finding
[I, J]=max(abs(Xraw(2:end)));
% I is useless

% estimate the points per cycle of the signal
pts_per_cycle=N/J;

if pts_per_cycle > 1000
% select 1 out of every jump data to reach 1000 points per cycle
jump = floor(pts_per_cycle/1000);
else
jump=1;
end

[f, amp, phi]=sinfapm(x(1:jump:end,2),fs/jump);
% amp and phi are useless

f

我们可以作图验证sinfapm是否正常工作了:


plot(t,ampsin(2pift+phi),'r-',t,x(:,2),'bo') % Figure 3

Figure 3 Effect of sinfapm

Figure 3 Effect of sinfapm

连幅度和相位都拟合得这么好,频率的拟合结果应该是可信的了。频率知道了之后,剩下的事情就不新鲜了,参见前一篇文章。


N=length(x);
pts_per_cycle=fs/f;
N_cycle=floor(N/pts_per_cycle);

N_cut=round(round(N_cycle/3)*pts_per_cycle); % use the last third of cycles
x=x(length(x)-N_cut:end,:); % crop the last N_cut data

Xraw=2*fft(x(:,2))/N_cut;
Xraw=Xraw(1:ceil(N_cut/2));
freq=0:fs/N_cut:fs/2-fs/N_cut;

A=abs(Xraw(round(N_cutf/fs)+1));
delta0=wrapTo2Pi(angle(Xraw(round(N_cut
f/fs)+1))+0.5pi);
frq=freq(round(N_cut
f/fs)+1); % this line can be omitted and use the f instead
x_rec=A.sin(2pifrq.x(:,1)+delta0); % frp can be replaced by f

Yraw=2*fft(x(:,3))/N_cut;
Yraw=Yraw(1:ceil(N_cut/2));

max_nr_harm=floor(fs/(2*f));
if max_nr_harm/2==floor(max_nr_harm/2)
max_nr_harm=max_nr_harm-1;
end

max_nr_harm=max_nr_harm-2;

if max_nr_harm>25
max_nr_harm=25;
end

I_n=zeros((max_nr_harm+1)/2,1);
delta_n=zeros((max_nr_harm+1)/2,1);

for i=1:(max_nr_harm+1)/2
I_n(i)=abs(Yraw(round((2i-1)N_cutf/fs)+1));
delta_n(i)=angle(Yraw(round((2
i-1)N_cutf/fs)+1))+0.5pi;
end
DC=abs(Yraw(1))
cos(angle(Yraw(1)))/2;

y_rec=zeros(length(x(:,3)),1); % create the variables for the reconstructed signals
for i=1:2:max_nr_harm
y_rec=y_rec+I_n((i+1)/2).sin(i2pif.*x(:,1)+delta_n((i+1)/2));
end
y_rec=y_rec+DC;

plot(x(:,2),x(:,3),'b-',x_rec,y_rec,'r-') % Figure 4

看效果:

Figure 4 Reconstruction of the LAOS results.

Figure 4 Reconstruction of the LAOS results.

我们的信号是已经转换成应力和应变的了。因此,Figure 4中横轴是应变,正负最值都是1.6,与软件命令值(160%)相符。我们换一个应变幅度为250%实验数据,结果如下:

Figure 5 Reconstruction of LAOS data

Figure 5 Reconstruction of LAOS data

γ0 = 250%时已有明显的非线性粘弹性。

计算非线性粘弹性参数

我们平时所说的G’G”,是FFT之后的基频幅值计算的。在线性粘弹性条件下,FFT之后只有一个基频帐,G’G”是“储能模量”和“损耗模量”。但是在非线性粘弹性条件下,G’G”只是许多n倍频谐波中的基频分量(n = 1),它们也没有“储能”或“损耗”的概念了。这一点要先搞清楚。

计算G’G”


delta=wrapTo2Pi(delta_n(1)-delta0);
G_p=cos(delta)I_n(1)/A;
G_pp=sin(delta)
I_n(1)/A;

In/1也可以直接计算:


In1=I_n(2:5)/I_n(1);

如何求切线

现在我们来计算GMGMGk参数。它们的定义已在上一篇文章中给出。根据定义,需要对曲线进行求导。在MATLAB如何做呢?按定义一步步来。

一个已经离散化了的曲线,要在曲线上某几何位置求切线,很可能并没有数据恰好落在这个点上,只能使用这个几何位置邻近的两个数据点,以这两个点所确定的直线作为切线。所以,如果一条曲率较大的曲线,离散化的取样频率又很低,那么很可能两个数据之间相隔很远,按以上办法求出来的切线与理想情况就会相差很远。也就是说,对离散数据求导的效果非常依赖于信号的取样质量(就是取样频率够不够高)。

其次,如果信号有噪音,那么作出的切线斜率将非常容易受噪音影响。在数值方法中,求导是一个放大噪音的操作。一个信号,很可能不求导的话,噪音还在可以接受的范围;一旦求导之后噪音就使信号无法分辨。因此,对离散数据求导的效果又非常依赖于信号的信噪比。

因此为了求得准确、稳定的导数,我们要采取一系列的办法。第一当然就是测试的时候让ADC以最高的采样频率进行采样(100 kHz)。

第二,这样高的采样频率,完全可以进行降采样(down sampling)。但我们的降采样方法不是像前文用过的那样简单地扔掉部分数据点——这样对于去噪没有任何效果——而是每几个数据点做一次平均值,这样的做法叫decimation,它的效果相当于使样品经过一个低通滤波品,把高于一定频率的信号消除掉。如果我们每N个数据取一个平均值,那么decimation之后的取样频率就是100000/N Hz。一开始,用远高于需要的取样频率取,称作过取样(oversampling)。Oversampling和decimation是信号采集中的一个基本操作,作为一种提供信噪比的标准步骤。使用FT Measurement采集的流变数据的时候,该软件是同时做了oversampling和decimation的,最终得到的文本数据文件的采样频率是decimation之后的采样频率。这方面内容,也可以参考我的博士论文的第二章。

第三,我们按照LAOS的假设(应力波型只有奇次谐波)对信号进行重构,用一个光滑的、公式计算的曲线来代替实验测得的、带有噪音的曲线。重构曲线是完全没有噪音的。这还不够——

第四,由于信号有很多个周期,那些过零点、最值点之类的几何位置,信号不止经过一次。那么,我们就把每次经过的这些位置所能作出的切线斜率都算出来,然后对这么多个周期得到的斜率曲一个平均值,进一步消除不同周期处的细微差别。

只有做足以上四个方面,才能比较保证地对绝大多数实测的LAOS数据进行准确可靠的求导操作。

在MATLAB中如何在重构的Lissajous曲线上找到要作切线的几何位点?根据定义,GM要找γ = 0,GL要找γ = γ0Gk要找σ = 0。我们需要确定这些几何位置邻近的两个数据点处在数据列中的第几行。以GM的计算为例(找γ = 0)。这相当于要找数据x_rec中取值经过零位的行。而且,我们只关心从小于零变成大于零的地方(这里默默假设了x_rec是没有直流分量的,如果有直流分量,x_rec波形的零位就不落在取值为零处,除非另把DC分量减去)。首先,我们把x_rec数据所有大于零的值标为1,小于零的值标为0。


A=x_rec;
A(A>0)=1;
A(A<0)=0;

在A这个临时变量,凡是导数不为零的地方就是转折点(不是从1变到0就是从0变到1),这些不为零的导数,不是等于1就是等于-1。由于我们只关心x_rec中从小于0变成大于零的地方,所以我们相当于只关心A的导数等于1的地方。


zero_ind=find(diff(A)>0);

以上用到的一些用逻辑式调用矩阵单元的方法,和find命令等,可以自己复习MATLAB的help。zero_ind就是x_rec中取值从负到正的第一个数据,即它是负的,然后第zero_ind+1是正的。我们可以临时作个图看看,我们找到的zero_ind位置都对不对:


plot(1:length(x_rec),x_rec,'-',zero_ind,zeros(length(zero_ind),1),'o') % Figure 6

Figure 6 Effect of zero finding

Figure 6 Effect of zero finding

求导、取平均,得到GM


G_M_raw=(y_rec(zero_ind+1)-y_rec(zero_ind))./(A(zero_ind+1)-A(zero_ind));
G_M=mean(G_M_raw);

我们可以再临时作个图看看这个切线取准了没有。但为作这个图,要先确定真实的位点在哪儿(不能还用那两个邻近数据点)。这个点,应该是两个相邻点所确定的直线与x = 0直线的交点。同样,这个交点也可以通过多个周期求平均而更为准确地确定。最终要作的切线,就是一条过这一交点,斜率为GM的直线,利用简单解析几何知识即可作出:


y_m_raw=-G_M_raw.x_rec(zero_ind+1)+y_rec(zero_ind+1);
y_m=mean(y_m_raw);
lx=min(x_rec):0.01:max(x_rec);
ly=G_M.
lx+y_m;
plot(x_rec,y_rec,'-b',lx,ly,'-r') % Figure 7

Figure 7 Effect of G_M calculation

Figure 7 Effect of G_M calculation

GL要找γ = γ0,即找x_rec中的最值。先对x_rec进行求导,问题就还原成找x_rec求导后的过零位,除了多一步求导之外,其余跟刚才的代码完全相同。注意的时,y_rec有明显的直流分量,计算时要减去。


A=diff(x_rec);
A(A>0)=1;
A(A<0)=0;

zero_ind=find(diff(A)<0);

G_L_raw=(y_rec(zero_ind)-DC)./x_rec(zero_ind);
G_L=mean(G_L_raw);

Gk是找σ = 0:


A=y_rec-DC;
A(A>0)=1;
A(A<0)=0;
zero_ind=find(diff(A)>0);

A=y_rec-DC;
G_k_raw=(A(zero_ind+1)-A(zero_ind))./(x_rec(zero_ind+1)-x_rec(zero_ind));

G_k=mean(G_k_raw);

至此,我已经完整地介绍了如何从实验获得的LAOS数据求出相应的非线性粘弹性参数。我再总结一下流程:

  1. 做实验,设定ADC采集参数进行数据采集(同时已进行oversampling和decimation,规定了信号的fs
  2. 通过对应变信号进行粗估和正弦拟合,获得实验的准确测试频率
  3. 对应变和应力信号进行重构
  4. 按照定义求得各非线性粘弹性参数

如果对实验体系以一系列的应变幅度进行了LAOS实验,然后想考察非线性参数随应变幅度的变化;那么,可以对每个应变幅度得到的LAOS数据做以上同样的操作,既编写一个循环结构来完成。这样的办法将在后面的文章中看到例子,此处不单独举例了。后面的文章主要介绍如何用流变学模型的结果(例如Giesekus模型)来拟合实验数据。

References

  1. K. Hyun, M. Wilhelm, C.O. Klein, K.S. Cho, J.G. Nam, K.H. Ahn, S.J. Lee, R.H. Ewoldt, and G.H. McKinley, "A review of nonlinear oscillatory shear tests: Analysis and application of large amplitude oscillatory shear (LAOS)", Progress in Polymer Science, vol. 36, pp. 1697-1753, 2011. http://dx.doi.org/10.1016/j.progpolymsci.2011.02.002

Fourier变换(二)

[mathjax]
上一篇介绍了用Fourier变换来重构一个未知正弦信号的方法。本篇介绍对于未知正弦信号的情况。

大幅振荡剪切的参数

大幅振荡剪切实验产生的波形是非正弦信号,非正弦是非线性粘弹性的特点。所以对非线性粘弹性程度的表征,就是对波形正弦形状偏离程度的表征。关于大幅振荡剪切和非线性粘弹性的原理与实践,可见Hyhn等的综述[1],或者参阅我的博士学位论文第1.2.1节、第1.3节和第2章。本文只解决MATLAB编程实现问题。

大幅振荡剪切(LAOS)实验中,我们从流变仪同时获取两个信号。一个是应变的信号,一个是应力的信号(事实上,一个是马达位移信号,另一个是力矩信号,它们都要乘以一个系数,才是应变和应力。此文先略去这一事实了)。在应变控制的实验中,我们施加一个正弦的应变\gamma\left(t\right),测量应力的响应\sigma\left(t\right),即
$$\begin{cases}\gamma\left(t\right)=\sin\omega t\\ \sigma\left(t\right)=\sum_{n=0:\text{odd}}^\infty\sigma_n\sin\left(\omega t+\delta_n\right)\end{cases}$$由此式可定义LAOS实验结果的表征参数I_{n/1}\equiv\frac{\sigma_n}{\sigma_1},以及\mathit{\Phi}_n=\delta_n-n\delta_1,来表征非线性粘弹性的程度(即\sigma\left(t\right)偏离正弦函数的程度。此外,我们还会以\sigma\left(t\right)\gamma\left(t\right)作图(参数方程作图),得到Lissajous曲线。然后用曲线的形状特征来表征非线性粘弹性的程度(即曲线偏离椭圆的程度)。描述曲线形状特征,往往就是各种各样的切线斜率(可自行上网搜索复习参数方程求导)。在LAOS中常用:$$G_\text{M}=\left.\frac{d\sigma}{d\gamma}\right|_{\gamma=0}$$$$G_\text{L}=\frac{\sigma{\gamma_0}}{\gamma_0}$$$$G_\text{k}=\left.\frac{d\sigma}{d\gamma}\right|_{\sigma=0}$$

MATLAB编程步骤

在详细介绍MATLAB代码之前,先总结一下我们要完成什么任务,跟从哪些步骤。我们的任务就是要从应力和应变的信号计算出上述的这么多参数。实验时,我们会规定应变的幅度\gamma_0和频率\omega,但是,由于仪器误差,实际施加在样品上的应变幅度和频率,未必完全等于的命令值。尽管这一误差非常小,但从做法上我们还是要以实际应变信号为准。所以,我们首先要对实际应变信号进行判断,获得实际的应变幅度和频率。如何判断一个未知正弦信号的振幅、频率和相位的方法已经在上一篇文章中介绍了。

然后,就要处理应力的信号。为了获得\sigma_n\delta_n同样需要对\sigma\left(t\right)进行fft处理,方法跟上一篇文章介绍的类似,区别只在于我们除了要获得基频的幅值和相位之外,还要获得高次谐波的幅值和相位。理论上来说n=\infty,但是实际上我们根据需要取有限的n个高次谐波,就可以比较准确地重构出\sigma\left(t\right)的波形了。提醒一下,定义式里应变是一个零相位的标准正弦函数,但实际重构的应变波形可能会有非零的相位,这时,应力的各个谐波的相位要都减去应变的相位,才是我们要求的\delta_n

Lissajous曲线的各种导数,要同时在\gamma\left(t\right)\sigma\left(t\right)都重构了之后,再进行求导计算得到。总而言之,要完成以下三个步骤:

  1. 重构应变\gamma\left(t\right),获得\gamma_0\omega和相位
  2. 重构\sigma\left(t\right),获得\sigma_n\delta_n,计算I_{n/1}
  3. 用重构的\sigma\left(t\right)\gamma\left(t\right)作Lissajous曲线,以及计算G_\text{M}G_\text{L}G_\text{k}

重构\gamma\left(t\right)\sigma\left(t\right)

MATLAB帮助文件里fft命令的条目有一个范例,应该能解决大部分问题了。其中在coherent sampling(见上篇文章)处,范例用next power of 2,其实没有必要。下面的代码,我就不多作解释了。


clear all
clc

% the strain signal
fs=1000; % sampling frequency
t=0:1/fs:30.2; % the time series of the strain signal
t=t(:); % convert to colume vector

w=5; % (rad/s) the angular frequency of the strain signal
f=w/(2*pi); % (Hz)

x=250sin(2pift+1)+25randn(size(t)); % strain signal with noise
y=1.001+8.1532.
sin(2pif.t+2.5936)+2.4952.sin(6pif.t+0.5199)+...
0.8856.
sin(10pif.t+4.3353)+0.2932.sin(14pif.t+1.8641)+...
0.0992.
sin(18pif.t-0.6417)+0.0282sin(22pif.+3.3818)+...
0.2
randn(size(t)); % stress signal with noise

[AX,H1,H2]=plotyy(t,x,t,y,'plot'); % Figure 1
set(AX(1),'XLim',[0 8])
set(AX(2),'XLim',[0 8]) % set the time axes

Figure 1 Parts of the strain and the stress signals

Figure 1 Parts of the strain and the stress signals

两个信号都加入了噪音,而且应力信号加入了直流分量,更接近真实的情况。另一个真实的情况是,应力响应的波形往往需要经过几个周期之后才达到稳定的形状。我们当然是取稳定之后的波形来进行Fourier变换,因此就要截取后几个周期。


N=length(x);
N_per_cycle = fs/f; % average points per cycle
N_cycles = floor(N / N_per_cycle); % maximum number of complete cycles


N_cut=round(15*N_per_cycle); % use the last 15 cycles
t=t(length(t)-N_cut:end);
x=x(length(x)-N_cut:end);
y=y(length(y)-N_cut:end);
% crop the last N_cut data.

接下来,先对应变信号进行重构,方法跟上篇文章一样。


Xraw=2fft(x)/N_cut;
Xraw=Xraw(1:ceil(N_cut/2));
freq=0:fs/N_cut:fs/2-fs/N_cut;
A = abs(Xraw(round(N_cut
f/fs)+1))
delta = wrapTo2Pi(angle(Xraw(round(N_cutf/fs)+1))+0.5pi)
frq = freq(round(N_cutf/fs)+1)
x_rec=A.
sin(2pifrq.*t+delta);
plot(t,x,'b-',t,x_rec,'-r')

Figure 2 Reconstruct the strain signal

Figure 2 Reconstruct the strain signal

然后轮到应力的波形。先看看它的Fourier变换结果:


Yraw=2*fft(y)/N_cut;
Yraw=Yraw(1:ceil(N_cut/2));


plot(freq, abs(Yraw)) % Figure 3

Figure 3 Amplitude spectrum of the stress signal

Figure 3 Amplitude spectrum of the stress signal

放大最左边的部分,可以看到好多高次谐波。为什么右边有这么长的多余部分?回忆上篇文章。Fourier变换后的频率范围等于fs/2。我们fs达到1000 Hz,而信号主频只有约0.8 Hz,几个高次谐波都集中在最左侧了。有时间的话,最好多点尝试不同信号的FFT,熟悉这一转换是认识大自然的一个新的思想高度。因为大自然很多自然过程就是在做Fourier变换。例如X线穿过晶体发生衍射,光就被Fourier变换了,形成的二维斑点图可通过进行二维的反Fourier变换,还原成样品中的晶格排布。所以我们总是说,X射线的图谱是“倒易空间”(Fourire变换前后坐标变倒数了)。

Figure 3还可看到,在频率为0处有一个峰。这个峰是原波形的直流分量(见文章开头构建信号的式子),我们需要知道各个谐波在信号序列中的具体位置。上篇文章已经解决了正弦信号的情况,对于高次谐波(都是奇数倍频),只要相应地乘以3、5、7即可。但是,根据定义式,应力的Fourier展开理论上是无穷多项的,而实际上也需要取尽量多项以确保最准确地重构原波形。到底Fourier变换之后,最多允许看到多少个高次谐波呢?这就相当于要找使nf<fs/2的最大奇数n。不过,如果信号的取样频率fs相当大,最大谐波次数n也会非常大,我们实在没必要取这么大的n(如Figure 3的情况右边大部分是多余的。因此我们又人为规定最大谐波次数不超过25个,就是说我们假定25个高次项对于再怎么非线性的信号都肯定足够了。以下是相关的代码:


max_nr_harm=floor(fs/(2*f)); % Maximum number of harmonics
if max_nr_harm/2==floor(max_nr_harm/2)
max_nr_harm=max_nr_harm-1;
end
% maxmum odd number of harmonic

max_nr_harm=max_nr_harm-2; % fall back one more odd harmonic for safety

if max_nr_harm>25
max_nr_harm=25;
end

接下来就是要确定这些谐波的幅值和辐角:


% create the variables for harmonic amplitudes and phase angles
I_n=zeros((max_nr_harm+1)/2,1);
delta_n=zeros((max_nr_harm+1)/2,1);


for i=1:(max_nr_harm+1)/2
I_n(i)=abs(Yraw(round((2i-1)N_cutf/fs)+1));
delta_n(i)=angle(Yraw(round((2
i-1)N_cutf/fs)+1))+0.5pi;
end
DC=abs(Yraw(1))
cos(angle(Yraw(1)))/2;

其中,直流分量的大小计算方法为什么是这个,我不太记得了。这时,你已经可以通过I_n(i)和delta_n(i)来计算相应的非线性粘弹性参数I_{n/1}\equiv\frac{\sigma_n}{\sigma_1}\mathit{\Phi}_n=\delta_n-n\delta_1了。再次提醒,应力波形的各谐流相位角要减去应变波形的相位角,才是定义式中的相位角。我们也可以检验一下重构的应力波形:


y_rec=zeros(length(y),1); % create the variables for the reconstructed signals
for i=1:2:max_nr_harm
y_rec=y_rec+I_n((i+1)/2).sin(i2pif.*t+delta_n((i+1)/2));
end
y_rec=y_rec+DC;
plot(t,y,'-b',t,y_rec,'-r') % Figure 4

Figure 4 Reconstructed stress signal

Figure 4 Reconstructed stress signal

作成Lissajous曲线来看:


plot(x,y,'b-',x_rec,y_rec,'r-')

Figure 5 Reconstructed Lissajous curve

Figure 5 Reconstructed Lissajous curve

可见,通过Fourier变换重构波形,可以很好地把噪音排除掉。Lissajous曲线的各个形状参数,需要在曲线上找各种交点求切线(见定义式)。如果在原信号的基础上直接做,可想而知误差会有多大。这就是为什么我们明明已经有信号不直接用要先重构一番的原因。具体如何在Lissajous曲线上找点、求切线的方法,在下一篇文章中介绍。

References

  1. K. Hyun, M. Wilhelm, C.O. Klein, K.S. Cho, J.G. Nam, K.H. Ahn, S.J. Lee, R.H. Ewoldt, and G.H. McKinley, "A review of nonlinear oscillatory shear tests: Analysis and application of large amplitude oscillatory shear (LAOS)", Progress in Polymer Science, vol. 36, pp. 1697-1753, 2011. http://dx.doi.org/10.1016/j.progpolymsci.2011.02.002

Fourier变换(一)

[mathjax]

为了带师弟做实验,简要总结一下在MATLAB里进行Fourier变换的一些代码,以供大家参考。

Discrete Fourier transform (DFT): The sequence of N complex numbers x0, …, xN-1 is transformed into an N-periodic sequence of complex numbers according to the DFT formula

X_k=\sum_{n=0}^{N-1}x_n e^{-i2\pi kn/N}\quad k=0,1,\cdots,N-1.

一般把变换前的信号xn当成时域信号,变换后的Xk当成频域信号。可见,时域是N个单元的信号,变换后频域还是N个单元的信号。什么周期啊频率啊这些信息是暂时没有的。例如xn为以下这个序列(Fig. 1),横坐标就是序号n。这段序列,可以是一段10秒钟的的信号,也可以是一段100秒钟的信号。当然,实际情况下一段信号是多少秒是事先知道的,出来的信号一共有多少个单元值(N)则取决于我们隔多长时间取一次样。如果一段10秒钟的信号,我们每秒取一次样,这个序列就有10个单元(N = 10)。反之,Fig. 1的这个序列N = 93,假如这个信号是每隔0.1秒取一次样得到的,这个信号的总时长就是9.2秒;而假如这个信号是每隔10秒取一次样得到的,这个信号总时长就是920秒了。每隔多长时间取一次样,可以用取样频率fs来表示。fs = 10 Hz就是每隔0.1 s取一次样。所以,为了给一对离散Fourier变换xnXk赋以周期或频率的信息,必须给时域信号规定一个fs。一旦fs规定了,周期和频率也就规定了。还拿Fig. 1的序列为例,设该序列的取样频率fs = 10 Hz,则该序列每个单元隔了0.1 s。我们可以数一下这个序列一个周期有多少个单元——20个,那么该序列的周期T = 2 s,频率f = 0.5 Hz。

Figure 1

Figure 1

事实上Fig. 1的序列是一个有一定时延的正弦序列(即不是从零开始),我们已经根据规定的fs知道了它的周期和频率,不仿就把它改作在时间轴上(Fig. 2)。

Figure 2

Figure 2

可以看到,该正弦信号的零位在第17和第18个单元之间,即在1.6 s到1.7 s处,以1.6 s计算,该信号可写成x\left(t\right)=A\sin(\left[2\pi f\left(t-1.6\right)\right]=A\sin(\left(2\pi ft+\delta\right),则相位角\delta=-1.6\pi,换成0到2π之间就是\delta=0.4\pi=1.2566。若取时移是1.7 s得\delta = 0.3\pi = 0.9425,所以δ应该在0.9425到1.2566之间。从图中还能读出振幅A = 2,则信号x\left(t\right)=2\sin\left(\pi t+0.9425\sim1.2566\right)。用正弦函数去拟合Fig. 2的序列发现其实最精确的函数描述应该是x\left(t\right)=2\sin\left(\pi t+1\right),见Fig. 3。

Figure 3

Figure 3

现在我们试着在MATLAB通过Fourier变换来精确地得到xn的幅值和相位,重构一个xn所对应的连续信号。首先构造上述的时域信号:


t = 0:0.1:9.2; % fs = 10 Hz
t = t(:); % convert into column vector
f = 0.5; % f = 0.5 Hz
x = 2 * sin(2 * pi * fi * t + 1)

前面说过对一组N个单元的序列进行Fourier变换之后得到的Xk也是一个N个复数单元的序列。我们看看它长什么样(分开模序列和辐角序列):


Xraw = fft(x);
stem(abs(Xraw),'o'); % Figure 4
stem(angle(Xraw),'o'); % Figure 5

Figure 4

Figure 4

Figure 5

Figure 5

我们从Fig. 4可以看到结果跟我们想像的差很远。主要有以下四个问题:

  1. 频域谱分成了对称的两半。要取左边一半。但总单元个数为奇数(N = 93),到底是取46个还是47个?
  2. 若只看一半,频谱出现了单峰是没错(因为我们的信号只有一个频率),但它不是只在一个频率上,而是有一个很宽的分布,似乎我的输入信号的频率是“不纯”的——这并不符合事实
  3. 幅值不对,信号幅值是2,但频谱的峰高快到80了
  4. 横轴如何对应到真实的频率轴?

我们一个一个问题地解决。第一个问题,按照fft算法的规律,若N为偶数,则左右分成各N/2的两半即可;若N为奇数,则处于中央的那个单元是左右两半谱共用的。所以,无论取左半还是右半,都取(N+1)/2+1个。综合奇数和偶数的情况,对任意正整数N的取法就是对N/2向上取整。


N = length(x);
Xraw = Xraw(1:ceil(N/2));
stem(abs(Xraw));

第二个问题,之所以频谱的峰有一个很宽的分布,是由于谱泄漏(spectral leakage)现象,简单地说就是我们用来做Fourier变换的信号序列长度并不是恰好整数个周期。只有截取整数个周期来做Fourier变换,才能避免谱泄漏。可是对于一个未知频率的信号,我们本来就是要通过Fourier变换来知道它的频率,又怎么能在此之前就得知信号的周期呢?这种情况下,只能采用另一种方法,那就是把信号的首尾进行某种衰减处理,而这一处理在数学上又要保证不严重影响Fourier变换的结果,称为加窗操作(windowing)。详细的原理不在此详述。作为示范,我们可以利用我们对时域信号的已知信息来解决。已知信号周期T = 2 s,取样频率fs = 10 Hz,到底截取多少个点算一个周期呢?前面计算过了,20个点。干脆我们就只取一个周期得了,试试看:


x_cut = x(1:20); % N = 20
Xraw = fft(x_cut);
Xraw = Xraw(1:10); % N=20, so ceil(N/2) = 10
stem(abs(Xraw)); % Figure 6

Figure 6

Figure 6

这次,频峰值终于只出现在一个频率上了。而由于用来做Fourier变换的信号N减小到了20个,所以频域信号也就只有20个单元,取左半边就是十个单元。但我们看到,峰值也变了,原来近80,现在是在20。这说明,这个不正确的峰值,是跟N值有关的。这就涉及到第三个问题:峰值怎么校正回来。其实,我们做离散Fourier变换,需要进行归一化。但是怎么归一化,要看离散Fourier变换和反Fourier变换的具体规定,不同的书规定不一样。MATLAB里fft命令是需要用N来归一化的。但如果出来双面的频谱,各边就要用N/2来归一化。所以进行了归一化的的Fourier变换操作应为:


Xraw = 2 * fft(x_cut) / 20;
Xraw = Xraw(1:10);
stem(abs(Xraw));

出来的结果,峰值就会准确落在2(图略)。现在剩下最后一个问题,那就是横轴怎样换成真正的频率?这就要先了解Shannon-Nyquist定理。在此我只把结论说出来:频域信号单元的间格频率是fs/N。因此整个左半边谱的频率范围就是0 Hz ~ fs/2 – fs/N。为了解决奇数偶数问题(即以防fs/2不一定是fs/N的整数倍),最终我们就如下构造相应的频率轴:


freq = 0:fs/20:(ceil(N/2)-1)*fs/N;
freq = freq(:);
stem(freq,abs(Xraw)); % Figure 7

Figure 7

Figure 7

其实,要取整数个周期,不一定要只取一个周期。原时域信号至少有4个周期(见Fig. 1),所以我们可以取满整4个周期来做Fourier变换:


x_cut = x(1:80); % 4 cycles = 80 points
Xraw = 2 * fft(x_cut) / 80; % N = 80
Xraw = Xraw (1:40);
freq = 0:fs/80:fs/2-1/80;
stem(freq,abs(Xraw)); % Figure 8

Figure 8

Figure 8

可见,当我们多取几个周期时,Fourier变换的结果频率“分辨率”更高。因此为了获得尽可能完整地信息,要在保持取整个周期的同时,尽量取最多个周期来进行Fourier变换。我们把相应的辐角谱也作出来:


stem(freq,angle(Xraw); % Figure 9

Figure 9

Figure 9

我们发现,跟Fig. 5相比,Fig. 9的辐角谱显得非常杂乱无章。这是由于我们很好的消除了谱泄漏。规律:谱泄漏越严重,辐角谱越有序;谱泄漏避免得越彻底,辐角谱就越杂乱无章。虽然杂乱无章,但是模谱的幅值所在相应频率对应的辐角是应该是正确的。我们看看0.5 Hz处的辐角:


wrapTo2Pi(angle(Xraw(5))) % wrap result within 0 to 2Pi

结果不对,这是由于在MATLAB的fft得到的辐角差了π/2,要加上:


wrapTo2Pi(angle(Xraw(5))+0.5*pi)

这次就对了。

问题是,在实际应用中,我们不可能做一个Fourier变换,就作一次图,用肉眼看出峰值落在哪个频率上。必须事先就知道信号频率f在频域信号中的对应序号,直接就给出模的峰值和相应的辐角。所以,现在的问题是,如何不通过作图就知道,对于一个频率为f,采样频率为fsN个单元的信号xn,进行了上述的Fourier变换后得到的Xk,第几个单元呢对应着f?这很简单,Fourier变换后的频率间隔是fs/N,因此,第Nf/fs+1个单元就是模的峰值所在频率。本例子中既可验证。

但是,假如信号的频率f在Fourier变换之后的频率轴上的位置,是恰好在频域信号两个单元之间呢?例如,我的信号频率是f=0.51 Hz,fsN值不变,按照上述算法,要取第5.08个单元,当然不存在这个非整数单元。这就是说,我们要对Nf/fs+1这个公式加一个四舍五入的取整。注意,由于信号频率不同了,“整数个周期”的截取可能会略有不同。以下是具体的运算过程:


f = 0.51
x = 2 * sin(2 * pi * f * t + 1);
N = length(x);
N_per_cycle = fs/f; % average points per cycle
N_cycles = floor(N / N_per_cycle); % maximum number of complete cycles
N_cut = round(N_cycles * N_per_cycle); % number of points for FFT
x_cut = x(1:N_cut);
Xraw = 2 * fft(x_cut) / N_cut;
Xraw = Xraw(1:ceil(N_cut/2));
freq = 0:fs/N_cut:fs/2-fs/N_cut;
abs(Xraw(round(N_cut*f/fs)+1))
wrapTo2Pi(angle(Xraw(round(N_cut*f/fs)+1))+0.5*pi)
freq(round(N_cut*f/fs)+1)

结果离真实值就稍有偏差。这时,如果我们试试把fs提高到1000 Hz。


clear all;
fs = 1000;
t = 0:1/fs:9.2;
t=t(:);
f = 0.51
x = 2 * sin(2 * pi * f * t + 1);
N = length(x);
N_per_cycle = fs/f; % average points per cycle
N_cycles = floor(N / N_per_cycle); % maximum number of complete cycles
N_cut = round(N_cycles * N_per_cycle); % number of points for FFT
x_cut = x(1:N_cut);
Xraw = 2 * fft(x_cut) / N_cut;
Xraw = Xraw(1:ceil(N_cut/2));
freq = 0:fs/N_cut:fs/2-fs/N_cut;
A = abs(Xraw(round(N_cut*f/fs)+1))
delta = wrapTo2Pi(angle(Xraw(round(N_cut*f/fs)+1))+0.5*pi)
frq = freq(round(N_cut*f/fs)+1)

结果就比之前更接近真实值了。这说明,时域信号的采样频率越高,越有利于精确地重构。现在可以看一下重构的效果:


x_rec = A * sin(2*pi*frq*t+delta);
plot(t,x,'bo',t,x_rec,'r-') % Figure 10

Figure 10

Figure 10

下一篇文章会介绍当原时域信号的频率“不纯”的时候如何通过Fourier变换进行重构。