在具有多个输入的函数上使用 map() 来摆脱 for 循环

Using map() on a function with multiple inputs to get rid of for loops

上下文:

我有一个函数可以对我想尽可能高效地编写的多个数组进行 ups 采样(因为我必须 运行 它 370000 次)。

此函数接受多个输入,由 2 for loops 组成。为了 ups 对我的数组进行采样,我使用参数 k 遍历此函数,并且我想摆脱此循环(它位于函数之外)。我尝试混合使用 map()list-comprehension 来最小化我的计算时间,但我无法让它工作。

问题:

如何让我的 map() 部分代码正常工作(请参阅代码的最后一部分 )?有没有比 map() 更好的方法来摆脱 for loops ?

总结:

我写了一些示例代码,带有 map() 的部分不起作用,因为我想不出一种方法将 k 参数作为 list,还有一个输入.

谢谢!

ps:

import numpy as np
import time

#%% --- SETUP OF THE PROBLEM --- %%#

temperatures = np.random.rand(10,4,7)*100
precipitation = np.random.rand(10,4,7)
snow = np.random.rand(10,4,7)

# Flatten the arrays to make them iterable with map()
temperatures = temperatures.reshape(10,4*7)
precipitation = precipitation.reshape(10,4*7)
snow = snow.reshape(10,4*7)

# Array of altitudes to "adjust" the temperatures
alt = np.random.rand(4,7)*1000

# Flatten the array
alt = alt.reshape(4*7)

# Weight Matrix

w = np.random.rand(4*7, 1000, 1000)


#%% Function
def interpolate_and_get_results(temp, prec, Eprec, w, i, k):
    
    # Do some calculations
    factor1 = ((temperatures[i,k]-272.15) + (-alt[k] * -6/1000))
    factor2 = precipitation[i,k]
    factor3 = snow[i,k]
    
    # Iterate through every cell of the upsampled arrays
    for i in range(w.shape[1]):
        for j in range(w.shape[2]):
            val = w[k, i, j]
            temp[i, j] += factor1 * val
            prec[i, j] += factor2 * val
            Eprec[i, j] += factor3 * val


    

#%% --- Function call without loop simplification --- ##%
# Prepare a template array
dummy = np.zeros((w.shape[1], w.shape[2]))

# Initialize the global arrays to be filled
tempYEAR2 = np.zeros((9, dummy.shape[0], dummy.shape[1]))
precYEAR2 = np.zeros((9, dummy.shape[0], dummy.shape[1]))
EprecYEAR2 = np.zeros((9, dummy.shape[0], dummy.shape[1]))

ts = time.time()
for i in range(temperatures.shape[0]):
    
    # Create empty host arrays
    temp = dummy.copy()
    prec = dummy.copy()
    Eprec = dummy.copy()
    
    for k in range(w.shape[0]):
         interpolate_and_get_results(temp, prec, Eprec, w, i, k)

print('Time: ', (time.time()-ts))




#%% --- With Map (DOES NOT WORK)  --- %%#
del k

dummy = np.zeros((w.shape[1], w.shape[2]))

# Initialize the global arrays to be filled
tempYEAR2 = np.zeros((9, dummy.shape[0], dummy.shape[1]))
precYEAR2 = np.zeros((9, dummy.shape[0], dummy.shape[1]))
EprecYEAR2 = np.zeros((9, dummy.shape[0], dummy.shape[1]))

# Create a list k to be iterated through with the map() function
k = [k for k in range(0, temperatures.shape[1])]

for i in range(temperatures.shape[0]):
    
    # Create empty host arrays
    temp = dummy.copy()
    prec = dummy.copy()
    Eprec = dummy.copy()
    
    # Call the interpolate function with map() iterating through k
    map(interpolate_and_get_results(temp, prec, Eprec, w, i, k), k)

@Jérôme Richard 使用 numba 应用户 @ken 的请求添加的代码(在我的电脑上需要 48.81 秒到 运行):

import numpy as np
import multiprocessing as mp
import time



#%% ------ Create data ------ ###
temperatures = np.random.rand(10,4,7)*100
precipitation = np.random.rand(10,4,7)
snow = np.random.rand(10,4,7)

# Array of altitudes to "adjust" the temperatures
alt = np.random.rand(4,7)*1000


#%% ------ IDW Interpolation ------ ###

# We create a weight matrix that we use to upsample our temperatures, precipitations and snow matrices
# This part is not that important, it works well as it is

MX,MY = np.shape(temperatures[0])
N = 300

T = np.zeros([N*MX+1, N*MY+1])

# create NxM inverse distance weight matrices based on Gaussian interpolation

x = np.arange(0,N*MX+1)
y = np.arange(0,N*MY+1)
X,Y = np.meshgrid(x,y)

k = 0
w =  np.zeros([MX*MY,N*MX+1,N*MY+1])
for mx in range(MX):
    for my in range(MY):

        # Gaussian
        add_point = np.exp(-((mx*N-X.T)**2+(my*N-Y.T)**2)/N**2)
        w[k,:,:] += add_point
        k += 1

