基4fft算法的蝶形图_原地且自动整序的FFT算法

f40b062a57ac2b6ce9d1652f311c63c4.png

传统的计算快速傅里叶变换的Cooley-Tukey算法效率极高,因其主要由蝶形运算构成,所以代码形式也非常简单,只是需要将输入或者输出按照位反转的方式重新排序。

这个重新排序的步骤并不是必须的。Clive Temperton于1991年在Self-Sorting In-Place Fast Fourier Transforms一文中给出了适用于混合基数的原地FFT算法,不需要对输入或输出重新排序。本文将介绍这种算法的原理并给出基数2(Radix-2)情况下的具体构造和C++实现。作为FFT算法研究成果的集大成者,FFTW已应用了这种算法。

FFT7071专栏​fourier.v.ariant.cn
461f5233eeac7f06a674dfbb78bd56e8.png

预备:内存中的矩阵转置

设x[t]为一个长度为M*N的向量,t也可表示为Ma+b。以M=5,N=3为例,x[0…14]={101, 102, 103,…, 115},如果将x视作一个5行3列的矩阵,那么a列b行的矩阵元素即是x[Ma+b]:

   a= 0,  1,  2
b=0 101 106 111
b=1 102 107 112
b=2 103 108 113
b=3 104 109 114
b=4 105 110 115

将这个矩阵转置,不难发现转置后的y[0…14]={101, 106, 111, 102,…, 110, 115}

   a= 0,  1,  2,  3,  4
b=0 101 102 103 104 105
b=1 106 107 108 109 110
b=2 111 112 113 114 115

y与x的关系是y[Nb+a]=x[Ma+b]。这个转置变换也可以用一个置换矩阵P表示:y=Px。

展开:DFT变换式

记长度为

的信号为
的离散傅里叶变换,并且设:

展开DFT变换得到以下表达式:

其中

利用

用y代换x并继续展开单位根的幂,其中

上式的求和等价于:

其中大括号内的求和相当于将y中的元素从地址0开始每相邻的N个为一组总共M个长度为N的DFT。如果将y看作M行N列的矩阵,这是对每一列的变换,变换的结果依次乘以

,这时剩下的一个求和相当于对y的每一行单独的DFT。

至此,长度为MN的变换分解为了长度为M和N的两遍短变换。如果上式中不将第一遍DFT的结果乘以

,结果将是M*N的2维DFT。需要注意的是,变换y可以使上式的两遍DFT均在原地进行,如果变换的是x,为保持变换结果的顺序正确,必须以转置的形式写回第一遍短变换的结果。

题外话:将中间结果写到另一处存储区x',并且以x'为输入做第二遍变换,结果写回x,如此往复可以解决变换无法在原地进行的问题,这即是Stockham FFT算法。但是如此一来FFT需要额外的等于x长度的内存,即需要额外O(N)的空间。因为置换群中的元素均可分解为2置换,也就是对换的乘积,置换矩阵也可做如此分解,将转置操作变换成一系列对换,从而可在原地进行,仅需要O(1)额外空间。然而转置只能分解为数量巨大的对换,这种操作的效率不高。这也预示着,充分利用内存中数据的对换,可以保持O(1)的额外空间需求,同时使FFT在原地进行且顺序正确。

展开:DFT变换矩阵和素因子算法

运用上文得出的分解到DFT的矩阵形式:

实际变换的是y(也就是转置的x),第一遍DFT作于相邻的N个元素(每列),将结果逐个乘以一个旋转因子,再变换间隔为N的每组元素(每行),这个过程对应DFT矩阵的一种分解,也就是素因子分解FFT算法的基本构造:

其中W为下标相应长度的DFT矩阵;P为x[Ma+b]转置到y[Nb+a]的置换矩阵;I为M或N阶的单位矩阵;D为M*N阶对角矩阵,第bN+d行对角线上的值为exp(2πibd/(MN))。⊗是矩阵的Kronecker积,定义如下:

例如:

Kronecker积满足结合律:

满足“混合乘积”性质:

为例:

