【问题标题】:Speedup MSD calculation in Python在 Python 中加速 MSD 计算
【发布时间】:2016-01-04 10:49:40
【问题描述】:

这是对社区的一次呼吁,看看是否有人有提高此 MSD 计算实施速度的想法。它主要基于这篇博文中的实现:http://damcb.com/mean-square-disp.html

目前,对于 5000 个点的 2D 轨迹,当前实现大约需要 9 秒。如果你需要计算很多轨迹,这真的太多了......

我没有尝试并行化它(使用multiprocessjoblib),但我觉得创建新进程对于这种算法来说太繁重了。

代码如下:

import os

import matplotlib
import matplotlib.pyplot as plt

import pandas as pd
import numpy as np

# Parameters
N = 5000
max_time = 100
dt = max_time / N

# Generate 2D brownian motion

t = np.linspace(0, max_time, N)
xy = np.cumsum(np.random.choice([-1, 0, 1], size=(N, 2)), axis=0)
traj = pd.DataFrame({'t': t, 'x': xy[:,0], 'y': xy[:,1]})
print(traj.head())

# Draw motion
ax = traj.plot(x='x', y='y', alpha=0.6, legend=False)

# Set limits
ax.set_xlim(traj['x'].min(), traj['x'].max())
ax.set_ylim(traj['y'].min(), traj['y'].max())

然后输出:

          t  x  y
0  0.000000 -1 -1
1  0.020004 -1  0
2  0.040008 -1 -1
3  0.060012 -2 -2
4  0.080016 -2 -2

def compute_msd(trajectory, t_step, coords=['x', 'y']):

    tau = trajectory['t'].copy()
    shifts = np.floor(tau / t_step).astype(np.int)
    msds = np.zeros(shifts.size)
    msds_std = np.zeros(shifts.size)

    for i, shift in enumerate(shifts):
        diffs = trajectory[coords] - trajectory[coords].shift(-shift)
        sqdist = np.square(diffs).sum(axis=1)
        msds[i] = sqdist.mean()
        msds_std[i] = sqdist.std()

    msds = pd.DataFrame({'msds': msds, 'tau': tau, 'msds_std': msds_std})
    return msds

# Compute MSD
msd = compute_msd(traj, t_step=dt, coords=['x', 'y'])
print(msd.head())

# Plot MSD
ax = msd.plot(x="tau", y="msds", logx=True, logy=True, legend=False)
ax.fill_between(msd['tau'], msd['msds'] - msd['msds_std'], msd['msds'] + msd['msds_std'], alpha=0.2)

然后输出:

       msds  msds_std       tau
0  0.000000  0.000000  0.000000
1  1.316463  0.668169  0.020004
2  2.607243  2.078604  0.040008
3  3.891935  3.368651  0.060012
4  5.200761  4.685497  0.080016

还有一些分析:

%timeit msd = compute_msd(traj, t_step=dt, coords=['x', 'y'])

给这个:

1 loops, best of 3: 8.53 s per loop

有什么想法吗?

【问题讨论】:

  • 因为你已经有了工作代码,这可能是 codereview 的一个很好的候选者。
  • 哦,我不知道 codereview。版主可以确认一下,我会将其移至 codereview 吗?
  • 我是 Code Review 的版主,我已将此问题标记为迁移到 Code Review。我们所能做的就是等着看 Stack Overflow 版主是否同意这一点。
  • 我得到一个 NA 并且 compute_msd 第二行中的 floor 函数在尝试转换为 int 时抛出异常。 (numpy 1.9.2、Py2.7.10、OSX)还有其他人吗?
  • 在 Ubuntu 上使用 numpy 1.9.3、pandas 0.16.2 和 python 3.4 对我有用...

标签: python-3.x numpy pandas physics


【解决方案1】:

它逐行进行了一些分析,熊猫似乎让这变得很慢。这个纯 numpy 版本大约快 14 倍:

def compute_msd_np(xy, t, t_step):
    shifts = np.floor(t / t_step).astype(np.int)
    msds = np.zeros(shifts.size)
    msds_std = np.zeros(shifts.size)

    for i, shift in enumerate(shifts):
        diffs = xy[:-shift if shift else None] - xy[shift:]
        sqdist = np.square(diffs).sum(axis=1)
        msds[i] = sqdist.mean()
        msds_std[i] = sqdist.std(ddof=1)

    msds = pd.DataFrame({'msds': msds, 'tau': t, 'msds_std': msds_std})
    return msds