sum_weights = np.sum(w, axis=0)
for k in range(MX*MY):
    w[k,:,:] /= sum_weights






#%% --- Function --- %%#
# Code from Jérôme Richard: 

import numba as nb

# get_results + interpolator
@nb.njit('void(float64[:,::1], float64[:,::1], float64[:,::1], float64[:,:,::1], int_, int_, int_, int_)', parallel=True)
def interpolate_and_get_results(temp, prec, Eprec, w, i, k, mx, my):
    factor1 = ((temperatures[i,mx,my]-272.15) + (-alt[mx, my] * -6/1000))
    factor2 = precipitation[i,mx,my]
    factor3 = snow[i,mx,my]

    # Filling the
    for i in nb.prange(w.shape[1]):
        for j in range(w.shape[2]):
            val = w[k, i, j]
            temp[i, j] += factor1 * val
            prec[i, j] += factor2 * val
            Eprec[i, j] += factor3 * val




#%% --- Main Loop --- %%#



ts = time.time()
if __name__ == '__main__':

    dummy = np.zeros((w.shape[1], w.shape[2]))

    # Initialize the permanent arrays to be filled
    tempYEAR = np.zeros((9, dummy.shape[0], dummy.shape[1]))
    precYEAR = np.zeros((9, dummy.shape[0], dummy.shape[1]))
    EprecYEAR = np.zeros((9, dummy.shape[0], dummy.shape[1]))
    smbYEAR = np.zeros((9, dummy.shape[0], dummy.shape[1]))
    
    # Initialize semi-permanent array
    smb = np.zeros((dummy.shape[0], dummy.shape[1]))



    # Loop over the "time" axis
    for i in range(0, temperatures.shape[0]):

        # Create empty semi-permanent arrays
        temp = dummy.copy()
        prec = dummy.copy()
        Eprec = dummy.copy()

        # Loop over the different weights
        for k in range(w.shape[0]):

            # Loops over the cells of the array to be upsampled
            for mx in range(MX):
                for my in range(MY):

                    interpolate_and_get_results(temp, prec, Eprec, w, i, k, mx, my)

        # At each timestep, update the semi-permanent array using the results from the interpolate function
        smb[np.logical_and(temp <= 0, prec > 0)] += prec[np.logical_and(temp <= 0, prec > 0)]

        # Fill the permanent arrays (equivalent of storing the results at the end of every year)
        # and reinitialize the semi-permanent array every 5th timestep
        if i%5 == 0:
            
            # Permanent
            tempYEAR[int(i/5)] = temp
            precYEAR[int(i/5)] = prec
            EprecYEAR[int(i/5)] = Eprec
            smbYEAR[int(i/5)] = smb

            # Semi-permanent
            smb = np.zeros((dummy.shape[0], dummy.shape[1]))


print("Time spent:", time.time()-ts)
  

注意:这个答案不是关于如何使用地图,而是关于“更好的方法”。

您正在进行大量冗余计算。信不信由你,这段代码输出相同的结果。

# No change in the initialization section above.

ts = time.time()
if __name__ == '__main__':

    dummy = np.zeros((w.shape[1], w.shape[2]))

    # Initialize the permanent arrays to be filled
    tempYEAR = np.zeros((9, dummy.shape[0], dummy.shape[1]))
    precYEAR = np.zeros((9, dummy.shape[0], dummy.shape[1]))
    EprecYEAR = np.zeros((9, dummy.shape[0], dummy.shape[1]))
    smbYEAR = np.zeros((9, dummy.shape[0], dummy.shape[1]))

    smb = np.zeros((dummy.shape[0], dummy.shape[1]))

    temperatures_inter = temperatures - 272.15
    w_inter = w.sum(axis=0)
    alt_inter = (alt * (-6 / 1000)).sum()

    for i in range(0, temperatures_inter.shape[0]):
        temp_i = (temperatures_inter[i].sum() - alt_inter) * w_inter
        prec_i = precipitation[i].sum() * w_inter
        Eprec_i = snow[i].sum() * w_inter

        condition = np.logical_and(temp_i <= 0, prec_i > 0)
        smb[condition] += prec_i[condition]

        if i % 5 == 0:
            tempYEAR[i // 5] = temp_i
            precYEAR[i // 5] = prec_i
            EprecYEAR[i // 5] = Eprec_i
            smbYEAR[i // 5] = smb
            smb = np.zeros((dummy.shape[0], dummy.shape[1]))

print("Time spent:", time.time() - ts)

我通过将结果与使用 numba 的代码的输出进行比较来验证结果。相差0.0000001左右,估计是四舍五入造成的。

print((tempYEAR_from_yours - tempYEAR_from_mine).sum())  # -8.429287845501676e-08
print((precYEAR_from_yours - precYEAR_from_mine).sum())  # 2.595697878859937e-09
print((EprecYEAR_from_yours - EprecYEAR_from_mine).sum())  # -7.430216442116944e-09
print((smbYEAR_from_yours - smbYEAR_from_mine).sum())  # -6.875431779462815e-09

在我的电脑上,这段代码用了 0.36 秒。它不使用 numba,甚至没有并行化。它只是消除了冗余。