Recurrencia con numpy

¿Hay alguna manera de hacer recurrencia sin usar for’s in numpy?

Usando np.add out palabra clave, haz el truco con dtype="int"

 import numpy as np N = 100 fib = np.zeros(N, dtype=np.int) fib[:2] = 1. np.add(fib[:-2], fib[1:-1], out=fib[2:]) print(fib[:10]) 

salida: [ 1 1 2 3 5 8 13 21 34 55]

Sin embargo, si dtype se cambia a np.float

 import numpy as np N = 100 fib = np.zeros(N, dtype=np.float) fib[:2] = 1. np.add(fib[:-2], fib[1:-1], out=fib[2:]) print(fib[:10]) 

salida: [ 1. 1. 2. 1. 3. 1. 4. 1. 5. 1.]

¿Podría alguien decirme por qué? o alguna otra forma de hacer recursion?

Aquí hay un método razonablemente eficiente usando un filtro scipy:

 import numpy as np from scipy import signal def scipy_fib(n): x = np.zeros(n, dtype=np.uint8) x[0] = 1 sos = signal.tf2sos([1], [1, -1, -1]) return signal.sosfilt(sos, x) 

(Tenga en cuenta que no podemos usar lfilter porque, en términos de procesamiento de la señal, este es un filtro inestable que hace que el intérprete de Python se bloquee, por lo que tenemos que convertirlo al formato de secciones de segundo orden).

El problema con el enfoque de filtrado es que no estoy seguro de si se puede generalizar al problema real de resolver una EDO.

Otra solución es simplemente escribir el bucle en Python y acelerarlo con un comstackdor JIT :

 import numba as nb @nb.jit(nopython=True) def numba_fib(n): y = np.empty(n) y[:2] = 1 for i in range(2, n): y[i] = y[i-1] + y[i-2] return y 

La ventaja del enfoque JIT es que puede implementar todo tipo de cosas sofisticadas, pero funciona mejor si se adhiere a bucles y twigs simples sin tener que llamar a ninguna función de Python (sin JIT).

Resultados de tiempo:

 # Baseline without JIT: %timeit numba_fib(10000) # 100 loops, best of 3: 5.47 ms per loop %timeit scipy_fib(10000) # 1000 loops, best of 3: 719 µs per loop %timeit numba_fib(10000) # 10000 loops, best of 3: 33.8 µs per loop # For fun, this is how Paul Panzer's solve_banded approach compares: %timeit banded_fib(10000) # 1000 loops, best of 3: 1.33 ms per loop 

El enfoque del filtro scipy es más rápido que el bucle de Python puro pero más lento que el bucle JITted. Supongo que la función de filtro es relativamente genérica y hace cosas que no necesitamos aquí, mientras que el bucle JITted se comstack en un bucle muy pequeño.

scipy.linalg.solve_banded solución no muy eficiente pero divertida sería abusar de scipy.linalg.solve_banded como tal

 import numpy as np from scipy import linalg N = 50 a = np.zeros((N,)) + np.array([[1, -1, -1]]).T b = np.zeros((N,)) b[0] = 1 linalg.solve_banded((2, 0), a, b) # array([ 1.00000000e+00, 1.00000000e+00, 2.00000000e+00, # 3.00000000e+00, 5.00000000e+00, 8.00000000e+00, # 1.30000000e+01, 2.10000000e+01, 3.40000000e+01, # 5.50000000e+01, 8.90000000e+01, 1.44000000e+02, # 2.33000000e+02, 3.77000000e+02, 6.10000000e+02, # 9.87000000e+02, 1.59700000e+03, 2.58400000e+03, # 4.18100000e+03, 6.76500000e+03, 1.09460000e+04, # 1.77110000e+04, 2.86570000e+04, 4.63680000e+04, # 7.50250000e+04, 1.21393000e+05, 1.96418000e+05, # 3.17811000e+05, 5.14229000e+05, 8.32040000e+05, # 1.34626900e+06, 2.17830900e+06, 3.52457800e+06, # 5.70288700e+06, 9.22746500e+06, 1.49303520e+07, # 2.41578170e+07, 3.90881690e+07, 6.32459860e+07, # 1.02334155e+08, 1.65580141e+08, 2.67914296e+08, # 4.33494437e+08, 7.01408733e+08, 1.13490317e+09, # 1.83631190e+09, 2.97121507e+09, 4.80752698e+09, # 7.77874205e+09, 1.25862690e+10]) 

