一个快速的Lomb-Scargle周期图。它很棒,并使用NUFFT。
项目描述
nifty-ls
一个快速的Lomb-Scargle周期图。它很棒,并使用NUFFT!
概述
用于识别不规则间距观测中周期性的Lomb-Scargle周期图,虽然有用但计算成本高昂。然而,它可以用数学方式表述为两个非均匀FFT(NUFFT)。这使我们能够利用Flatiron研究所的 finufft 软件包,它真的很快!它还支持GPU(CUDA)并比默认设置的Astropy的Lomb Scargle准确几个数量级。
背景
Press & Rybicki (1989) 的Lomb-Scargle方法将计算表述为四个加权三角函数和,并通过“抽提”到等间距网格来解决这些和。具体来说,和的形式为
\begin{align}
S_k &= \sum_{j=1}^M h_j \sin(2 \pi f_k t_j), \\
C_k &= \sum_{j=1}^M h_j \cos(2 \pi f_k t_j),
\end{align}
其中,$k$ 下标从0到 $N$,即频率分箱数,$f_k$ 是分箱 $k$ 的循环频率,$t_j$ 是观测时间(共有 $M$ 个),$h_j$ 是权重。
我们的关键观察是这恰好是一个非均匀FFT计算的内容!具体来说,在 finufft约定 中,“类型-1”(非均匀到均匀)复数NUFFT计算
g_k = \sum_{j=1}^M h_j e^{i k t_j}.
这个变换的复部和实部是Press & Rybicki的 $S_k$ 和 $C_k$,并对循环/角频率、$k$ 的域、实部与复数变换等进行了调整。finufft有一个特别快速且准确的扩散核(“半圆的指数”),它使用这个核而不是Press & Rybicki的抽提。
对于 $S_k$ 和 $C_k$ 的一些预处理和后处理来计算周期图,这可能会成为瓶颈,因为finufft非常快。这个软件包还优化并并行化了这些计算。
安装
来自PyPI
针对CPU支持
$ pip install nifty-ls
针对GPU(CUDA)支持
$ pip install nifty-ls[cuda]
默认情况下,安装支持CUDA 12;可以使用nifty-ls[cuda11]
代替以支持CUDA 11(安装cupy-cuda11x
)。
从源代码安装
首先,克隆仓库并将cd
到仓库根目录
$ git clone https://www.github.com/flatironinstitute/nifty-ls
$ cd nifty-ls
然后,安装支持CPU
$ pip install .
安装支持GPU(CUDA)
$ pip install .[cuda]
或.[cuda11]
以支持CUDA 11。
用于开发(默认在pyproject.toml
中启用自动重建)
$ pip install nanobind scikit-build-core
$ pip install -e .[test] --no-build-isolation
开发人员还可能对在pyproject.toml
中设置这些键感兴趣
[tool.scikit-build]
cmake.build-type = "Debug"
cmake.verbose = true
install.strip = false
为了最佳性能
您可能希望自行编译和安装finufft和cufinufft,以便它们可以针对您的硬件进行优化。为此,首先安装nifty-ls,然后按照finufft和cufinufft的Python安装说明进行操作,并按需配置库。
nifty-ls也可以根据上述说明从源代码构建以获得最佳性能,但由于大部分繁重计算都由(cu)finufft处理,因此性能提升很小。
用法
来自Astropy
导入nifty_ls
将nifty-ls通过Astropy的LombScargle模块中的method="fastnifty"
提供。名称前缀为"fast",因为它属于假设具有均匀频率网格的快速方法系列。
import nifty_ls
from astropy.timeseries import LombScargle
frequency, power = LombScargle(t, y).autopower(method="fastnifty")
完整示例
import matplotlib.pyplot as plt
import nifty_ls
import numpy as np
from astropy.timeseries import LombScargle
rng = np.random.default_rng(seed=123)
N = 1000
t = rng.uniform(0, 100, size=N)
y = np.sin(50 * t) + 1 + rng.poisson(size=N)
frequency, power = LombScargle(t, y).autopower(method='fastnifty')
plt.plot(frequency, power)
plt.xlabel('Frequency (cycles per unit time)')
plt.ylabel('Power')
要使用CUDA(cufinufft)后端,通过method_kws
传递适当的参数
frequency, power = LombScargle(t, y).autopower(method="fastnifty", method_kws=dict(backend="cufinufft"))
在许多情况下,加速您的周期图只需在您的Astropy Lomb Scargle代码中设置method
即可!更高级的使用,例如并行计算多个周期图,应直接通过nifty-ls接口进行。
来自nifty-ls(本地接口)
nifty-ls具有自己的接口,该接口提供的批量周期图的灵活性比Astropy接口更高。
单个周期图
单个周期图可以通过nifty-ls计算,如下所示
import nifty_ls
# with automatic frequency grid:
nifty_res = nifty_ls.lombscargle(t, y, dy)
# with user-specified frequency grid:
nifty_res = nifty_ls.lombscargle(t, y, dy, fmin=0.1, fmax=10, Nf=10**6)
完整示例
import nifty_ls
import numpy as np
rng = np.random.default_rng(seed=123)
N = 1000
t = np.sort(rng.uniform(0, 100, size=N))
y = np.sin(50 * t) + 1 + rng.poisson(size=N)
# with automatic frequency grid:
nifty_res = nifty_ls.lombscargle(t, y)
# with user-specified frequency grid:
nifty_res = nifty_ls.lombscargle(t, y, fmin=0.1, fmax=10, Nf=10**6)
plt.plot(nifty_res.freq(), nifty_res.power)
plt.xlabel('Frequency (cycles per unit time)')
plt.ylabel('Power')
批量周期图
批量周期图(具有相同观测时间的多个对象)可以按如下方式计算
import nifty_ls
import numpy as np
N_t = 100
N_obj = 10
Nf = 200
rng = np.random.default_rng()
t = np.sort(rng.random(N_t))
obj_freqs = rng.random(N_obj).reshape(-1,1)
y_batch = np.sin(obj_freqs * t)
dy_batch = rng.random(y_batch.shape)
batched = nifty_ls.lombscargle(t, y_batch, dy_batch, Nf=Nf)
print(batched.power.shape) # (10, 200)
请注意,此方法在具有相同观测时间的时间序列集合上同时计算多个周期图。此方法对于短时间序列特别有效,并且/或者当使用GPU时。
目前尚未实现支持具有不同观测时间的时间序列的批量,但计划实现。
限制
该代码仅支持固定间隔的频率网格;然而,finufft支持类型3 NUFFTs(非均匀到非均匀),这将启用任意频率网格。尚不清楚这有多有用,因此尚未实现,但如果您对此感兴趣,请打开GitHub问题。
性能
使用英特尔Icelake CPU的16个核心和NVIDIA A100 GPU,我们获得了以下性能。首先,我们将查看单个周期图的结果(即非批量)
在这种情况下,finufft比Astropy大5倍(带线程为11倍)的速度快,对于(非常)小的转换速度为2倍。相对于Astropy,小转换在更多频率分箱中进一步改进。(动态多线程调度转换是计划中的未来功能,特别有利于小的$N$。)
cufinufft对于大的$N$比Astropy快200倍!性能在小的$N$时趋于平稳,这主要是由于将数据发送到GPU和获取结果的开销。(在GPU上并发作业执行是计划中的另一个功能,将特别帮助小的$N$。)
以下演示了"批量模式",其中从10个具有相同观测时间的不同时间序列中计算了10个周期图
在这里,finufft单线程的优势在问题规模上始终为6倍,而多线程优势在大型变换中可达30倍。
在这个案例中,GPU的200倍优势扩展到了更小的$N$,因为我们一次发送和接收更多数据。
我们注意到,多线程的finufft和cufinufft特别受益于批量变换,因为这揭示了更多的并行性,并分摊了固定的延迟。
在这些基准测试中,我们对finufft使用了FFTW_MEASURE
,这提高了性能几个百分点。
多线程对小问题规模的影响很大;nifty-ls在这种情况下默认使用更少的线程。 “多线程”线使用1到16个线程。
在CPU上,nifty-ls不仅通过使用finufft获得性能,而且通过将预处理和后处理步骤卸载到编译扩展来提高性能。这些扩展使我们能够对元素进行更多处理,而不是对数组进行处理。换句话说,它们使“内核融合”(借用GPU计算中的一个术语)成为可能,从而提高了计算密度。
精度
虽然我们与Astropy的fast
方法进行了性能比较,但这并不完全公平。nifty-ls比Astropy fast
更准确!Astropy fast
使用Press & Rybicki的外推近似,以速度换取精度,但多亏了finufft,nifty-ls可以两者兼得。
在下图中,我们用圆圈绘制了不同$N$(和默认$N_F \approx 12N$)的astropy、finufft和cufinufft的中值频谱误差和99百分位误差。
astropy的结果提供了两种情况:一种是标准情况,另一种是“最坏情况”。内部,astropy使用一个FFT网格,其大小是目标过采样率以上的下一个2的幂。通常,每次跳到新的2的幂都会导致精度提高。因此,“最坏情况”是那些不会导致这种跳跃的最高频率。
最坏情况评估中常见的误差为$\mathcal{O}(10\%)$或更大。典型评估中常见的误差为$\mathcal{O}(1\%)$或更大。nifty-ls的精度保守估计比这高6个数量级。
上图中的参考结果来自“相绕行”方法,该方法使用三角恒等式来避免昂贵的正弦和余弦评估。也可以使用astropy的fast
方法作为参考,通过use_fft=False
启用精确评估。发现相同的结果,但相绕行要快几个数量级(但仍然与finufft不相竞争)。
总之,nifty-ls在保持高性能的同时,也具有高度的准确性。
float32 vs float64
虽然32位浮点数可以为finufft和cufinufft提供实质性的速度提升,但我们通常不建议在Lomb-Scargle中使用它们。原因是问题的挑战性条件数。条件数是输出对输入微小扰动的响应——换句话说,是导数。 可以很容易地证明 NUFFT相对于非均匀点的导数与$N$成正比,即变换长度(即模式数)。换句话说,观测时间误差被放大$\mathcal{O}(N)$。由于float32的相对误差为$\mathcal{O}(10^{-7})$,长度为$10^5$的变换已经受到$\mathcal{O}(1\%)$误差的影响。因此,我们在nifty-ls中关注float64,但float32也由所有后端原生支持,供有冒险心的用户使用。
条件数也是上述图中误差随$N$增加的温和上升趋势的可能贡献者,至少对于finufft/cufinufft。对于float64的相对误差为$\mathcal{O}(10^{-16})$和变换长度为$\mathcal{O}(10^6)$,最小误差为$\mathcal{O}(10^{-10})$。
测试
首先,从源代码安装(pip install .[test]
)。然后,从仓库根目录运行
$ pytest
测试定义在 tests/
目录中,包括对 nifty-ls 和 Astropy 的迷你基准测试,如下所示
$ pytest
======================================================== test session starts =========================================================
platform linux -- Python 3.10.13, pytest-8.1.1, pluggy-1.4.0
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=True min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /mnt/home/lgarrison/nifty-ls
configfile: pyproject.toml
plugins: benchmark-4.0.0, asdf-2.15.0, anyio-3.6.2, hypothesis-6.23.1
collected 36 items
tests/test_ls.py ...................... [ 61%]
tests/test_perf.py .............. [100%]
----------------------------------------- benchmark 'Nf=1000': 5 tests ----------------------------------------
Name (time in ms) Min Mean StdDev Rounds Iterations
---------------------------------------------------------------------------------------------------------------
test_batched[finufft-1000] 6.8418 (1.0) 7.1821 (1.0) 0.1831 (1.32) 43 1
test_batched[cufinufft-1000] 7.7027 (1.13) 8.6634 (1.21) 0.9555 (6.89) 74 1
test_unbatched[finufft-1000] 110.7541 (16.19) 111.0603 (15.46) 0.1387 (1.0) 10 1
test_unbatched[astropy-1000] 441.2313 (64.49) 441.9655 (61.54) 1.0732 (7.74) 5 1
test_unbatched[cufinufft-1000] 488.2630 (71.36) 496.0788 (69.07) 6.1908 (44.63) 5 1
---------------------------------------------------------------------------------------------------------------
--------------------------------- benchmark 'Nf=10000': 3 tests ----------------------------------
Name (time in ms) Min Mean StdDev Rounds Iterations
--------------------------------------------------------------------------------------------------
test[finufft-10000] 1.8481 (1.0) 1.8709 (1.0) 0.0347 (1.75) 507 1
test[cufinufft-10000] 5.1269 (2.77) 5.2052 (2.78) 0.3313 (16.72) 117 1
test[astropy-10000] 8.1725 (4.42) 8.2176 (4.39) 0.0198 (1.0) 113 1
--------------------------------------------------------------------------------------------------
----------------------------------- benchmark 'Nf=100000': 3 tests ----------------------------------
Name (time in ms) Min Mean StdDev Rounds Iterations
-----------------------------------------------------------------------------------------------------
test[cufinufft-100000] 5.8566 (1.0) 6.0411 (1.0) 0.7407 (10.61) 159 1
test[finufft-100000] 6.9766 (1.19) 7.1816 (1.19) 0.0748 (1.07) 132 1
test[astropy-100000] 47.9246 (8.18) 48.0828 (7.96) 0.0698 (1.0) 19 1
-----------------------------------------------------------------------------------------------------
------------------------------------- benchmark 'Nf=1000000': 3 tests --------------------------------------
Name (time in ms) Min Mean StdDev Rounds Iterations
------------------------------------------------------------------------------------------------------------
test[cufinufft-1000000] 8.0038 (1.0) 8.5193 (1.0) 1.3245 (1.62) 84 1
test[finufft-1000000] 74.9239 (9.36) 76.5690 (8.99) 0.8196 (1.0) 10 1
test[astropy-1000000] 1,430.4282 (178.72) 1,434.7986 (168.42) 5.5234 (6.74) 5 1
------------------------------------------------------------------------------------------------------------
Legend:
Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.
OPS: Operations Per Second, computed as 1 / Mean
======================================================== 36 passed in 30.81s =========================================================
结果是在 16 核的英特尔 Icelake CPU 和 1 个 NVIDIA A100 GPU 上获得的。与最快的运行时间相比的比率显示在括号中。您可能在您的平台上获得非常不同的性能!特别是最慢的 Astropy 结果可能取决于您安装的 Numpy 发行版及其三角函数性能。
作者
nifty-ls 最初由 Lehman Garrison 实现,基于 Dan Foreman-Mackey 在 dfm/nufft-ls 仓库中的工作,并在 Alex Barnett 的咨询下完成。
致谢
nifty-ls 直接建立在 Alex Barnett 和其他人出色的 finufft 包之上(参见 finufft 致谢)。
本包的许多部分是对 Astropy LombScargle 的改编,特别是 Press & Rybicki (1989) 方法。
项目详情
nifty_ls-1.0.1.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 137259ec69cd3e05b894e344b2080f7649c2b74b5803d6d8b059362ec4ddc09a |
|
MD5 | 8274b029a9ffb4fd7ded6fd850a09c17 |
|
BLAKE2b-256 | c1436c082952f0dbdf9dd3481a00b382bae9f0e11523098b61bf0e1afa8c92ee |
nifty_ls-1.0.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 176433fd58f5fdabc9f385c693292528c31c801bccb55fbd8e6f795017c8e422 |
|
MD5 | bc13e20ac1bf97958939d91eaf701f69 |
|
BLAKE2b-256 | c7b737d24c15e3887ad3397e842c2778dcd718d39a06c21ed99b6b7af5d710a9 |
nifty_ls-1.0.1-cp312-abi3-macosx_12_0_x86_64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 89a7765d8b337d03f034593891bee7575418fb7b1e4dc1d0a077f07b4205d7a4 |
|
MD5 | 1d4de38e07c79fc92af51c651ece9462 |
|
BLAKE2b-256 | f92d0c82532e8a5f434e1521736d1f617780d60895fc8aaa07de915ef45c0f6e |
nifty_ls-1.0.1-cp312-abi3-macosx_12_0_arm64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 3e6bc4a6cbae2b4b672833b85a9df2d798209e2d64556bd8a7e04fdaeb8ef906 |
|
MD5 | 9d1caffa842c4006e8db7e0fc4d45824 |
|
BLAKE2b-256 | debb7a3fc09f192cc3ca1bdaa036ef34cc19f43e64ca1fe61b966e5906abb100 |
nifty_ls-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 47dc83f969f7438a93a459c601bdd6c80b23acd3ae985b45605bbbfe452a0ed7 |
|
MD5 | 98b862a6a5f1fb779573abe91e910ea5 |
|
BLAKE2b-256 | 2727220362fccb18140f7ee79148dbc26f6959e932d8b2b86d6d7148d0a34d6c |
nifty_ls-1.0.1-cp311-cp311-macosx_12_0_x86_64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 716013f4bfae9ecdac099b8cfb0725756f43e7d36bc38d4966807228416c25a7 |
|
MD5 | 1e5aa2ca077690fe90652b5c5782f554 |
|
BLAKE2b-256 | 73c40bca5da57ab4fb42bf8fe43824e8e82bc7bd63ce6a992e18ef3405e8f88b |
nifty_ls-1.0.1-cp311-cp311-macosx_12_0_arm64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | cd029abde4c1d0e145988a766fa0cf7643b81dfbec8e32acf62e3db6a278e30e |
|
MD5 | 6ef75bd2b2ec81975d1f19f3714229df |
|
BLAKE2b-256 | 95459112b786d061dd54d5e3d53b35b707151483ee6cd54f51abf181c7439ee5 |
nifty_ls-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 690131da04a1648743a42ea0139d6f570c0b2a1e44b16f52ab73972d9db6241c |
|
MD5 | ea304a16286af4c23197578711be73d4 |
|
BLAKE2b-256 | 134e888dcc03ea9e255f7de1f99b7e6be44bf04c3dcd8aca2f854603f68a488f |
nifty_ls-1.0.1-cp310-cp310-macosx_12_0_x86_64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 5c2806c3cb1387a798e0bdc300f6597ff1a9fb5753a0c59961ef89e84dc701fe |
|
MD5 | ef8c3e14625666c50f355a775949a45a |
|
BLAKE2b-256 | 3362f259496b65132730c7533e3c767a80ba9c966c54fcb46b37a1e2f512b37d |
nifty_ls-1.0.1-cp310-cp310-macosx_12_0_arm64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ca37cbd945b4f8aa77f50a99666496895d405d255a0d1ec79fa1520923143cea |
|
MD5 | 3fa7b77639c5627f364a0bb46b27f96b |
|
BLAKE2b-256 | 234b56eba6124ce012b4bb365073d7f403f9a78ad348af14766a468afc1a0dc1 |
nifty_ls-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 8eccb2390dd9f66f909edd1b8c8b96237a12b230eca212ed8b52a7501a3e304f |
|
MD5 | ef436cf7245fa321c9ead85c68af5903 |
|
BLAKE2b-256 | 586339c6f8f3e9415c31aebd9480786c3d8b694bbfaf78f4e4803a78de705735 |
哈希值 用于 nifty_ls-1.0.1-cp39-cp39-macosx_12_0_x86_64.whl
算法 | 哈希摘要 | |
---|---|---|
SHA256 | e877e39b817a3b5ac093b8dd2933ffd5922ce3868dbd0772b07d82db5bc53993 |
|
MD5 | dd3098538c46af35337946bb8a964ff5 |
|
BLAKE2b-256 | e8570cd0604ac2779de36770101ea64a3d9be1c57b9d18768b67ab45aeaa3434 |
哈希值 用于 nifty_ls-1.0.1-cp39-cp39-macosx_12_0_arm64.whl
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 3189d12405558b84e740cd5cc384797440c56549812cc17aa20cd364f7340d48 |
|
MD5 | 7ae4862a81be2ccc2ce4047f313e3789 |
|
BLAKE2b-256 | 0aae40d7520328fd36907cde135606c55f3486a50b6906f003b04036bee1a966 |
哈希值 用于 nifty_ls-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 36cc39c53b47e92e6dc1add64ad09ade14c310f9efa7f7a7d7109f2dea7a419a |
|
MD5 | 32c1313d99532d095ff1b0c28b2b4b69 |
|
BLAKE2b-256 | 5ebe1236682c020006b906a0c54eafd7de3e43dfc814f3d3245660f200f2ac26 |
哈希值 用于 nifty_ls-1.0.1-cp38-cp38-macosx_12_0_x86_64.whl
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 1030c89d6ac5f99844c5bd3eca7fcb0a99bcbeea7a6e7a405315cd480eacd88c |
|
MD5 | e939c59bf4180b19fa04cdcd06db4f99 |
|
BLAKE2b-256 | aed526774f6dacdc423e1a2b4ea10c0538eaecec340b1090908769ca471f81e4 |
哈希值 用于 nifty_ls-1.0.1-cp38-cp38-macosx_12_0_arm64.whl
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ac465e418b40d7c633937320612001bbd6cced912f47affe80a06dee3e92ac03 |
|
MD5 | 655ee0e934f0fb767b975c6dfedfbe4b |
|
BLAKE2b-256 | 5fba385cb0af6d33ca375620686f7bd0944d84447fb521e37af02e2eb44d5051 |