fft and ifft file by taichi

最近在学taichi,想用taichi 模拟相场模型(PFM),但PFM模型中普遍使用半隐式傅里叶谱方法( semi-implicit Fourier spectral algorithm )求解AC和CH方程, 以提高模拟效率和准确性。目前貌似 taichi库中没有 fft和 ifft, 因此,写了一个简单的文件,用以实现fft和ifft,具体程序如下。程序中, (i)fft_ti和fft_np的结果做了一点对比,不是特别的准确,如果有错误和优化,欢迎提出建议。 仅供参考。

程序如下:

import time
import math
import numpy as np
import taichi as ti
import taichi.math as tm

ti.init(arch=ti.gpu, device_memory_fraction=0.95)

fft -numpy

np.random.seed = int(time.time())
nx, ny, nz = 64, 64, 64
a0 = np.random.randint(1000, size=(nx, ny, nz))
ak_np0 = np.fft.fftn(a0)

inver fft -numpy

a0_complex = np.random.randint(1000, size=(nx, ny, nz)) + 1j * np.random.randint(1000, size=(nx, ny, nz))
real0, imag0 = a0_complex.real, a0_complex.imag
a_np = np.fft.ifftn(a0_complex).real

fft -taichi

a = ti.field(dtype=ti.int32, shape=(nx, ny, nz))
a.from_numpy(a0)

ifft -taichi

real_ti = ti.field(dtype=ti.int32, shape=(nx, ny, nz))
imag_ti = ti.field(dtype=ti.int32, shape=(nx, ny, nz))
real_ti.from_numpy(real0), imag_ti.from_numpy(imag0)

fft -taichi

ak_fft = ti.Struct.field({
‘a0’: tm.vec2,
‘fftx’: tm.vec2,
‘ffty’: tm.vec2,
‘fftz’: tm.vec2}, shape=(nx, ny, nz))

ifft -taichi

ak_ifft = ti.Struct.field({
‘a0’: tm.vec2,
‘ifftx’: tm.vec2,
‘iffty’: tm.vec2,
‘ifftz’: tm.vec2}, shape=(nx, ny, nz))

@ti.func
def fft(var, var_fft, dim):

if dim == 0:
    for ix, iy, iz, iix in ti.ndrange(nx, ny, nz, nx):
        var_fft[ix, iy, iz][0] += (var[iix, iy, iz][0] * tm.cos(2 * math.pi / nx * ix * iix) +
                                   var[iix, iy, iz][1] * tm.sin(2 * math.pi / nx * ix * iix))
        var_fft[ix, iy, iz][1] += (var[iix, iy, iz][1] * tm.cos(2 * math.pi / nx * ix * iix) -
                                   var[iix, iy, iz][0] * tm.sin(2 * math.pi / nx * ix * iix))

elif dim == 1:
    for ix, iy, iz, iiy in ti.ndrange(nx, ny, nz, ny):
        var_fft[ix, iy, iz][0] += (var[ix, iiy, iz][0] * tm.cos(2 * math.pi / ny * iy * iiy) +
                                   var[ix, iiy, iz][1] * tm.sin(2 * math.pi / ny * iy * iiy))
        var_fft[ix, iy, iz][1] += (var[ix, iiy, iz][1] * tm.cos(2 * math.pi / ny * iy * iiy) -
                                   var[ix, iiy, iz][0] * tm.sin(2 * math.pi / ny * iy * iiy))

else:
    for ix, iy, iz, iiz in ti.ndrange(nx, ny, nz, nz):
        var_fft[ix, iy, iz][0] += (var[ix, iy, iiz][0] * tm.cos(2 * math.pi / nz * iz * iiz) +
                                   var[ix, iy, iiz][1] * tm.sin(2 * math.pi / nz * iz * iiz))
        var_fft[ix, iy, iz][1] += (var[ix, iy, iiz][1] * tm.cos(2 * math.pi / nz * iz * iiz) -
                                   var[ix, iy, iiz][0] * tm.sin(2 * math.pi / nz * iz * iiz))