继续分解

运用“混合乘积”的性质将上式拆分为矩阵积:

对于长度为

的DFT矩阵分解,设:

可以得到:

这种分解正是时间抽取(DIT)基数2(Radix-2)的Cooley-Tukey算法,下文中只考虑此种FFT,频率抽取(DIF)在附录中讨论。

已知T对应Cooley-Tukey算法每次迭代在整个输入向量上的所有蝶形运算,上式中的

为2行8列到8行2列的矩阵转置,作用是将x[2b+a]的值变换到x[8a+b],其中
。观察8a+b和2b+a的二进制表示:
2^3 2^2 2^1 2^0
[b] [b] [b] [a] = 2b+a
[a] [b] [b] [b] = 8a+b

可知

的作用是将
的地址t二进制位向右环移1位。
的作用是保持t的最高1位不变,t的余下三位向右环移1位,因此经过所有的
变换:
2^3  2^2  2^1  2^0
[k3] [k2] [k1] [k0]
[k0] [k3] [k2] [k1] - P16
[k0] [k1] [k3] [k2] - P8
[k0] [k1] [k2] [k3] - P4

很明显T之前所有

矩阵的乘积是输入数据翻转对应地址二进制位的置换矩阵。

对换:蝶形运算和对换

设DFT的长度为N,则x[t]中的t在二进制下有N位,定义

为对换
的置换矩阵,
对换二进制位中低位i和高位N-i-1得到。可以用一系列Q的乘积取代P的乘积:

的效果同样完全反转二进制位:
2^3  2^2  2^1  2^0
[k3] [k2] [k1] [k0]
[k0] [k2] [k1] [k3] - Q03
[k0] [k1] [k2] [k3] - Q12

在N=2M或2M+1的情况下:

转置P无法简单地在原地计算,而Q仅包含数量较少的对换,因此可以在原地完成。目前为止以T和Q组成的DFT矩阵W分解仍然表示先重排数据再开始蝶形运算的迭代,如果将T和Q结合起来,就能省略重排数据的操作,但是这要求T和Q可交换。为了证明这一点,首先需要求出Q的表达式。

观察

翻转二进制最高位和最低位的操作:
0000    0000
0001 -> 1000
0010    0010
0011 -> 1010
0100    0100
0101 -> 1100
0110    0110
0111 -> 1110
1000 -> 0001
1001    1001
1010 -> 0011
1011    1011
1100 -> 0101
1101    1101
1110 -> 0111
1111    1111

可以发现

的作用是将前一半数中的奇数
对换。因此:

这里

阶置换矩阵,对换
交换二进制的最高位和最低位得到。

在为二进制数添加前缀的操作下

的作用是不变的:
00000    00000    10000    10000
00001 -> 01000    10001 -> 11000
00010    00010    10010    10010
00011 -> 01010    10011 -> 11010
00100    00100    10100    10100
00101 -> 01100    10101 -> 11100
00110    00110    10110    10110
00111 -> 01110    10111 -> 11110
01000 -> 00001    11000 -> 10001
01001    01001    11001    11001
01010 -> 00011    11010 -> 10011
01011    01011    11011    11011
01100 -> 00101    11100 -> 10101
01101    01101    11101    11101
01110 -> 00111    11110 -> 10111
01111    01111    11111    11111

因此对于N位二进制数:

为二进制数添加后缀的操作使

作用于全部后缀,可得出:

,现在可以将
展开:

因此对于所有的

可交换。这使
可以写为:

已知一次蝶形运算的迭代

的作用是:将两个长度为
的DFT结果作为奇偶两部分,合并为长度为
的DFT结果,这样的奇偶对共有
个。

中单个蝶形运算的偶、奇两个输入分别是
会将
对换。取
,则
将与
对换。在
的情况下,
分别是另一蝶形运算的偶、奇输入。

可见

中,输入数据地址相差
的两个蝶形运算是成对的,第一个蝶形运算的奇数项输入与第二个蝶形运算的偶数项输入对换。如下图所示:

