使用FFT在周期性边界条件下找到质心

时间:2013-08-10 21:26:38

标签: python numpy fft

我想使用傅里叶变换在周期性边界条件下找到模拟实体的中心;周期性边界条件意味着,只要有东西从盒子的一侧出来,就会像在经典游戏小行星中那样扭曲出现在相反的一侧。

所以我所拥有的是每个时间帧一个矩阵(Nx3),其中N是xyz中的点数。我想要做的是确定云的中心,即使它们都在周期性边界上移动,也就是说卡在两者之间。

我对解决方案的想法现在是对这些点进行(质量权重)直方图,然后对其执行FFT并使用第一个傅里叶系数的相位来确定最大值在框中的位置。

作为我使用的测试用例

import numpy as np
Points_x  = np.random.randn(10000)
Box_min   = -10
Box_max   =  10
X         = np.linspace( Box_min, Box_max, 100 )

### make a Histogram of the points
Histogram_Points = np.bincount( np.digitize( Points_x, X ),  minlength=100 )

### make an artifical shift over the periodic boundary
Histogram_Points = np.r_[ Histogram_Points[45:], Histogram_Points[:45] ]

Histogram of the points that moved over the periodic boundary

所以现在我可以使用FFT,因为它总是需要一个周期函数。

## doing fft
F = np.fft.fft(Histogram_Points)

## getting rid of everything but first harmonic
F[2:] = 0.

## back transforming
Fist_harmonic = np.fft.ifft(F)

这样我得到的正弦波最大值恰好是直方图的最大值。

现在我想提取最大值的位置而不是通过对正弦向量采用max函数,但不知何故它应该从第一个(不是第0个)傅立叶系数中检索,因为它应该以某种方式包含相位正弦的移动使其最大值恰好在直方图的最大值。

确实,密谋

Cos_approx = cos( linspace(0,2*pi,100) * angle(F[1]) )

会给 enter image description here

但我无法弄清楚如何从这个角度获得峰值的位置。

2 个答案:

答案 0 :(得分:6)

当你需要的只是一个傅里叶系数时,使用FFT是过度的。相反,您可以使用

简单地计算数据的点积
w = np.exp(-2j*np.pi*np.arange(N) / N)

其中N是点数。 (用FFT计算所有傅里叶系数的时间是O(N * log(N))。只计算一个系数是O(N)。)

这是一个类似于你的脚本。数据放在y;数据点的坐标位于x

import numpy as np

N = 100

# x coordinates of the data
xmin = -10
xmax = 10
x = np.linspace(xmin, xmax, N, endpoint=False)

# Generate data in y.
n = 35
y = np.zeros(N)
y[:n] = 1 - np.cos(np.linspace(0, 2*np.pi, n))
y[:n] /= 0.7 + 0.3*np.random.rand(n)
m = 10
y = np.r_[y[m:], y[:m]]

# Compute coefficent 1 of the discrete Fourier transform.
w = np.exp(-2j*np.pi*np.arange(N) / N)
F1 = y.dot(w)
print "F1 =", F1

# Get the angle of F1 (in the interval [0,2*pi]).
angle = np.angle(F1.conj())
if angle < 0:
    angle += 2*np.pi

center_x = xmin + (xmax - xmin) * angle / (2*np.pi)
print "center_x = ", center_x

# Create the first sinusoidal mode for the plot.
mode1 = (F1.real * np.cos(2*np.pi*np.arange(N)/N) -
         F1.imag*np.sin(2*np.pi*np.arange(N)/N))/np.abs(F1)


import matplotlib.pyplot as plt

plt.clf()
plt.plot(x, y)
plt.plot(x, mode1)
plt.axvline(center_x, color='r', linewidth=1)
plt.show()

这会生成情节:

Plot with "center" indicated.

回答“为什么F1.conj()?”的问题:

由于减号,使用F1的复共轭 w = np.exp(-2j*np.pi*np.arange(N) / N)(我之所以使用它,是因为它 是一个常见的惯例。)

可以写w

w = np.exp(-2j*np.pi*np.arange(N) / N)
  = cos(-2*pi*arange(N)/N) + 1j*sin(-2*pi*arange(N)/N)
  = cos(2*pi*arange(N)/N) - 1j*sin(2*pi*arange(N)/N)

点积y.dot(w)基本上是y的投影 cos(2*pi*arange(N)/N)F1的真实部分)和-sin(2*pi*arange(N)/N)F1的虚部)。但是当我们弄清楚阶段的时候 最大值,它基于函数cos(...)和sin(...)。以 复共轭解释了罪的相反符号() 功能。如果使用了w = np.exp(2j*np.pi*np.arange(N) / N),那么 不需要F1的复共轭。

答案 1 :(得分:2)

您可以直接在数据上计算圆形均值。

计算圆形平均值时,数据将映射到-pi..pi。此映射数据被解释为与单位圆上的点的角度。然后计算x和y分量的平均值。下一步是计算结果角度并将其映射回定义的“框”。

import numpy as np
import matplotlib.pyplot as plt

Points_x  = np.random.randn(10000)+1
Box_min   = -10
Box_max   =  10
Box_width = Box_max - Box_min

#Maps Points to Box_min ... Box_max with periodic boundaries
Points_x = (Points_x%Box_width + Box_min)
#Map Points to -pi..pi
Points_map = (Points_x - Box_min)/Box_width*2*np.pi-np.pi
#Calc circular mean
Pmean_map  = np.arctan2(np.sin(Points_map).mean() , np.cos(Points_map).mean())
#Map back
Pmean = (Pmean_map+np.pi)/(2*np.pi) * Box_width + Box_min

#Plotting the result
plt.figure(figsize=(10,3))
plt.subplot(121)
plt.hist(Points_x, 100);
plt.plot([Pmean, Pmean], [0, 1000], c='r', lw=3, alpha=0.5);
plt.subplot(122,aspect='equal')
plt.plot(np.cos(Points_map), np.sin(Points_map), '.');
plt.ylim([-1, 1])
plt.xlim([-1, 1])
plt.grid()
plt.plot([0, np.cos(Pmean_map)], [0, np.sin(Pmean_map)], c='r', lw=3, alpha=0.5);