2012-06-19 2 views
10

Ich habe ein Python-Skript geschrieben, um den Abstand zwischen zwei Punkten im 3D-Raum unter Berücksichtigung periodischer Randbedingungen zu berechnen. Das Problem ist, dass ich diese Berechnung für viele, viele Punkte machen muss und die Berechnung ist ziemlich langsam. Hier ist meine Funktion.Optimierung der Python-Abstandsberechnung unter Berücksichtigung periodischer Randbedingungen

def PBCdist(coord1,coord2,UC): 
    dx = coord1[0] - coord2[0] 
    if (abs(dx) > UC[0]*0.5): 
     dx = UC[0] - dx 
    dy = coord1[1] - coord2[1] 
    if (abs(dy) > UC[1]*0.5): 
     dy = UC[1] - dy 
    dz = coord1[2] - coord2[2] 
    if (abs(dz) > UC[2]*0.5): 
     dz = UC[2] - dz 
    dist = np.sqrt(dx**2 + dy**2 + dz**2) 
    return dist 

ich die Funktion rufen Sie dann als so

for i, coord2 in enumerate(coordlist): 
    if (PBCdist(coord1,coord2,UC) < radius): 
     do something with i 

Vor kurzem las ich, dass ich stark durch die Verwendung Liste Verständnis Leistung erhöhen kann. Die folgenden Arbeiten für den Nicht-PBC Fall, aber nicht für den PBC Fall

coord_indices = [i for i, y in enumerate([np.sqrt(np.sum((coord2-coord1)**2)) for coord2 in coordlist]) if y < radius] 
for i in coord_indices: 
    do something 

Gibt es eine Möglichkeit die äquivalent dies für den PBC Fall zu tun? Gibt es eine Alternative, die besser funktioniert?

+3

Sie verwenden NumPy, daher sollten Sie die Schleife vektorisieren, um die Leistung zu verbessern. Was genau ist die Struktur von "Koordinator"? Es sollte ein zweidimensionales NumPy-Array sein, um die Schleife mit NumPy ufuncs optimieren zu können. –

+0

coordlist ist ein numpy Array mit Form ca. (5711,3). Die Koordinatenliste selbst stammt aus einer größeren Liste, so dass ich die Koordinatenkoordinate 20.000 Mal durchlaufen habe und diese Liste der Koordinatenliste etwa 50 mal durchlaufen wird ... Sie erhalten das Bild. – johnjax

+0

Ich habe die Vektorisierungsfunktion in NumPy nachgeschlagen. Die Dokumentation sagt: ["Die Vectorize-Funktion wird hauptsächlich aus Gründen der Zweckmäßigkeit, nicht der Leistung bereitgestellt. Die Implementierung ist im Wesentlichen eine for-Schleife."] (Http://docs.scipy.org/doc/numpy/reference/generated/numpy. vectorize.html) – johnjax

Antwort

12

Sie sollten schreiben distance() funktionieren so, dass Sie die Schleife über die 5711 Punkte vektorisieren können. Die folgende Implementierung übernimmt eine Reihe von Punkten, entweder als die x0 oder x1 Parameter:

def distance(x0, x1, dimensions): 
    delta = numpy.abs(x0 - x1) 
    delta = numpy.where(delta > 0.5 * dimensions, delta - dimensions, delta) 
    return numpy.sqrt((delta ** 2).sum(axis=-1)) 

Beispiel:

>>> dimensions = numpy.array([3.0, 4.0, 5.0]) 
>>> points = numpy.array([[2.7, 1.5, 4.3], [1.2, 0.3, 4.2]]) 
>>> distance(points, [1.5, 2.0, 2.5], dimensions) 
array([ 2.22036033, 2.42280829]) 

Das Ergebnis ist die Anordnung der Abstände zwischen den Punkten als zweiten Parameter zu distance() und jedem weitergegeben zeigen Sie in points.

+0

Dies führte zu einer etwa 5-fachen Beschleunigung in meinem Code. Vielen Dank! Für diejenigen, die nach einer Alternative suchen, hat sich auch die Antwort von Lazyr bewährt. – johnjax

+0

Es macht keinen Unterschied, nachdem ich die Norm genommen habe, aber ich mag es, das richtige Vorzeichen für Delta zu bekommen, indem ich die dritte Zeile durch 'delta = numpy.where (", delta - dimensions, ") ersetzt habe. Beachten Sie auch, dass "np.hypot" einen Überlauf besser vermeidet als sqrt (sum (x ** 2)) – Patrick

+0

@Patrick 'numpy.hypot()' funktioniert nur für zwei Dimensionen, während das OP Code benötigt, der für drei arbeitet. dimensionale Punkte. Und in Bezug auf das Zeichen von "Delta", nun, wenn Sie sich interessieren, können Sie den Beitrag bearbeiten. :) –

