并行快速傅里叶变换 mpi4py-fft

上一篇中我们介绍了一个使用 mpi4py 实现的并行日志工具 —— python-mpi-logger,下面将介绍一个并行的快速傅里叶变换库 —— mpi4py-fft。

快速傅里叶变换(FFT)简介

快速傅里叶变换(Fast Fourier Transform, FFT),是快速计算序列的离散傅里叶变换(DFT)或其逆变换的方法。傅里叶分析将信号从原始域(通常是时间或空间)转换到频域的表示或者逆过来转换。FFT 会通过把 DFT 矩阵分解为稀疏(大多为零)因子之积来快速计算此类变换。因此,它能够将计算 DFT 的复杂度从只用 DFT 定义计算需要的 O(n2)降低到 O(n log n),其中 n 为数据大小。

快速傅里叶变换广泛地应用于工程、科学和数学领域。1994年美国数学家吉尔伯特·斯特朗把 FFT 描述为“我们一生中最重要的数值算法”,它还被 IEEE 科学与工程计算期刊列入 20 世纪十大算法。

numpy.fft 模块提供了进行快速傅里叶变换的函数,可以对 1 维,2 维或更高维的数值进行快速傅里叶变换,不过 numpy.fft 只能对完整的数据集或者分布式数据集的某些完整的数据轴进行快速傅里叶变换操作,无法直接对一个分布在不同进程上的数据轴进行快速傅里叶变换。

下面是 numpy.fft 模块用于快速傅里叶变换的主要函数:

fft(a[, n, axis, norm])

计算数组 a 沿轴 axis 的 1 维快速傅里叶变换。

ifft(a[, n, axis, norm])

计算数组 a 沿轴 axis 的 1 维快速逆傅里叶变换。

fft2(a[, s, axes, norm])

计算数组 a 沿轴 axes 的 2 维快速傅里叶变换。

ifft2(a[, s, axes, norm])

计算数组 a 沿轴 axes 的 2 维快速逆傅里叶变换。

fftn(a[, s, axes, norm])

计算数组 a 沿轴 axes 的 n 维快速傅里叶变换。

ifftn(a[, s, axes, norm])

计算数组 a 沿轴 axes 的 n 维快速逆傅里叶变换。

关于 numpy.fft 模块的更多快速傅里叶变换相关的方法及其参数介绍请参加其文档

下面我们介绍一个使用 mpi4py 对(大的)多维数组(可以分布在多个 MPI 进程上)进行快速傅里叶变换的工具 —— mpi4py-fft。

mpi4py-fft

mpi4py-fft 是一个用于并行快速傅里叶变换计算的 Python 包。它可以分布及重新分布以多种方式分布在多个 MPI 进程上的多维数组,并对其沿任意指定轴(可以是多个轴,包括数据分布轴)进行快速傅里叶变换。mpi4py-fft 支持 FFTW 库(快速傅里叶变换的最快的自由软件实现)中的各种变换方法及它们的各种组合,它提供了 FFTW 库中各种变换方法的 Python 接口。另外,mpi4py-fft 也可以仅仅只对大的数组进行分布及重新分布(借助 mpi4py)而不做任何傅里叶变换操作。

mpi4py-fft 包含如下 4 个主要模块:

  • mpifft
  • pencil
  • libfft
  • fftw

mpifft.PFFT 类是做并行快速傅里叶变换的主要入口,这是一个功能强大而丰富的类,它能够自动完成沿各个轴的各种变换所需的(大)数组分布等任务。

pencil 具体完成数组的全局分布(通过 mpi4py)。一般情况下并不直接使用该模块,除非你仅仅只需进行数组的分布操作而不做任何傅里叶变换。mpifft.PFFT 在底层大量使用 pencil 模块的相关功能。

libfft 模块提供了一个(非并行)调用 FFTW 库中相关变换函数的通用的接口。

fftw 模块是对 FFTW 库中相应变换函数的具体包装。通过此模块,用户基本上可以直接在 Python 中使用 FFTW 库中的几乎任何功能。

下载和安装 mpi4py-fft

用下面的命令下载和安装 mpi4py-fft;

git clone https://bitbucket.org/mpi4py/mpi4py-fft.git
cd mpi4py-fft
export FFTW_ROOT=/path/to/your/FFTW
python setup.py install [--user] 

数组的全局分布

在高性能计算中,大的多维数组一般会分布在多个处理器上(这些处理器可以处于不同的物理机器上),我们称之为数组的全局分布。

mpi4py-fft 的 pencil 模块提供了 Pencil、 Subcomm 和 Transfer 三个类用来进行数组的全局分布。

下面是这三个类的定义及主要方法接口:

Pencil 类

class Pencil(object)

Pencil 类,表示一个分布式的数组。

def __init__(self, subcomm, shape, axis=-1)