c9cafb594e957431612d4864cfe4a5a4.png

下图中画出来N=16的基数2变换,输入和输出的顺序均是正确的;图中用颜色标出了某些蝶形运算,使蝶形运算的配对更清晰。注意其中成对蝶形运算的4个输入输出均在原地,并且与传统Cooley-Tukey算法相比没有计算量的差别。

19424ea3596c0a578fc8bd403edda118.png

下图是作为对比的传统Cooley-Tukey算法。

e2b75f96cb0f6acf57005e72dd23f352.png

附录:本文算法的C++实现

/* copyright 2020, github.com/zhxxch, all rights reserved. */
#include <complex>
#include <iterator>
#include <cmath>
#include <cassert>
#include <cstddef>
template<typename iter_t>
#if __cplusplus > 201703L
requires std::random_access_iterator<iter_t>
#endifinline void fft_in_place(iter_t arr_begin,iter_t arr_end, const bool is_inverse) {using cplx_t = typename std::iterator_traits<iter_t>::value_type;using real_t = typename cplx_t::value_type;const size_t length= std::distance(arr_begin, arr_end);assert("requires length = pow(2,n)"&& (length & (length - 1)) == 0);constexpr real_t pi= 3.141592653589793238462643383;size_t sub_ft_size = 1;size_t num_sub_ft = length / sub_ft_size;size_t num_sub_ft_pair = num_sub_ft / 2;for(; sub_ft_size < num_sub_ft_pair;sub_ft_size *= 2, num_sub_ft /= 2,num_sub_ft_pair /= 2) {for(size_t coupled_group_pos = 0;coupled_group_pos < length;coupled_group_pos += 2 * num_sub_ft_pair) {for(size_t sub_ft_pos = coupled_group_pos;sub_ft_pos< coupled_group_pos + num_sub_ft_pair;sub_ft_pos += 2 * sub_ft_size) {for(size_t i = sub_ft_pos, nth_pow = 0;i < sub_ft_pos + sub_ft_size;i++, nth_pow += num_sub_ft_pair) {const cplx_t W = exp((is_inverse ? 1. : -1.)* cplx_t(0, 2 * nth_pow * pi)/ (real_t)length);const cplx_t parit00= arr_begin[i];const cplx_t parit01= arr_begin[num_sub_ft_pair+ i]* W;const cplx_t parit10= arr_begin[i + sub_ft_size];const cplx_t parit11= arr_begin[num_sub_ft_pair + i+ sub_ft_size]* W;arr_begin[i] = parit00 + parit01;arr_begin[i + sub_ft_size]= parit00 - parit01;arr_begin[num_sub_ft_pair + i]= parit10 + parit11;arr_begin[num_sub_ft_pair + i+ sub_ft_size]= parit10 - parit11;}}}}for(; sub_ft_size < length; sub_ft_size *= 2,num_sub_ft /= 2, num_sub_ft_pair /= 2) {for(size_t sub_ft_pos = 0; sub_ft_pos < length;sub_ft_pos += 2 * sub_ft_size) {for(size_t i = sub_ft_pos, nth_pow = 0;i < sub_ft_pos + sub_ft_size;i++, nth_pow += num_sub_ft_pair) {const cplx_t parit1= arr_begin[i + sub_ft_size]* exp((is_inverse ? 1. : -1.)* cplx_t(0, 2 * nth_pow * pi)/ (real_t)length);const cplx_t parit0 = arr_begin[i];arr_begin[i] = parit0 + parit1;arr_begin[i + sub_ft_size]= parit0 - parit1;}}}
}

使用方法-FFT

fft_in_place(arr.begin(), arr.end(), 0);

使用方法-IFFT

fft_in_place(arr.begin(), arr.end(), 1);

arr的长度必须是2的幂。

附录:时间抽取与频率抽取

为例,频率抽取的FFT算法中:

换为使用Q表达的形式则为:

因此Q作用于蝶形运算的输出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537198.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式BootLoader技术内幕(二)

