2011-05-26 11 views
9

Ayuda a que mi código sea más rápido: Mi código python necesita generar un enrejado 2D de puntos que caen dentro de un rectángulo delimitador. Arreglé un código (que se muestra a continuación) que genera este enrejado. Sin embargo, esta función se llama muchas veces y se ha convertido en un serio cuello de botella en mi aplicación.Genere de manera eficiente un enrejado de puntos en python

Estoy seguro de que hay una forma más rápida de hacerlo, probablemente con matrices numpy en lugar de listas. ¿Alguna sugerencia para una forma más rápida y más elegante de hacer esto?

Descripción de la función: Tengo dos vectores 2D, v1 y v2. Estos vectores define a lattice. En mi caso, mis vectores definen una retícula que es casi, pero no del todo, hexagonal. Quiero generar el conjunto de todos los puntos 2D en este enrejado que están en algún rectángulo delimitador. En mi caso, una de las esquinas del rectángulo está en (0, 0), y las otras esquinas están en coordenadas positivas.

Ejemplo: Si la esquina más alejada de mi rectángulo delimitador estaba en (3, 3), y mis vectores de la red fueron:

v1 = (1.2, 0.1) 
v2 = (0.2, 1.1) 

me gustaría que mi función para devolver los puntos:

(1.2, 0.1) #v1 
(2.4, 0.2) #2*v1 
(0.2, 1.1) #v2 
(0.4, 2.2) #2*v2 
(1.4, 1.2) #v1 + v2 
(2.6, 1.3) #2*v1 + v2 
(1.6, 2.3) #v1 + 2*v2 
(2.8, 2.4) #2*v1 + 2*v2 

No me preocupan los casos extremos; no importa si la función retorna (0, 0), por ejemplo.

La forma más lenta Actualmente lo estoy haciendo:

import numpy, pylab 