初始化方法,subcomm 为 MPI 通信子或者一系列的 MPI 通信子。shape 为数组的 shape,axis 为数组对齐的轴(即不分布的轴)。

def pencil(self, axis)

返回一个在 axis 轴上对齐的 Pencil 对象。

def transfer(self, pencil, dtype)

返回一个 Transfer 类对象,该对象能进行当前的 Pencil 对象和参数 pencil 所表示的全局分布之间的相互转换。datatyp 为所使用的数据类型。

Subcomm 类

class Subcomm(tuple)

Subcomm 类,为表示多维数组分布的一系列通信子对象所组成的 tuple。

def __new__(cls, comm, dims=None, reorder=True)

构造方法,comm 为一个或一系列 MPI 通信子,dim 可为 None,一个整数或者一系列整数,指定分布在各维的进程数,对大于 0 的值会保持该值指定的进程数,对等于 0 的值所对应的维会根据总的进程数进行分配。所用应该使用 0 指定自动进程分配的分布轴,用 1 指定在该轴上不做分布,其它大于 0 的数值指定在该轴上分布的进程数。

Transfer 类

class Transfer(object)

Transfer 类,执行数组的全局分布操作。

def __init__(self, comm, shape, dtype, subshapeA, axisA, subshapeB, axisB)

初始化方法,comm 为一个 MPI 通信子,shape 为输入数组的 shape,dtype 为输入数组的 dtype,subshpaeA 为输入 pencil 的 shape,axisA 为输入数组对齐的轴,subshpaeA 为输出 pencil 的 shape,axisA 为输出数组对齐的轴。

def forward(self, arrayA, arrayB)

执行从 arrayAarrayB 的全局分布。

def backward(self, arrayB, arrayA)

执行从 arrayBarrayA 的全局分布。

下面这个例子展示这几个类的使用。

# pencil.py

import numpy as np
from mpi4py_fft import pencil
from mpi4py import MPI

comm = MPI.COMM_WORLD # MPI communicator

N = (8, 8) # shape of the global array
subcomm = pencil.Subcomm(comm, [0, 1]) # distribute on axis 0, not axis 1
p0 = pencil.Pencil(subcomm, N, axis=1) # create a pencil that aligned in axis 1
p1 = p0.pencil(0) # return a new pencil aligned in axis 0
a0 = np.zeros(p0.subshape) # local array with p0's distribution
a0[:] = comm.Get_rank()
print 'p0.subshape: ', a0.shape
transfer = p0.transfer(p1, np.float) # return a Transfer object from p0 to p1
a1 = np.zeros(p1.subshape) # local array with p1's distribution
print 'p1.subshape: ', a1.shape
transfer.forward(a0, a1) # global distribute from a0 to a1
transfer.backward(a1, a0) # global distribute from a1 to a0

以 4 个进程执行以上程序,结果为:

$ mpirun -np 4 python pencils.py
p0.subshape: (2, 8)
p0.subshape: (2, 8)
p0.subshape: (2, 8)
p0.subshape: (2, 8)
p1.subshape: (8, 2)
p1.subshape: (8, 2)
p1.subshape: (8, 2)
p1.subshape: (8, 2)

下图展示了该 8 × 8 数组从 p0 pencil 到 p1 pencil 的全局分布过程。

pencil (p0) y 轴对齐
pencil (p1) x 轴对齐

fftw 模块

fftw 模块提供了 FFTW 库中各种变换函数的包装接口,在 fftw.xfftn 子模块中有下列变换函数:

  • fftn() - 复到复快速傅里叶变换
  • ifftn() - 复到复快速逆傅里叶变换
  • rfftn() - 实到实快速傅里叶变换
  • irfftn() - 实到实快速逆傅里叶变换
  • dctn() - 实到实离散余弦变换 (DCT)
  • idctn() - 实到实离散逆余弦变换
  • dstn() - 实到实离散正弦变换 (DST)
  • idstn() - 实到实离散逆正弦变换
  • hfftn() - 厄密对称序列的复到实快速傅里叶变换
  • ihfftn() - 厄密对称序列的实到复快速逆傅里叶变换

并行快速傅里叶变换

并行快速傅里叶变换可以通过数组的全局分布和串行的快速傅里叶变换组合实现,mpi4py-fft 的子模块 mpifft 中的 PFFT 类实现并行快速傅里叶变换的功能(在底层自动完成数据的全局分布和再分布,无需使用者关心)。

下面是 PFFT 类及一个辅助的 Function 类的定义及主要方法:

PFFT 类

class PFFT(object)

PFFT 类,实现并行快速傅里叶变换。

def __init__(self, comm, shape, axes=None, dtype=float, slab=False, padding=False, collapse=False, use_pyfftw=False, transforms=None, **kw)