三、Boot Loader 的主要任务与典型结构框架 在继续本节的讨论之前&#xff0c;首先我们做一个假定&#xff0c;那就是&#xff1a;假定内核映像与根文件系统映像都被加载到 RAM 中运行。之所以提出这样一个假设前提是因为&#xff0c;在嵌入式系统中内核映像与根文件系统映像也…

MongoDB数据库的迁移

最近公司开始要换服务器啦&#xff0c;MongoDB上面的数据又得迁移&#xff0c;还是记录一下比较好。 1&#xff09;、将MongoDB的压缩包解压至相对应的路径(压缩文件在本地服务器的地址192.168.0.22的/opt/zip文件下面) 2&#xff09;、配置好mongodb.conf文件&#xff0c;配…

excel vba 如何将日期周几转换成文字_这5个超实用的Excel技巧,让你的办公效率更高...

导读&#xff1a;对于办公职员来说&#xff0c;Excel是几乎每天都会接触的办公软件。在Excel中&#xff0c;有非常多的小技巧&#xff0c;学习这些小技巧需要不断的积累和应用&#xff0c;今天指北针就来给大家分享5个超实用的Excel技巧&#xff0c;让办公变得更加有效率。文/芒…

VMware创建Linux及局域网内独立访问IP和访问外网IP的配置

好早之前有一篇是配置远程连接Linux和部署Tomcat的文章&#xff0c;但是并没有讲解如何配置IP的相关知识。最近公司在搞集群配置&#xff0c;我就先拿电脑上的VMware上的Linux做个测试&#xff0c;分享和总结一下经验吧&#xff0c;也算是为了补齐之前的那个空白&#xff01; …

每位设计师都应该拥有的50个CSS代码片段

每位设计师都应该拥有的50个CSS代码片段

C#浅拷贝与深拷贝区别

也许会有人这样解释C# 中浅拷贝与深拷贝区别&#xff1a; 浅拷贝是对引用类型拷贝地址&#xff0c;对值类型直接进行拷贝。 不能说它完全错误&#xff0c;但至少还不够严谨。比如&#xff1a;string 类型咋说&#xff1f; 其实&#xff0c;我们可以通过实践来寻找答案。 首先&a…

内网安装nginx+keepalived环境配置及简单使用

分享一下这次艰难的配置过程&#xff0c;衔接上一篇的配置内网独立IP虚拟机。 先吐槽一波&#xff0c;由于公司网络属于内网&#xff0c;与外网互不相通&#xff0c;所以在安装nginx的时候可能会去外网找相对应rpm文件&#xff0c;而且也有许多的版本不兼容问题&#xff0c;好…

cad连续标注数字123怎么弄_实例讲解CAD模型与布局中的各种比例

好课推荐&#xff1a;零基础CAD&#xff1a;点我CAD室内&#xff1a;点我 周站长CAD&#xff1a;点我CAD机械&#xff1a;点我 Bim教程&#xff1a;点我CAD建筑&#xff1a;点我CAD三维&#xff1a;点我全屋定制&#xff1a;点我 ps教程&#xff1a;点我苹果版CAD:点我 3dmax教…

SpringMvc异步请求的使用及部分原理

最近隔壁项目组的项目又出问题了&#xff0c;一直被用户投诉太卡了&#xff0c;页面白屏的那种&#xff0c;打开源代码一看&#xff0c;全是非异步请求&#xff0c;类似于以下写法&#xff1a; ResponseBodyRequestMapping(value "/getTest")public String getTest(…

Microsoft BizTalk ESB Toolkit 2.0

[>>> 更多<BizTalk开发系列>文章 ] 微软于6月8号发布了BizTalk Server 2009企业集成平台的最后一个功能组件:ESB Toolkit 2.0 (原名:ESB Guidance 2.0)&#xff0c;ESB ToolKit 2.0一个是工具和代码集扩展了BizTalk Server 2009对于松耦合和动态消息架构的支持…

python解释器环境中用于表示上一次运算结果的特殊变量_判断正误 PUSH CL_学小易找答案...

