Быстрая numpy
оценка требует применения встроенных скомпилированных операторов / функций для целых массивов. Любая итерация уровня python замедляет вас, как и оценка (общих) функций Python в скалярах. Быстрые вещи в основном ограничены операторами (например, **
) и ufunc
(np.sin
, et c).
Ваша сгенерированная sympy
функция иллюстрирует это:
В сеансе isympy
:
In [65]: M = get_sympy(3)
с использованием самоанализа кода ipython
:
In [66]: M??
Signature: M(x)
Docstring:
Created with lambdify. Signature:
func(x)
Expression:
Matrix([[x**2, x, x], [x**3, x**2, x**3], [x**4, x, x**2]])
Source code:
def _lambdifygenerated(x):
return (array([[x**2, x, x], [x**3, x**2, x**3], [x**4, x, x**2]]))
Imported modules:
Source:
def _lambdifygenerated(x):
return (array([[x**2, x, x], [x**3, x**2, x**3], [x**4, x, x**2]]))
File: /<lambdifygenerated-8>
Type: function
Так что это функция в x
с использованием операций numpy
, **
оператор и создание массива. Точно так же, как если бы вы его набрали. sympy
создает это с помощью лексических подстановок в своем символьном c коде, так что вы можете сказать, что он «печатает».
Он может работать на скаляре
In [67]: M(3)
Out[67]:
array([[ 9, 3, 3],
[27, 9, 27],
[81, 3, 9]])
в массиве, здесь получается результат (3,3,3):
In [68]: M(np.arange(1,4))
Out[68]:
array([[[ 1, 4, 9],
[ 1, 2, 3],
[ 1, 2, 3]],
[[ 1, 8, 27],
[ 1, 4, 9],
[ 1, 8, 27]],
[[ 1, 16, 81],
[ 1, 2, 3],
[ 1, 4, 9]]])
Я ожидаю, что легко написать выражение sympy
, которое при переводе , не может принимать аргументы массива. if
тесты общеизвестно трудны для записи в форме, совместимой с массивами, поскольку выражение Python if
работает только для скалярных логических значений.
Ваш get_python
не будет принимать массив x
, прежде всего потому, что
output = np.zeros((N,N))
имеет фиксированный размер; использование np.zeros((N,N)+x.shape), x.dtype)
может обойти это.
В любом случае, оно будет медленным из-за итерации уровня python при каждом вызове.
===
Было бы быстрее, если бы вы попытались назначить группы элементов. Например, в этом случае:
In [76]: output = np.zeros((3,3),int)
In [77]: output[:] = 3
In [78]: output[:,0]=3**4
In [79]: output[1,:]=3**3
In [80]: output[np.arange(3),np.arange(3)]=3**2
In [81]: output
Out[81]:
array([[ 9, 3, 3],
[27, 9, 27],
[81, 3, 9]])
===
frompyfunc
- удобный инструмент для подобных случаев. В некоторых случаях он предлагает увеличение скорости в 2 раза по сравнению с прямой итерацией. Но даже без этого он может сделать код более кратким.
Например, быстрое описание вашего примера:
In [82]: def foo(x,i,j):
...: if i==j: return x**2
...: if i==1: return x**3
...: if j==0: return x**4
...: return x
In [83]: f = np.frompyfunc(foo, 3, 1)
In [84]: f(3,np.arange(3)[:,None], np.arange(3))
Out[84]:
array([[9, 3, 3],
[27, 9, 27],
[81, 3, 9]], dtype=object)
и для примера Out[68]
:
In [98]: f(np.arange(1,4),np.arange(3)[:,None,None], np.arange(3)[:,None]).shape
Out[98]: (3, 3, 3)
In [99]: timeit f(np.arange(1,4),np.arange(3)[:,None,None], np.arange(3)[:,None]).shape
23 µs ± 471 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [100]: timeit M(np.arange(1,4))
21.7 µs ± 440 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Оценивается по скаляру x
, мой f
примерно такой же скорости, как ваш get_python
.
In [115]: MM = get_sympy(30)
In [116]: timeit MM(3)
109 µs ± 112 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [117]: timeit get_python(3,30)
241 µs ± 2.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [118]: timeit f(3,np.arange(30)[:,None], np.arange(30)).astype(int)
254 µs ± 1.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)