初始化方法,comm 为 MPI 通信子,shape 为要进行变换的数组的 shape, axes 指定变换轴,可以为 None、一个整数或者一系列整数,dtype 为输入数组的 dtype,slab 如果为 True,则数组只会分布到一个轴上,padding 可以为 True/False、一个数值或一系列数值,如果为 False 则变换的数据不做任何填充。如果为一个数值,则所用轴都会以该值作为填充因子进行填充,如果为一系列数值,则每个轴以该系列中对应的数值作为填充因子进行填充,在这种情况下,padding 的长度必须同 axes 的长度,collapse 指明是否将多个串行的变换合并为一个一起操作,pyfftw 指明是否使用 pyfftw 而不是本地的 FFTW 包装接口,transforms 可为 None 或一个字典,在字典中指定一系列变换轴及需执行的变换,如 {(0, 1): (dctn, idctn), (2, 3): (dstn, idstn)},如果为 None,则对实的输入数组默认使用 rfftn/irfftn,对复的输入数组使用 fftn/ifftn。

forward(input_array=None, output_array=None, **kw)

并行的正向变换,input_array 为输入数组,output_array 为输出数组。

backward(input_array=None, output_array=None, **kw)

并行的反向变换,input_array 为输入数组,output_array 为输出数组。

Function 类

class Function(np.ndarray)

Function 类,继承自 numpy.ndarray,本质上是一个分布式的 numpy 数组,其 shape 同创建此类对象的 PFFT 实例的输入变换或输出变换的 shape。

def __new__(cls, pfft, forward_output=True, val=0, tensor=None)

构造方法,pfft 为一个 PFFT 类对象,forward_output 如果为 True,则创建的 Function 数组的shape 同 pfft 变换的输出 shape,否则同 pfft 变换的输入 shape,val 为创建的数组的初始填充值。

以上简单介绍了 mpi4py-fft 工具及其中主要的模块和类方法,更多内容可以参见其文档

例程

下面给出使用例程:

# transform.py

"""
Demonstrates parallel FFT operations with mpi4py-fft package.

Run this with 2 processes like:
$ mpiexec -n 2 python transform.py
"""

import functools
import numpy as np
from mpi4py import MPI
from mpi4py_fft.mpifft import PFFT, Function
from mpi4py_fft.fftw import dctn, idctn


comm = MPI.COMM_WORLD

# global size of global array
N = [8, 8, 8]

dct = functools.partial(dctn, type=3) # the Discrete Cosine Transform
idct = functools.partial(idctn, type=3) # the inverse Discrete Cosine Transform

# transform: dct on axis 1, idct on axis 2
transforms = {(1, 2): (dct, idct)}

# FFT without padding
fft = PFFT(MPI.COMM_WORLD, N, axes=None, collapse=True, slab=True, transforms=transforms)
# FFT with padding, padding factor = [1.5, 1.0, 1.0] for each axis
pfft = PFFT(MPI.COMM_WORLD, N, axes=((0,), (1, 2)), slab=True, padding=[1.5, 1.0, 1.0], transforms=transforms)

# create and initialize the input array
u = Function(fft, False)
u[:] = np.random.random(u.shape).astype(u.dtype)

u_hat = Function(fft) # the output array
u_hat = fft.forward(u, u_hat) # the forward transform

# check the transform
uj = np.zeros_like(u)
uj = fft.backward(u_hat, uj)
print np.allclose(uj, u)

# transform with padding
u_padded = Function(pfft, False)
uc = u_hat.copy()
u_padded = pfft.backward(u_hat, u_padded)
u_hat = pfft.forward(u_padded, u_hat)
print np.allclose(u_hat, uc)

# complex transform, default use fftn/ifftn for the forward/backward transform
cfft = PFFT(MPI.COMM_WORLD, N, dtype=complex)

uc = np.random.random(cfft.backward.input_array.shape).astype(complex)
u2 = cfft.backward(uc)
u3 = uc.copy()
u3 = cfft.forward(u2, u3)

print np.allclose(uc, u3)

执行结果为:

$ mpiexec -n 2 python transform.py
True
True
True
True
True
True
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 一、傅立叶变换的由来 关于傅立叶变换,无论是书本还是在网上可以很容易找到关于傅立叶变换的描述,但是大都是些故弄玄虚...
    constant007阅读 4,416评论 1 10
  • 深入理解傅里叶变换Mar 12, 2017 这原本是我在知乎上对傅立叶变换、拉普拉斯变换、Z变换的联系?为什么要进...
    价值趋势技术派阅读 5,742评论 2 2
  • 姓名:荣皓宇 学号:17101223406 转载自:https://mp.weixin.qq.com/s/B026...
    荣皓宇阅读 3,513评论 2 9
  • 备份自:http://blog.rainy.im/2015/11/03/fourier-transform-in-...
    蛙声一爿阅读 6,044评论 4 40
  • 在上一篇中,我写到了傅里叶变换的来历以及直观的理解。关于傅里叶变换就只剩下它的性质,以及拉普拉斯变换和 Z-变换两...
    温素年阅读 3,009评论 0 2