【讨论】:

    【解决方案2】:

    添加到上面的 moarningsun 答案:

    • 您可以使用 numexpr 加快速度
    • 如果您以对数比例绘制 MSD,则无需每次都计算它

      import numpy as np
      import numexpr
      
      def logSpaced(L, pointsPerDecade=15):
          """Generate an array of log spaced integers smaller than L"""
          nbdecades = np.log10(L)
          return np.unique(np.logspace(
              start=0, stop=nbdecades, 
              num=nbdecades * pointsPerDecade, 
              base=10, endpoint=False
              ).astype(int))
      
      def compute_msd(xy, pointsPerDecade=15):
          dts = logSpaced(len(xy), pointsPerDecade)
          msd = np.zeros(len(idts))
          msd_std = np.zeros(len(idts))
          for i, dt in enumerate(dts):
              sqdist = numexpr.evaluate(
                  '(a-b)**2',
                  {'a': xy[:-dt], 'b':xy[dt:]}
                  ).sum(axis=-1)
              msd[i] = sqdist.mean()
              msd_std[i] = sqdist.std(ddof=1)
          msds = pd.DataFrame({'msds': msd, 'tau': dt, 'msds_std': msd_std})
          return msds
      

    【讨论】:

    • 谢谢。你有比较过 numexpr 版本和 moarningsun 版本的速度吗?
    【解决方案3】:

    到目前为止提到的 MSD 计算都是 O(N**2) ,其中 N 是时间步数。使用 FFT,这可以减少到 O(N*log(N))。请参阅this question and answer 了解python 中的解释和实现。

    编辑: 一个小基准(我还添加了这个基准to this answer):使用

    生成轨迹
    r = np.cumsum(np.random.choice([-1., 0., 1.], size=(N, 3)), axis=0)
    

    对于 N=100.000,我们得到

    $ %timeit msd_straight_forward(r)
    1 loops, best of 3: 2min 1s per loop
    
    $ %timeit msd_fft(r)
    10 loops, best of 3: 253 ms per loop
    

    【讨论】:

    • 如果它对某人有帮助我很高兴:)
    【解决方案4】:

    我用 cmets 设计了这个功能:

    def get_msd(traj, dt, with_nan=True):
    
        shifts = np.arange(1, len(traj), dtype='int')
        msd = np.empty((len(shifts), 2), dtype='float')
        msd[:] = np.nan
    
        msd[:, 1] = shifts * dt
    
        for i, shift in enumerate(shifts):
            diffs = traj[:-shift] - traj[shift:]
            if with_nan:
                diffs = diffs[~np.isnan(diffs).any(axis=1)]
            diffs = np.square(diffs).sum(axis=1)
    
            if len(diffs) > 0:
                msd[i, 0] = np.mean(diffs)
    
        msd = pd.DataFrame(msd)
        msd.columns = ["msd", "delay"]
    
        msd.set_index('delay', drop=True, inplace=True)
        msd.dropna(inplace=True)
    
        return msd
    

    具有以下特点:

    • numpy数组作为轨迹输入。
    • 它返回一个几乎没有覆盖的pandas.DataFrame
    • with_nan 允许处理包含 NaN 值的轨迹,但它增加了很大的开销(超过 100%),所以我把它作为函数参数。
    • 可以处理多维轨迹(1D、2D、3D等)

    一些分析:

    $ print(traj.shape)
    (2108, 2)
    
    $ %timeit get_msd(traj, with_nan=True, dt=0.1)
    10 loops, best of 3: 143 ms per loop
    
    $ %timeit get_msd(traj, with_nan=False, dt=0.1)
    10 loops, best of 3: 68 ms per loop
    

    【讨论】:

      【解决方案5】:

      也许不是主题,但是必须计算 MSD,而不是像第 37 行那样的平均值:

      msds[i] = sqdist.mean()
      

      冒充mean=N

      你必须除以:

      msds[i] = sqdist/N-1 // for lag1
      

      然后:

      msds[i] = sqdist/N-2 // for lag2 .... msds[i] = sqdist/N-n // for lag n
      

      等等。

      因此,您没有得到标准偏差,只有单个轨迹的 MSD

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-01-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-01
        相关资源
        最近更新 更多