Cómo funciona esto es que escribimos F_0, F_1, F_2 … como un vector F y las identidades -F_ {i-1} -F_i + F {i + 1} = 0 como una matriz que está bien agrupada y luego resolvemos para F. Tenga en cuenta que esto se puede adaptar a otras recurrencias similares.

Dado que las recurrencias lineales tienen soluciones analíticas ( aquí para Fibonacci), otro método rápido de escisión es:

 def fib_scipy2(N): r5=math.sqrt(5) phi=(1+r5)/2 a= (-phi*ones(N)).cumprod() return (np.abs(a)-1/a)/r5 

Carreras :

 In [413]: fib_scipy2(8) Out[413]: array([ 1., 1., 2., 3., 5., 8., 13., 21.]) In [414]: %timeit fib(10**4) 103 µs ± 888 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) 

Puedes ajustarlo a cualquier ecuación lineal.

Su add funciona para este cálculo, pero debe aplicarse repetidamente, de modo que los valores distintos de cero se propaguen. No veo cómo se generó su cálculo [ 1. 1. 2. 1. 3. 1. 4. 1. 5. 1.] .

 In [619]: fib=np.zeros(10,int);fib[:2]=1 In [620]: for _ in range(10): ...: np.add(fib[:-2], fib[1:-1], out=fib[2:]) ...: print(fib) ...: [1 1 2 1 0 0 0 0 0 0] # ** [1 1 2 3 3 1 0 0 0 0] [1 1 2 3 5 6 4 1 0 0] [ 1 1 2 3 5 8 11 10 5 1] [ 1 1 2 3 5 8 13 19 21 15] ... 

(Editar: np.add cuenta que el primer np.add actúa como si np.add completamente almacenado en búfer. Compare el resultado en ** con sus dos matrices de objetos y flotantes. Estoy usando la versión 1.13.1 en Py3).

O para resaltar los buenos valores en cada etapa.

 In [623]: fib=np.zeros(20,int);fib[:2]=1 In [624]: for i in range(10): ...: np.add(fib[:-2], fib[1:-1], out=fib[2:]) ...: print(fib[:(i+2)]) ...: [1 1] [1 1 2] [1 1 2 3] [1 1 2 3 5] [1 1 2 3 5 8] [ 1 1 2 3 5 8 13] [ 1 1 2 3 5 8 13 21] [ 1 1 2 3 5 8 13 21 34] [ 1 1 2 3 5 8 13 21 34 55] [ 1 1 2 3 5 8 13 21 34 55 89] 

fib[2:] = fib[:-2]+fib[1:-1] hace lo mismo.

Como se explica en la documentación de ufunc.at , las operaciones como np.add utilizan el almacenamiento en búfer, incluso con el parámetro out . Así que, si bien ellos intervienen en el código de nivel C , no se acumulan; es decir, los resultados del ith paso no se utilizan en el paso i+1 .

add.at se puede utilizar para realizar a[i] += b búfer a[i] += b . Eso es útil cuando los índices contienen duplicados. pero no permite la retroalimentación de los valores de a a b . Así que no es útil aquí.

También es un add.accumulate (también np.cumsum como np.cumsum ) que puede ser útil para ciertas definiciones iterativas. Pero es difícil de aplicar en casos generales.

cumprod (multiplicar acumular) puede trabajar con el enfoque qmatrix

La función matrix_power de Numpy da resultados incorrectos para grandes exponentes

Use np.matrix para definir qmatrix , de modo que * multiplicar signifique el producto de la matriz (a diferencia de elementwise):

 In [647]: qmatrix = numpy.matrix([[1, 1], [1, 0]]) 

Rellene una matriz de objetos con copias (punteros en realidad) a esta matriz.

 In [648]: M = np.empty(10, object) In [649]: M[:] = [qmatrix for _ in range(10)] 

Aplique cumprod y extraiga un elemento de cada matriz.

 In [650]: m1=np.cumprod(M) In [651]: m1 Out[651]: array([matrix([[1, 1], [1, 0]]), matrix([[2, 1], [1, 1]]), matrix([[3, 2], [2, 1]]), matrix([[5, 3], [3, 2]]), matrix([[8, 5], [5, 3]]), matrix([[13, 8], [ 8, 5]]), matrix([[21, 13], [13, 8]]), matrix([[34, 21], [21, 13]]), matrix([[55, 34], [34, 21]]), matrix([[89, 55], [55, 34]])], dtype=object) In [652]: [x[0,1] for x in m1] Out[652]: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 

La respuesta vinculada utiliza numpy.linalg.matrix_power que también hace productos matriciales repetidos. Para una sola potencia, matrix_power es más rápido, pero para una amplia gama de poderes, el cumprod es más rápido.