def generate_lattice(#Help me speed up this function, please! 
    image_shape, lattice_vectors, center_pix='image', edge_buffer=2): 

    ##Preprocessing. Not much of a bottleneck: 
    if center_pix == 'image': 
     center_pix = numpy.array(image_shape) // 2 
    else: ##Express the center pixel in terms of the lattice vectors 
     center_pix = numpy.array(center_pix) - (numpy.array(image_shape) // 2) 
     lattice_components = numpy.linalg.solve(
      numpy.vstack(lattice_vectors[:2]).T, 
      center_pix) 
     lattice_components -= lattice_components // 1 
     center_pix = (lattice_vectors[0] * lattice_components[0] + 
         lattice_vectors[1] * lattice_components[1] + 
         numpy.array(image_shape)//2) 
    num_vectors = int(##Estimate how many lattice points we need 
     max(image_shape)/numpy.sqrt(lattice_vectors[0]**2).sum()) 
    lattice_points = [] 
    lower_bounds = numpy.array((edge_buffer, edge_buffer)) 
    upper_bounds = numpy.array(image_shape) - edge_buffer 

    ##SLOW LOOP HERE. 'num_vectors' is often quite large. 
    for i in range(-num_vectors, num_vectors): 
     for j in range(-num_vectors, num_vectors): 
      lp = i * lattice_vectors[0] + j * lattice_vectors[1] + center_pix 
      if all(lower_bounds < lp) and all(lp < upper_bounds): 
       lattice_points.append(lp) 
    return lattice_points 


##Test the function and display the output. 
##No optimization needed past this point. 
lattice_vectors = [ 
    numpy.array([-40., -1.]), 
    numpy.array([ 18., -37.])] 
image_shape = (1000, 1000) 
spots = generate_lattice(image_shape, lattice_vectors) 

fig=pylab.figure() 
pylab.plot([p[1] for p in spots], [p[0] for p in spots], '.') 
pylab.axis('equal') 
fig.show() 
+0

¿Sería mejor para usted hacer 'lattice_components = numpy.modf (lattice_components) [0]'? (No pregunta por su pregunta, pero solo por curiosidad. ¿Sería significativamente más rápido/más lento?) – JAB

+0

No sabía nada sobre modf, buena sugerencia. Creo que la mayor parte del tiempo dedicado a esta función está dentro del bucle for anidado doble, pero haré un benchmark para asegurarme. – Andrew

+0

por favor, escriba un resumen de lo que hace esta función. –

Respuesta

4

Si desea vectorizar todo, genere un enrejado cuadrado y luego cizalla. Luego corta los bordes que se encuentran fuera de tu caja.

Esto es lo que se me ocurrió. Todavía hay muchas mejoras que podrían hacerse, pero esta es la idea básica.

def generate_lattice(image_shape, lattice_vectors) : 
    center_pix = numpy.array(image_shape) // 2 
    # Get the lower limit on the cell size. 
    dx_cell = max(abs(lattice_vectors[0][0]), abs(lattice_vectors[1][0])) 
    dy_cell = max(abs(lattice_vectors[0][1]), abs(lattice_vectors[1][1])) 
    # Get an over estimate of how many cells across and up. 
    nx = image_shape[0]//dx_cell 
    ny = image_shape[1]//dy_cell 
    # Generate a square lattice, with too many points. 
    # Here I generate a factor of 4 more points than I need, which ensures 
    # coverage for highly sheared lattices. If your lattice is not highly 
    # sheared, than you can generate fewer points. 
    x_sq = np.arange(-nx, nx, dtype=float) 
    y_sq = np.arange(-ny, nx, dtype=float) 
    x_sq.shape = x_sq.shape + (1,) 
    y_sq.shape = (1,) + y_sq.shape 
    # Now shear the whole thing using the lattice vectors 
    x_lattice = lattice_vectors[0][0]*x_sq + lattice_vectors[1][0]*y_sq 
    y_lattice = lattice_vectors[0][1]*x_sq + lattice_vectors[1][1]*y_sq 
    # Trim to fit in box. 
    mask = ((x_lattice < image_shape[0]/2.0) 
      & (x_lattice > -image_shape[0]/2.0)) 
    mask = mask & ((y_lattice < image_shape[1]/2.0) 
        & (y_lattice > -image_shape[1]/2.0)) 
    x_lattice = x_lattice[mask] 
    y_lattice = y_lattice[mask] 
    # Translate to the centre pix. 
    x_lattice += center_pix[0] 
    y_lattice += center_pix[1] 
    # Make output compatible with original version. 
    out = np.empty((len(x_lattice), 2), dtype=float) 
    out[:, 0] = y_lattice 
    out[:, 1] = x_lattice 
    return out 
+0

Tanto este código como Pavan dan el tipo de aceleración que estoy buscando. Estoy marcando este correcto, ya que Pavan lo describe como más general. Gracias, todos los que ayudaron! – Andrew

6

Desde lower_boundsupper_bounds y son sólo las matrices de 2 elementos, numpy podría no ser la mejor opción aquí. De tratar de reemplazar

if all(lower_bounds < lp) and all(lp < upper_bounds): 

con cosas básicas Python:

if lower1 < lp and lower2 < lp and lp < upper1 and lp < upper2: 

Según timeit, el segundo método es mucho más rápido:

>>> timeit.timeit('all(lower < lp)', 'import numpy\nlp=4\nlower = numpy.array((1,5))') 
3.7948939800262451 
>>> timeit.timeit('lower1 < 4 and lower2 < 4', 'lp = 4\nlower1, lower2 = 1,5') 
0.074192047119140625 

Desde mi experiencia, siempre y cuando se No es necesario que maneje datos n-diminsionales y si no necesita flotadores de doble precisión, generalmente es más rápido usar tipos de datos y construcciones Python básicos. ead of numpy, que es un poco sobrecargado en tales casos - eche un vistazo a this other question.


Otra mejora podría ser menor para calcular range(-num_vectors, num_vectors) sola vez y luego volver a utilizarlo. Además, es posible que desee utilizar un product iterator en lugar de un bucle anidado, aunque no creo que estos cambios tengan una influencia significativa en el rendimiento.

+0

Cambio de las desigualdades a los tipos de datos básicos de Python y cambiando el rango aceleró las cosas, pero menos de un factor de 2, tal como se calcula por 'timeit.timeit ( 'generate_lattice (image_shape, lattice_vectors)', 'de prueba generate_lattice importación, lattice_vectors , image_shape ', number = 10) '. Supongo que mucha de la carga está en el circuito en sí mismo. – Andrew

+0

@Andrew: Reproduje tu ejemplo en Python 2.6 (Ubuntu 10.10) y el enfoque básico de Python fue de aprox. 2.3 veces más rápido. Supongo que para una mayor velocidad necesitarás un cambio en el nivel del algoritmo, como sugieren en parte otras respuestas. –

3

Puede ser que pueda reemplazar los dos bucles for con esto.

i,j = numpy.mgrid[-num_vectors:num_vectors, -num_vectors:num_vectors] 
numel = num_vectors ** 2; 
i = i.reshape(numel, 1) 
j = j.reshape(numel, 1) 
lp = i * lattice_vectors[0] + j * lattice_vectors[1] + center_pix 
valid = numpy.all(lower_bounds < lp, 1) and numpy.all(lp < upper_bounds, 1) 
lattice_points = lp[valid] 

Puede haber algunos errores menores, pero se entiende la idea ..

EDITAR

I realizó una edición a la "numpy.all (lower_bounds ..)" para dar cuenta de la dimensión correcta.

+0

Esto es bastante rápido,> 10 veces más rápido. La línea 'válida' necesita una ligera corrección (reemplacé' y' con '*' para que funcione). Volveré a verificar la salida para asegurarme de que sea correcta. – Andrew

+0

Es posible que desee comprobar el código de Kiyo también. Es una versión más generalizada de lo que debería hacer: Vectorizar su código. –

1

que tiene más de un aumento de velocidad 2x reemplazando el cálculo de lp con adiciones repetidas en lugar de multiplicaciones. La optimización xrange parece ser inconsecuente (aunque probablemente no duela); las adiciones repetidas parecen ser más eficientes que las multiplicaciones. Combinar esto con las otras optimizaciones mencionadas anteriormente debería darle más aceleración. Pero, por supuesto, lo mejor que puede obtener es una aceleración por un factor constante, ya que el tamaño de su salida es cuadrático, al igual que su código original.

lv0, lv1 = lattice_vectors[0], lattice_vectors[1] 
lv0_i = -num_vectors * lv0 + center_pix 
lv1_orig = -num_vectors * lv1 
lv1_j = lv1_orig 
for i in xrange(-num_vectors, num_vectors): 
    for j in xrange(-num_vectors, num_vectors): 
     lp = lv0_i + lv1_j 
     if all(lower_bounds < lp) and all(lp < upper_bounds): 
      lattice_points.append(lp) 
     lv1_j += lv1 
    lv0_i += lv0 
    lv1_j = lv1_orig 

resultados del temporizador:

>>> t = Timer("generate_lattice(image_shape, lattice_vectors, orig=True)", "from __main__ import generate_lattice, lattice_vectors, image_shape") 
>>> print t.timeit(number=50) 
5.20121788979 
>>> t = Timer("generate_lattice(image_shape, lattice_vectors, orig=False)", "from __main__ import generate_lattice, lattice_vectors, image_shape") 
>>> print t.timeit(number=50) 
2.17463898659 
Cuestiones relacionadas