Время выполнения вашей функции будет масштабироваться линейно (до некоторой постоянной нагрузки) с количеством итераций.Таким образом, сокращение количества итераций является ключом к ускорению алгоритма.Хотя предварительное вычисление HARMONIC_10MIL
является разумной идеей, на самом деле это приводит к худшей точности, когда вы усекаете ряд;вычисление только части серии дает более высокую точность.
Код ниже представляет собой модифицированную версию кода, опубликованного выше (хотя с использованием cython
вместо numba
).
from libc.math cimport log, log1p
cimport cython
cdef:
float EULER_MAS = 0.577215664901532 # euler mascheroni constant
@cython.cdivision(True)
def gammaln(float z, int n=1000):
"""Compute log of gamma function for some real positive float z"""
cdef:
float out = -EULER_MAS*z - log(z)
int k
float t
for k in range(1, n):
t = z / k
out += t - log1p(t)
return out
Он может получить близкое приближение даже после 100 приближений, как показано на рисунке ниже.
При100 итераций, его время выполнения того же порядка, что и scipy.special.gammaln
:
%timeit special.gammaln(5)
# 932 ns ± 19 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit gammaln(5, 100)
# 1.25 µs ± 20.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Остается вопрос, конечно, сколько итераций использовать.Функция log1p(t)
может быть расширена как серия Тейлора для малых t
(что относится к пределу больших k
).В частности,
log1p(t) = t - t ** 2 / 2 + ...
такой, что для большого k
аргумент суммы становится
t - log1p(t) = t ** 2 / 2 + ...
Следовательно, аргумент суммы равен нулю до второго порядка вt
, что незначительно, если t
достаточно мало.Другими словами, число итераций должно быть не менее 1032 *, предпочтительно на порядок больше.
Однако я бы придерживался проверенной реализации scipy
.если это вообще возможно.