0

Werfen Sie einen Blick auf Ian Ozsvalds high performance python tutorial. Es enthält viele Vorschläge, wo Sie als nächstes hingehen können.

einschließlich:

  • Vektorisierung
  • Cython
  • PyPy
  • numexpr
  • pycuda
  • multiprocesing
  • parallel Python
4
import numpy as np 

bounds = np.array([10, 10, 10]) 
a = np.array([[0, 3, 9], [1, 1, 1]]) 
b = np.array([[2, 9, 1], [5, 6, 7]]) 

min_dists = np.min(np.dstack(((a - b) % bounds, (b - a) % bounds)), axis = 2) 
dists = np.sqrt(np.sum(min_dists ** 2, axis = 1)) 

Hier a und b sind Listen von Vektoren Sie wollen den Abstand berechnen zwischen und bounds die Grenzen des Raums sind (so hier alle drei Dimensionen gehen von 0 bis 10 und dann wickeln). Er berechnet die Abstände zwischen a[0] und b[0], a[1] und b[1] und so weiter.

Ich bin sicher, numpy Experten besser machen können, aber das wird wahrscheinlich eine Größenordnung schneller sein als das, was Sie tun, die meiste Arbeit, da nun in C erfolgt

+0

Ich habe diesen Ansatz auch versucht. Es führte auch zu einer 5-fachen Verbesserung des Codes. Leider kann ich nur 1 Antwort als die richtige überprüfen:/ – johnjax

+0

@johnjax Für was es wert ist, hätte ich Sven Marnachs Antwort auch akzeptiert, wenn ich in Ihren Schuhen gewesen wäre. Es ist direkter anwendbar als meins. –

0

Ich habe festgestellt, dass meshgrid sehr nützlich ist, um Entfernungen zu generieren.Zum Beispiel:

import numpy as np 
row_diff, col_diff = np.meshgrid(range(7), range(8)) 
radius_squared = (row_diff - x_coord)**2 + (col_diff - y_coord)**2 

Ich habe jetzt einen Array (radius_squared), wobei jeder Eintrag das Quadrat der Entfernung von der Feldposition [x_coord, y_coord] angibt.

das Array circularize, ich folgendes tun:

row_diff, col_diff = np.meshgrid(range(7), range(8)) 
row_diff = np.abs(row_diff - x_coord) 
row_circ_idx = np.where(row_diff > row_diff.shape[1]/2) 
row_diff[row_circ_idx] = (row_diff[row_circ_idx] - 
         2 * (row_circ_idx + x_coord) + 
         row_diff.shape[1]) 
row_diff = np.abs(row_diff) 
col_diff = np.abs(col_diff - y_coord) 
col_circ_idx = np.where(col_diff > col_diff.shape[0]/2) 
col_diff[row_circ_idx] = (row_diff[col_circ_idx] - 
         2 * (col_circ_idx + y_coord) + 
         col_diff.shape[0]) 
col_diff = np.abs(row_diff) 
circular_radius_squared = (row_diff - x_coord)**2 + (col_diff - y_coord)**2 

Ich habe jetzt alle Array Entfernungen mit Vektor-Mathematik zirkularisiert.