簡體   English   中英

更有效的循環方式?

[英]More efficient way to loop?

我從一個更大的腳本中獲得了一小段代碼。 我發現當調用函數t_area時,它負責大部分運行時間。 我自己測試了這個功能,它並不慢,它需要花費很多時間,因為它必須運行的次數我相信。 以下是調用函數的代碼:

tri_area = np.zeros((numx,numy),dtype=float)
for jj in range(0,numy-1):
    for ii in range(0,numx-1):
      xp = x[ii,jj]
      yp = y[ii,jj]
      zp = surface[ii,jj]
      ap = np.array((xp,yp,zp))

      xp = xp+dx
      zp = surface[ii+1,jj]
      bp = np.array((xp,yp,zp))

      yp = yp+dx
      zp = surface[ii+1,jj+1]
      dp = np.array((xp,yp,zp))

      xp = xp-dx
      zp = surface[ii,jj+1]
      cp = np.array((xp,yp,zp))

      tri_area[ii,jj] = t_area(ap,bp,cp,dp)

這里使用的數組大小為216 x 217xy的值也是如此。 我對python編碼很新,我過去使用過MATLAB。 所以我的問題是,有沒有辦法繞過這兩個for循環,或者更有效的方式來運行這段代碼? 尋找任何幫助加快這一點! 謝謝!

編輯:

感謝大家的幫助,這已經清除了很多混亂。 我被問到循環中使用的函數t_area,下面的代碼如下:

def t_area(a,b,c,d):
ab=b-a
ac=c-a
tri_area_a = 0.5*linalg.norm(np.cross(ab,ac))

db=b-d
dc=c-d
tri_area_d = 0.5*linalg.norm(np.cross(db,dc))

ba=a-b
bd=d-b
tri_area_b = 0.5*linalg.norm(np.cross(ba,bd))

ca=a-c
cd=d-c
tri_area_c = 0.5*linalg.norm(np.cross(ca,cd))

av_area = (tri_area_a + tri_area_b + tri_area_c + tri_area_d)*0.5
return(av_area)

對於令人困惑的記譜法感到抱歉,當時它有意義,現在回想起來我可能會改變它。 謝謝!

在我們開始之前的一個警告。 range(0, numy-1) ,等於range(numy-1) ,產生從0到numy-2的數字,不包括numy-1。 那是因為你有從0到numy-2的numy-1值。 雖然MATLAB具有基於1的索引,但Python基於0,因此在轉換中對索引進行一些小心。 考慮到你有tri_area = np.zeros((numx, numy), dtype=float)tri_area[ii,jj]永遠不會以你設置循環的方式訪問最后一行或列。 因此,我懷疑正確的意圖是寫range(numy)

由於功能t_area()是可矢量化的,因此您可以完全取消循環。 矢量化意味着numpy通過處理引擎蓋下的循環來同時對整個陣列應用一些操作,在那里它們將更快。

首先,我們在(m,n,3)數組中堆疊每個(i,j)元素的所有ap ,其中(m,n)是x的大小。 如果我們取兩個(m,n,3)數組的叉積,默認情況下操作將應用於最后一個軸。 這意味着np.cross(a, b)將對每個元素(i,j)采用a[i,j]b[i,j]中的3個數的叉積 類似地, np.linalg.norm(a, axis=2)將為每個元素(i,j)計算a[i,j] 3個數的范數 這也將有效地減少我們的數組大小(m,n)。 這里有點謹慎,因為我們需要明確說明我們希望在第二軸上完成此操作。

請注意,在以下示例中,我的索引關系可能與您的索引關系不對應。 使這項工作的最低限度是surfacexy有一個額外的行和列。

import numpy as np

def _t_area(a, b, c):
    ab = b - a
    ac = c - a
    return 0.5 * np.linalg.norm(np.cross(ab, ac), axis=2)

def t_area(x, y, surface, dx):
    a = np.zeros((x.shape[0], y.shape[0], 3), dtype=float)
    b = np.zeros_like(a)
    c = np.zeros_like(a)
    d = np.zeros_like(a)

    a[...,0] = x
    a[...,1] = y
    a[...,2] = surface[:-1,:-1]

    b[...,0] = x + dx
    b[...,1] = y
    b[...,2] = surface[1:,:-1]

    c[...,0] = x
    c[...,1] = y + dx
    c[...,2] = surface[:-1,1:]

    d[...,0] = bp[...,0]
    d[...,1] = cp[...,1]
    d[...,2] = surface[1:,1:]

    # are you sure you didn't mean 0.25???
    return 0.5 * (_t_area(a, b, c) + _t_area(d, b, c) + _t_area(b, a, d) + _t_area(c, a, d))

nx, ny = 250, 250

dx = np.random.random()
x = np.random.random((nx, ny))
y = np.random.random((nx, ny))
surface = np.random.random((nx+1, ny+1))

tri_area = t_area(x, y, surface, dx)

此示例中的x支持索引0-249,而surface 0-250。 surface[:-1]surface[0:-1]的簡寫,將返回從0開始直到最后一行的所有行,但不包括它。 -1提供相同的功能並在MATLAB中end 因此, surface[:-1]將返回索引0-249的行。 類似地, surface[1:]將返回索引1-250的行,這與surface[ii+1]


注意 :在知道t_area()可以完全矢量化之前,我已經寫過這一節。 因此,雖然這個答案的目的已經過時了,但我將把它留作遺產來表明如果函數不是可矢量化的話可以進行哪些優化。

不要為每個昂貴的元素調用函數,而應該將它傳遞給xy,surfacedx並在內部迭代。 這意味着只有一個函數調用和更少的開銷。

此外,您不應該為每個循環創建apbpcpdp的數組,這又會增加開銷。 一旦在循環外部分配它們,只需更新它們的值。

最后一個改變應該是循環的順序。 默認情況下,Numpy數組是行主要的(而MATLAB是列專業),因此ii作為外部循環表現得更好。 您不會注意到您的大小數組的差異,但是,嘿,為什么不呢?

總的來說,修改后的功能應如下所示。

def t_area(x, y, surface, dx):
    # I assume numx == x.shape[0]. If not, pass it as an extra argument.
    tri_area = np.zeros(x.shape, dtype=float)

    ap = np.zeros((3,), dtype=float)
    bp = np.zeros_like(ap)
    cp = np.zeros_like(ap)
    dp = np.zeros_like(ap)

    for ii in range(x.shape[0]-1): # do you really want range(numx-1) or just range(numx)?
        for jj in range(x.shape[1]-1):
            xp = x[ii,jj]
            yp = y[ii,jj]
            zp = surface[ii,jj]
            ap[:] = (xp, yp, zp)

            # get `bp`, `cp` and `dp` in a similar manner and compute `tri_area[ii,jj]`

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM