Python计算傅里叶变换

news/2024/9/25 14:52:56/文章来源:https://www.cnblogs.com/dechinphy/p/18427010/fft

技术背景

傅里叶变换在几乎所有计算相关领域都有可能被使用到,例如通信领域的滤波、材料领域的晶格倒易空间计算还有分子动力学中的倒易力场能量项等等。最简单的例子来说,计算周期性盒子的电势能\(k\sum_i\frac{q_i}{r_i}\)本身就是一个类似于调和级数的形式,很难求得精确解。但是在Edward求和方法中使用傅里叶变换,可以做到在倒易空间进行能量计算,可以逼近精确解。本文主要介绍傅里叶变换的原理及相应的Python代码实现。

DFT原理

DFT计算的本质是一个矩阵运算:

\[y_k=\sum_{n=0}^{N-1}x_ne^{-j\frac{2\pi nk}{N}},0\leq k\leq N-1 \]

\[x_n=\frac{1}{N}\sum_{k=0}^{N-1}y_ke^{j\frac{2\pi nk}{N}},0\leq n\leq N-1 \]

如果写成一个矩阵的形式,那就是:

\[\left[\begin{matrix} y_1\\ y_2\\ y_3\\ ...\\ y_{N-1} \end{matrix}\right]=\left[\begin{matrix} 1&&1&&1&&...&&1\\ 1&&e^{-j\frac{2\pi}{N}\cdot1}&&e^{-j\frac{2\pi}{N}\cdot2}&&...&&e^{-j\frac{2\pi}{N}\cdot(N-1)}\\ 1&&e^{-j\frac{2\pi}{N}\cdot2}&&e^{-j\frac{2\pi}{N}\cdot4}&&...&&e^{-j\frac{2\pi}{N}\cdot2(N-1)}\\ ...&&...&&...&&...&&...\\ 1&&e^{-j\frac{2\pi}{N}\cdot(N-1)}&&e^{-j\frac{2\pi}{N}\cdot2(N-1)}&&...&&e^{-j\frac{2\pi}{N}\cdot(N-1)(N-1)} \end{matrix}\right]\left[ \begin{matrix} x_1\\ x_2\\ x_3\\ ...\\ x_{N-1} \end{matrix}\right] \]

类似的,逆傅里叶变换的矩阵形式为:

\[\left[ \begin{matrix} x_1\\ x_2\\ x_3\\ ...\\ x_{N-1} \end{matrix}\right] =\left[\begin{matrix} 1&&1&&1&&...&&1\\ 1&&e^{j\frac{2\pi}{N}\cdot1}&&e^{j\frac{2\pi}{N}\cdot2}&&...&&e^{j\frac{2\pi}{N}\cdot(N-1)}\\ 1&&e^{j\frac{2\pi}{N}\cdot2}&&e^{j\frac{2\pi}{N}\cdot4}&&...&&e^{j\frac{2\pi}{N}\cdot2(N-1)}\\ ...&&...&&...&&...&&...\\ 1&&e^{j\frac{2\pi}{N}\cdot(N-1)}&&e^{j\frac{2\pi}{N}\cdot2(N-1)}&&...&&e^{j\frac{2\pi}{N}\cdot(N-1)(N-1)} \end{matrix}\right] \left[\begin{matrix} y_1\\ y_2\\ y_3\\ ...\\ y_{N-1} \end{matrix}\right] \]

如果记参数\(W_{N,n,k}=e^{-j\frac{2\pi}{N}nk}\),则其共轭\(W_{N,n,k}^*=e^{j\frac{2\pi}{N}nk}\)是逆傅里叶变换的参数。而且根据复变函数的性质,该参数具有周期性:\(W_{N,n+N,k}=W_{N,n,k+N}=W_{N,n,k}\),共轭参数同理。最后还有一个非常重要的性质:\(W_{N/m,n/m,k}=W_{N/m,n,k/m}=W_{N,n,k}\),根据这个特性,可以将大规模的运算变成小范围的计算。在不考虑这些参数特性的情况下,我们可以使用Python做一个初步的DFT简单实现。

初步Python实现

这里没有做任何的优化,仅仅是一个示例:

import numpy as npdef dft(x):y = np.zeros_like(x, dtype=np.complex64)N = x.shape[0]for k in range(N):y[k] = np.sum(x * np.exp(-1j*2*np.pi*k*np.arange(N)/N))return ydef idft(y):x = np.zeros_like(y, dtype=np.float32)N = y.shape[0]for n in range(N):x[n] = np.real(np.sum(y * np.exp(1j*2*np.pi*n*np.arange(N)/N)) / N)return xN = 128
x = np.random.random(N).astype(np.float32)
y0 = dft(x)
y1 = np.fft.fft(x)
print (np.allclose(y0, y1))yr = np.random.random(N).astype(np.float32)
yi = np.random.random(N).astype(np.float32)
y = yr + 1j*yi
x0 = idft(y)
x1 = np.fft.ifft(y).real
print (np.allclose(x0, x1))
# True
# True

输出的两个结果都是True,也就说明这个计算结果是没问题的。

FFT快速傅里叶变换

首先我们整理一下所有参数相关的优化点:

\[\left\{ \begin{matrix} W_{N,n+N,k}=W_{N,n,k+N}=W_{N,n,k}\\ W_{N/m,n/m,k}=W_{N/m,n,k/m}=W_{N,n,k}\\ W_{N,\beta,\frac{N}{2\beta}}=W^*_{N,\beta,\frac{N}{2\beta}}=-1\Rightarrow W_{N,n,k}\cdot W_{N,\beta,\frac{N}{2\beta}}=-W_{N,n,k}\\ W_{N,N-n,k}=W_{N,N,k}\cdot W_{N,-n,k}=W_{N,-n,k}=W_{N,n,N-k} \end{matrix} \right. \]

此时如果我们把原始的输入\(x_n\)拆分为奇偶两组(如果总数N不是偶数,一般可以对输入数组做padding):

\[\left\{ \begin{matrix} x_{2r}\\ x_{2r+1} \end{matrix},0\leq r\leq \frac{N}{2}-1 \right. \]

则有:

\[y_k=\sum_{n=0}^{N-1}x_ne^{-j\frac{2\pi nk}{N}}=\sum_{r=0}^{\frac{N}{2}-1}x_{2r}W_{N,2r,k}+\sum_{r=0}^{\frac{N}{2}-1}x_{2r+1}W_{N,2r+1,k} \]

如果我们把\(x_{2r}\)\(x_{2r+1}\)看作是两个独立的输入数据,那么上述分解可以进一步优化:

\[\begin{align*} y_k&=\sum_{r=0}^{\frac{N}{2}-1}x_{2r}W_{N,2r,k}+\sum_{r=0}^{\frac{N}{2}-1}x_{2r+1}W_{N,2r+1,k}\\ &=\sum_{r=0}^{\frac{N}{2}-1}x^{(odd)}_{r'}W_{\frac{N}{2},r',k}+W_{N,1,k}\sum_{r=0}^{\frac{N}{2}-1}x^{(even)}_{r'}W_{\frac{N}{2},r',k}\\ &=y^{(odd)}_k+W_{N,1,k}y^{(even)}_k \end{align*} \]

同理可以得到:

\[\begin{align*} y_{k+\frac{N}{2}}&=y^{(odd)}_{k+\frac{N}{2}}+W_{N,1,k+\frac{N}{2}}y^{(even)}_{k+\frac{N}{2}}\\ &=y^{(odd)}_{k+\frac{N}{2}}-W_{N,1,k}y^{(even)}_{k+\frac{N}{2}} \end{align*} \]

这就是所谓的蝶形运算(图像来自于参考链接):

这个运算式的意义在于,假如我们原本做一个$2^N$点数据的傅里叶变换,使用原始的DFT运算我们需要做$2^{2N}$次乘法和$2^N(2^N-1)$次加法,但是这种方法可以把计算量缩减到$2\cdot2^N+2^{\frac{N}{2}}$次乘法和$2^{\frac{N}{2}}(2^\frac{N}{2}-1)$次加法。做一次分解,就把复杂度从$O(2^{2N})$降到了$O(2^N)$(注意:这里的$N$跟前面用到的数据点总数不是一个含义,这里的$N$指代数据点总数是2的整数次方,只是两者的表述习惯都常用$N$)。相关代码实现如下:
import numpy as npdef dft(x):y = np.zeros_like(x, dtype=np.complex64)N = x.shape[0]for k in range(N):y[k] = np.sum(x * np.exp(-1j*2*np.pi*k*np.arange(N)/N))return ydef dft2(x):y = np.zeros_like(x, dtype=np.complex64)N = x.shape[0]for k in range(N//2):c1 = np.exp(-1j*2*np.pi*k*np.arange(N//2)/(N//2))c2 = np.exp(-1j*2*np.pi*k/N)y1 = np.sum(x[::2] * c1)y2 = np.sum(x[1::2] * c1)y[k] = y1 + c2 * y2y[k+N//2] = y1 - c2 * y2return yN = 128
x = np.random.random(N).astype(np.float32)
y0 = dft2(x)
y1 = np.fft.fft(x)
print (np.allclose(y0, y1))
# True

运行输出为True,表示计算结果一致。需要注意的是,这里的代码未考虑padding问题,不能作为正式的代码实现,仅仅是一个算法演示。既然能够分割一次,那么就可以分割多次,直到无法分割为止,或者分割到一个指定的参数为止。这也就是多重蝶形运算的原理:

简单一点可以使用递归的方式进行计算:

import numpy as npdef dft(x):y = np.zeros_like(x, dtype=np.complex64)N = x.shape[0]for k in range(N):y[k] = np.sum(x * np.exp(-1j*2*np.pi*k*np.arange(N)/N))return ydef dftn(x, N_cut=2):y = np.zeros_like(x, dtype=np.complex64)N = x.shape[0]if N > N_cut:y1 = dftn(x[::2])y2 = dftn(x[1::2])else:return dft(x)for k in range(N//2):c2 = np.exp(-1j*2*np.pi*k/N)y[k] = y1[k] + c2 * y2[k]y[k+N//2] = y1[k] - c2 * y2[k]return yN = 1024
x = np.random.random(N).astype(np.float32)
y0 = dftn(x)
y1 = np.fft.fft(x)
print (np.allclose(y0, y1))
# True

这里的实现使用递归的方法,结合了前面实现的DFT算法和蝶形运算方法,得到的结果也是正确的。这里使用的蝶形运算优化方法,就是FFT快速傅里叶变换的基本思路。

N点快速傅里叶变换

所谓的N点FFT,其实就是每次只取N个数据点执行傅里叶变换。那么取数据点的方式就有很多种了,例如只取前N个数据点,或者降采样之后再取前N个数据点,再就是加窗,在经过窗函数的运算后,对每个窗体内的数据点做傅里叶变换。最简单的方式就是矩形窗,常见的还有汉宁窗和汉明窗,这里不做详细分析。值得注意的是,如果使用降采样的方法,采样率需要遵循奈奎斯特采样定理,要大于两倍的target frequency。尤其对于周期性边界条件和远程相互作用的场景,高频区域的贡献是不可忽视的。

至于为什么不使用全域数据点的傅里叶变换,即使我们可以用快速傅里叶变换把计算复杂度缩减到\(O(N\log N)\)(这里的\(N\)是数据点数)的级别,对于那些大规模数据传输和计算的场景,也是不适用的,因此使用降低傅里叶变换点数的思路对于大多数的场景来说可以兼顾到性能与精确度。而窗体函数的出现,进一步优化了截断处数据泄露的问题。

总结概要

本文介绍了离散傅里叶变换和快速傅里叶变换的基本原理及其对应的Python代码实现,并将计算结果与numpy所集成的fft函数进行对比。其实现在FFT计算的成熟工具已经有很多了,不论是CPU上scipy的fft模块还是GPU上的cufft动态链接库,都有非常好的性能。但还是得真正去了解计算背后的原理,和相关的物理图像,才能更恰当的使用这个强大的工具。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/fft.html

作者ID:DechinPhy

更多原著文章:https://www.cnblogs.com/dechinphy/

请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html

参考链接

  1. https://blog.csdn.net/qq_42604176/article/details/105559756

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

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

相关文章

9月24日课件之动手动脑

在本次课件中有多个动手动脑作业,再次我逐一学习分析。 一、首先是关于枚举的学习代码为, 运行结果为。 首先第一个运行结果显而易见的是false,第二个是因为枚举为类所以不是基本类型,在.isprimitive()中基本类型是返回true,类的话将会返回false。 第三个是.valueof()会返…

如何在低成本ARM平台部署LVGL免费图形库,基于全志T113-i

LVGL简介 LVGL(Littlev Graphics Library)是一个开源的图形库,主要用于嵌入式系统创建图形用户界面(GUI),采用C语言编写,具有高效性和可定制性,在各种微控制器平台和显示硬件上开发用户界面时备受欢迎。LVGL具社区免费开源、控件资源丰富、跨平台可移植等特点。 社区免费开…

一万字全面解析CRM的定义、分类与核心价值

1、CRM定义与分类 1.1CRM的定义 CRM,英文Customer Relationship Management的缩写,中文全称为客户关系管理。通常情况下,人们通常用CRM直接表达客户关系管理软件系统——一个以客户为中心的专门用于管理与客户关系的软件工具,以确保与客户在营销、销售、服务的每一环节上都…

module collections has no attribute Hashable PyDocx 库报错

### 项目背景在测试PyDocx代码时```python from pydocx import PyDocXhtml = PyDocX.to_html("test.docx") with项目背景 在测试PyDocx代码时 ```python from pydocx import PyDocX html = PyDocX.to_html("test.docx") with open("test.html", …

SimpleAIAgent:使用免费的glm-4-flash即可开始构建简单的AI Agent应用FI

合集 - C#(80)1.使用C#将几个Excel文件合并去重分类2023-11-152.C#使用SqlSugar操作MySQL数据库实现简单的增删改查2023-11-163.C#中的类和继承2023-11-174.C#中的virtual和override关键字2023-11-175.C#中的属性2023-11-206.C#winform中使用SQLite数据库2023-11-237.C#简化工作…

VLAN原理和配置

VLAN原理和配置 VLAN:虚拟局域网,将一个物理的局域网在逻辑上划分成多个广播域 华为交换机默认4094个VLAN 在交换机上配置VLAN,同一个VLAN内的用户可以进行二层互访,而不同VLAN 间的用户被二层隔离 VLAN帧格式 Tag用于区分不同的VLAN 没有携带Tag的帧DMAC SMAC Type Data F…

ddsadasdasd

目录理论部分 Ceph的诞生主要是为了解决以下问题: 操作部分 第一部分(虚拟机配置) 一、修改主机名 二、修改防火墙、SELinux状态 三、修改hosts文件 四、验证网络环境(请参阅 第一步、第四步) 五、配置 ceph 源 六、开始执行yum安装 七、创建目录 第二部分(部署ceph) 1…

.net 到底行不行!2000 人在线的客服系统真实屏录演示(附技术详解)

时常有朋友问我性能方面的问题,正好有一个真实客户,在线的访客数量达到了 2000 人。在争得客户同意后,我录了一个视频。升讯威在线客服系统可以在极低配置的服务器环境下,轻松应对这种情况,依然可以做到消息毫秒级送达,操作毫秒级响应。业余时间用 .net 写了一个免费的在…

记.Net Framework中wwwroot文件限制用户访问

背景 项目.Net Framework做的,已经线上跑了很多年了,突然发现用户上传的文件都被放到了wwwroot//Content/Upload目录,这些文件都是比较重要的,程序用来读取解析数据的,但是被直接可以公开访问了。 其实要改也很简单,代码改一下,文件挪一下位置就可以了,但是如果这样改就…

项目可能问问题

项目和简历 hr面试问题 自我介绍 面试官你好,我叫王首都,重庆邮电大学 计算机科学与技术专业研二在读,主要从事java后端开发,项目达人探店,它主要,实现了登录验证,缓存查询,优惠券秒杀,接口限流,以及签到打卡等功能。在上学期间获得了人民奖学金,新生奖学金,学业讲…

MongoDB 双活集群在运营商的实践

本文将着重分享某头部运营商订单中心在实现双活架构过程中的最佳实践,提供详细的技术细节和实际案例。通过介绍项目实施过程中的技术细节,提供类似场景需求的方案参考。在现代电信行业中,订单中心作为核心业务系统之一,承担着处理客户订单、管理订单状态、与各个业务系统进…

信创里程碑:TapData 与海量数据达成产品兼容互认证,共同助力基础设施国产化建设

测试结果显示,TapData LDP V3 与 Vastbase G100 V2.2 完全兼容,整体运行稳定高效,性能表现优秀,可为企业级客户提供可靠的中间件与数据库支撑。近日,深圳钛铂数据有限公司(以下简称钛铂数据)自主研发的钛铂实时数据平台(TapData Live Data Platform,TapData LDP)与北…