【单选题】将数学关系式2 【填空题】请用4位十六进制写出每条指令结束后AX的值。 MOV AX, 0 DEC AX ADD AX, 7FFFH ADC AX, 1 NEG AX OR AX, 3FDFH AND AX, 0EBEDH XCHG AH, AL SAL AX, 1 RCL AX, 1 【判断题】判断正误 MOV DX, 09H 【判断题】判断正误 MOV [1200H], [SI] 【单…

Java线程的使用及共享协作

创建线程的三种方式 1、继承Thread&#xff1b; static class MyThread extends Thread{Overridepublic void run() {//do something...} } public static void main(String[] args) throws InterruptedException {MyThread thread new MyThread ();thread.start(); } 2、实…

WCF学习笔记(三):开启net.tcp端口

正在做一个使用tcp协议的WCF示例&#xff0c;遇到很多问题。首当其冲的问题就是——如何为WCF打开tcp端口。。。 具体步骤如下&#xff1a; 1、在IIS中为WCF安装支持TCP协议的组件&#xff1a; 2、在防火墙的入栈规则中开启808端口&#xff1b; 3、在servies.msc中打开两个服务…

孪生神经网络_轩辕实验室:数字孪生:基于机器学习的汽车数字孪生模型

本文来源&#xff1a;A. Rassolkin, T. Vaimann, A. Kallaste, and V. Kuts, “Digital twin for propulsion drive of autonomous electric vehicle,” in 2019 IEEE 60th International Scientific Conference on Power and Electrical Engineering of Riga Technical Univer…

Java线程Fork/Join思想及实现

最近在看线程这一块的东西&#xff0c;所以之前的那篇文章就是用来记录的&#xff0c;但看起来好简单的样子&#xff0c;哈哈哈&#xff01; 这两天看的是Fork/Join 分而治之的思想&#xff0c;Doug Lea大师的JUC还是挺强的&#xff0c;学并发编程应该没有人不知道这个大佬吧&…

Sgen.exe: Speed up XmlSerializer's Startup Performance [.NET 2.0, XML Serialization]

Sgen.exe: Speed up XmlSerializers Startup Performance [.NET 2.0, XML Serialization] Written by Allen Lee 1. Why Sgen.exe? 在《Serialize Your Deck with Positron [XML Serialization, XSD, C#]》一文中&#xff0c;我们领略到 XML Serialization 是如何简化我们的 X…

Java线程并发常用工具类使用

这次整理了一些比较常用的线程工具类啦。 CountDownLatch&#xff1a;在一组线程执行完后&#xff0c;才能开始执行调用等待的线程。上片文章提到过junit的测试尽量不要测试线程&#xff0c;如果硬是要可以使用CountDownLatch进行测试 CyclicBarrier&#xff1a;在一组线程中…

三维图形几何变换算法实验_计算机视觉方向简介 | 深度学习视觉三维重建

点击上方“计算机视觉life”&#xff0c;选择“星标”快速获得最新干货作者&#xff1a; Moonsmilehttps://zhuanlan.zhihu.com/p/79628068本文已由作者授权&#xff0c;未经允许&#xff0c;不得二次转载三维重建意义三维重建作为环境感知的关键技术之一&#xff0c;可用于自动…

读《高效程序员的45个习惯——敏捷开发修炼之道》

本书主要用平易的语言讲述了45个有助于提高程序员自身敏捷的习惯&#xff0c;个人感觉这种老外写的书翻译成中文就少了很多意思。 主要的45个习惯是&#xff1a; 做事欲速则不达对事不对人排除万难跟踪变化对团队投资懂得丢弃打破沙锅问到底把握开发节奏让客户做决定让设计指导…

Java线程CAS原子操作

这次分享一些关于原子操作(CAS)的东西. 定义 CAS(Compare And Swap)是CPU的一个指令级别的操作&#xff0c;叫原子操作&#xff0c;原子操作是不可分割的&#xff0c;跟事务差不多&#xff0c;要么全部执行完成&#xff0c;要么不执行&#xff1b; 像这种操作有点类似阻塞锁…