I am trying to calculate this convlution integral a(x)=\int{\varphi(x,x\prime)}w(x\prime)\rm{d}\it{V\prime},where \varphi(x,x\prime) is a compact support kernel function with a radius \ell. Currently, \varphi(x,x\prime) is choosen as \varphi(x,x\prime)=\frac{5}{\pi\ell^2}(1+3\frac{|x-x\prime|}{\ell})(1-\frac{|x-x\prime|}{\ell})^3 in the support and \varphi(x,x\prime)=0 outside the support. And the following is the code generate the kernel function:
## kernel function φ(x,x′)
function kernel_func(r,ℓ)
y = 5.0 * (1.0 + 3.0 * r / ℓ) * ( 1.0 - r / ℓ) ^ 3 / (π * ℓ ^ 2)
return y
end
function gene_kernel!(conv_kernel,poicoo_x,poicoo_y,numpoi_x,numpoi_y,ℓ)
for i in 1 : numpoi_x
for j in 1 : numpoi_y
r = sqrt(poicoo_x[i] ^ 2 + poicoo_y[j] ^ 2)
conv_kernel[i,j] = kernel_func(r,ℓ) * float(r < ℓ) + 0.0im
end
end
return conv_kernel
end
As this integral has a convolution structure, I attempt to use the convolution theorem as a(x) = \mathcal{F}^{-1}[\mathcal{F}(\varphi)\cdot\mathcal{F}(w)]. On the other hand, for each point x, I can find other points x\prime with the suppot and add all w(x\prime) to get the result.
However, I find that the result from FFT and the result from directly adding is different, with a 17% deviation. Attached is my code.
using FFTW
#### Evaluate a(x) = ∫φ(x,x′)w(x′)dV′
## kernel function φ(x,x′)
function kernel_func(r,ℓ)
y = 5.0 * (1.0 + 3.0 * r / ℓ) * ( 1.0 - r / ℓ) ^ 3 / (π * ℓ ^ 2)
return y
end
function gene_kernel!(conv_kernel,poicoo_x,poicoo_y,numpoi_x,numpoi_y,ℓ)
for i in 1 : numpoi_x
for j in 1 : numpoi_y
r = sqrt(poicoo_x[i] ^ 2 + poicoo_y[j] ^ 2)
conv_kernel[i,j] = kernel_func(r,ℓ) * float(r < ℓ) + 0.0im
end
end
return conv_kernel
end
## loop
function getpoiloc(i,numpoi_x)
if i % numpoi_x == 0
i_x = numpoi_x
i_y = Int(i / numpoi_x)
else
i_x = i % numpoi_x
i_y = fld(i,numpoi_x) + 1
end
return i_x,i_y
end
## as the kernel is compact support, I just need to loop point pairs within the radius ℓ
function loopconv!(a_loop,infvol,w,ℓ,numpoi_x,numpoi,poicoo_x,poicoo_y,dx,dy)
a_loop .= 0.0
infvol .= 0.0
for ithpoi = 1 : numpoi - 1
i_x,i_y = getpoiloc(ithpoi,numpoi_x)
for jthpoi = ithpoi : numpoi
j_x,j_y = getpoiloc(jthpoi,numpoi_x)
dist = sqrt((poicoo_x[i_x] - poicoo_x[j_x]) ^ 2 + (poicoo_y[i_y] - poicoo_y[j_y]) ^ 2)
if dist < ℓ
φ = kernel_func(dist,ℓ)
a_loop[i_x,i_y] += φ * w[j_x,j_y] * dx * dy
a_loop[j_x,j_y] += φ * w[i_x,i_y] * dx * dy
infvol[i_x,i_y] += φ * dx * dy
infvol[j_x,j_y] += φ * dx * dy
end
end
end
a_loop .= a_loop ./ infvol
return a_loop
end
## Spatial discretization
length_x = 1.2 # length in x-direction
length_y = 1.0 # length in y-direction
numpoi_x = 2 ^ 8 # number of points in x-direction
numpoi_y = 2 ^ 8 # number of points in y-direction
numpoi = numpoi_x * numpoi_y # number of all points
dx = length_x / (float(numpoi_x) - 1.0) # distance between points in x-direction
dy = length_y / (float(numpoi_y) - 1.0) # distance between points in y-direction
poicoo_x = collect(-0.5 * length_x : dx : 0.5 * length_x) # x coordinate of points
poicoo_y = collect(-0.5 * length_y : dy : 0.5 * length_y) # y coordinate of points
## generate kernel function
ℓ = 0.025
conv_kernel = zeros(ComplexF64,numpoi_x,numpoi_y)
gene_kernel!(conv_kernel,poicoo_x,poicoo_y,numpoi_x,numpoi_y,ℓ)
## function w(x)
w = rand(numpoi_x,numpoi_y)
## FFT-Based Convolution to evaluate ∫φ(x,x′)w(x′)dV′
# periodic shifted version of the kernel function as DFT requires the first element to be φ[0,0]
shift_kernel = fftshift(conv_kernel)
# φ̂
conv_kernel = fft(shift_kernel)
# a_fft
a_fft = ifft(conv_kernel .* fft(w) .* dx .* dy)
## using brutal force loop to evaluate a(x) = ∫φ(x,x′)w(x′)dV′
a_loop = zeros(Float64,numpoi_x,numpoi_y)
infvol = zeros(Float64,numpoi_x,numpoi_y)
loopconv!(a_loop,infvol,w,ℓ,numpoi_x,numpoi,poicoo_x,poicoo_y,dx,dy)
err = maximum(real(a_fft)) - maximum(a_loop) / maximum(a_loop)
Hi! @stevengj and @roflmaostc, I have updated a more trival version which only depends on FFTW
and add some comments. I hope this version can be easy to follow. Thanks for your help!