@ti.func
def ifft(var, var_ifft, dim):

if dim == 0:
    for ix, iy, iz, iix in ti.ndrange(nx, ny, nz, nx):
        var_ifft[ix, iy, iz][0] += (var[iix, iy, iz][0] * tm.cos(2 * math.pi / nx * ix * iix) -
                                    var[iix, iy, iz][1] * tm.sin(2 * math.pi / nx * ix * iix)) / nx
        var_ifft[ix, iy, iz][1] += (var[iix, iy, iz][1] * tm.cos(2 * math.pi / nx * ix * iix) +
                                    var[iix, iy, iz][0] * tm.sin(2 * math.pi / nx * ix * iix)) / nx

elif dim == 1:
    for ix, iy, iz, iiy in ti.ndrange(nx, ny, nz, ny):
        var_ifft[ix, iy, iz][0] += (var[ix, iiy, iz][0] * tm.cos(2 * math.pi / ny * iy * iiy) -
                                    var[ix, iiy, iz][1] * tm.sin(2 * math.pi / ny * iy * iiy)) / ny
        var_ifft[ix, iy, iz][1] += (var[ix, iiy, iz][1] * tm.cos(2 * math.pi / ny * iy * iiy) +
                                    var[ix, iiy, iz][0] * tm.sin(2 * math.pi / ny * iy * iiy)) / ny

else:
    for ix, iy, iz, iiz in ti.ndrange(nx, ny, nz, nz):
        var_ifft[ix, iy, iz][0] += (var[ix, iy, iiz][0] * tm.cos(2 * math.pi / nz * iz * iiz) -
                                    var[ix, iy, iiz][1] * tm.sin(2 * math.pi / nz * iz * iiz)) / nz
        var_ifft[ix, iy, iz][1] += (var[ix, iy, iiz][1] * tm.cos(2 * math.pi / nz * iz * iiz) +
                                    var[ix, iy, iiz][0] * tm.sin(2 * math.pi / nz * iz * iiz)) / nz

@ti.kernel
def test():

for i in ti.grouped(ak_ifft):
    # ak_fft[i].a0[0] = a[i]
    ak_ifft[i].a0[0] = real_ti[i]
    ak_ifft[i].a0[1] = imag_ti[i]

# fft(ak_fft.a0, ak_fft.fftx, 0)
# fft(ak_fft.fftx, ak_fft.ffty, 1)
# fft(ak_fft.ffty, ak_fft.fftz, 2)

ifft(ak_ifft.a0, ak_ifft.ifftx, 0)
ifft(ak_ifft.ifftx, ak_ifft.iffty, 1)
ifft(ak_ifft.iffty, ak_ifft.ifftz, 2)

test()

#--------------- for check----------------------------

fft

ak_ti = ak_fft.fftz.to_numpy()
ak_np = np.zeros([nx, ny, nz, 2])
ak_np[:, :, :, 0] = ak_np0.real
ak_np[:, :, :, 1] = ak_np0.imag

ak_ti0, ak_ti1 = ak_ti[:, :, :, 0], ak_ti[:, :, :, 1]
ak_np0, ak_np1 = ak_np[:, :, :, 0], ak_np[:, :, :, 1]

ifft

a0_ti = ak_ifft.ifftz.to_numpy()
a_ti = a0_ti[:, :, :, 0]
diff_a = a_np - a_ti
ratio = a_np / a_ti
print(np.max(diff_a), np.min(diff_a))
print(np.max(ratio), np.min(ratio))

用64位精度试试,ti.init(arch=ti.gpu, device_memory_fraction=0.95,default_fp=ti.f64)

另外在我的感觉上taichi的精度没有其它语言并行时的精度高

64位精度时,还是满准确的,说明算法是没有什么打的问题的