首页 > 编程语言 >Python计算傅里叶变换

Python计算傅里叶变换

时间:2024-09-25 14:45:26浏览次数:8  
标签:frac matrix Python sum 变换 && np pi 傅里叶

技术背景

傅里叶变换在几乎所有计算相关领域都有可能被使用到,例如通信领域的滤波、材料领域的晶格倒易空间计算还有分子动力学中的倒易力场能量项等等。最简单的例子来说,计算周期性盒子的电势能\(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 np

def 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 y

def 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 x

N = 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 np

def 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 y

def 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 * y2
        y[k+N//2] = y1 - c2 * y2
    return y

N = 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 np

def 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 y

def 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 y

N = 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

标签:frac,matrix,Python,sum,变换,&&,np,pi,傅里叶
From: https://www.cnblogs.com/dechinphy/p/18427010/fft

相关文章

  • 轻松编排工作流,浅谈DolphinScheduler如何使用Python调用API接口?
    最近,在做某大型零售企业项目时,有客户用到DolphinScheduler,并咨询是否可以用Python脚本编排工作流?该如何实现?相信有很多人会有这样的疑问,那么,本文将为我们简单分享DolphinScheduler的优势和实际使用。为什么企业数据开发要使用海豚调度?当企业在做数据开发时,任务调度平台会扮演自......
  • 轻松编排工作流,浅谈DolphinScheduler如何使用Python调用API接口?
    最近,在做某大型零售企业项目时,有客户用到DolphinScheduler,并咨询是否可以用Python脚本编排工作流?该如何实现?相信有很多人会有这样的疑问,那么,本文将为我们简单分享DolphinScheduler的优势和实际使用。为什么企业数据开发要使用海豚调度?当企业在做数据开发时,任务调度平台会扮演自动......
  • 基于python+flask框架的临海市社区居民户籍管理系统的设计与实现(开题+程序+论文) 计算
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着城市化进程的加速,临海市作为一座快速发展的城市,其社区管理面临着前所未有的挑战。社区居民数量的激增、户籍信息的复杂化以及人口流动......
  • 基于python+flask框架的零食销售系统的设计与实现(开题+程序+论文) 计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着互联网的飞速发展,电子商务已成为现代商业活动的重要组成部分,深刻改变了人们的消费习惯。零食作为日常生活中不可或缺的一部分,其市场需......
  • 基于python+flask框架的流浪动物救助系统的设计与实现(开题+程序+论文) 计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着城市化进程的加快和生活方式的改变,流浪动物问题日益凸显,成为社会关注的焦点之一。城市中流浪猫狗数量的激增,不仅给城市环境带来压力,也......
  • python调用另一个.py文件中的类和函数或直接运行另一个.py文件
    同一文件夹下的调用1.调用函数A.py文件如下:defadd(x,y):print('和为:%d'%(x+y))在B.py文件中调用A.py的add函数如下:importAA.add(1,2)或fromAimportaddadd(1,2)2.调用类A.py文件如下:classA:def__init__(self,xx,yy):self.x=xxself.y=y......
  • python 2024-9
    第一课问题a,b求最大值?分类讨论ifa>b:print("最大值=",a)else:print("最大值=",b)a,b,c求最大值?条件语句if...elif...else列表最大值?与参照物循环比较a=[1.7,1.65,1.8,1.55,1.6]#身高列表mx=0#初始化最大值forxin......
  • python-成绩转换/药房管理/求正整数2~n之间的完全数
    一:成绩转换题目描述输入一个百分制的成绩 t ,将其转换成对应的等级,具体转换规则如下:90∼10090∼100 为A;80∼8980∼89为B;70∼7970∼79为C;60∼6960∼69为D;0∼590∼59为E。输入格式输入数据有多组,每组占一行,由一个整数组成。输出格式对于每组输入数据,输出一行。......
  • 基于python+flask框架的老人养老社区服务平台(开题+程序+论文) 计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着全球人口老龄化趋势的加剧,老年人口比例不断上升,如何为老年人提供全面、便捷、高效的养老服务成为社会各界关注的焦点。传统家庭养老模......
  • 基于python+flask框架的乐佳购物商城的设计与实现(开题+程序+论文) 计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,电子商务已成为现代商业活动不可或缺的一部分,极大地改变了人们的消费习惯。乐佳购物商城的设计与实现正是在这一......