This is an interesting problem - looking at hydrology flow/downhill direction?
The issue with the description is that you don't specify which of the neighboring cells to iterate to next, meaning, if you have three neighboring cells that are lower, they all get added to the output array but which is selected? Also, when looking back through the output array how do you know which ones got added with respect to a single neighboring cell? I went about this by selecting the lowest value of the neighboring cells with less than the source cell's value (biggest dropoff), then continued the search from that cell and so on. For each iteration, there is a new nested list of the cell positions for the neighboring cells that are lower. This could readily be updated any number of ways, to include exporting an additional list showing which cells were used for search (essentially the path taken, while there might be highlighted cells (lower) that were not visited per se.
import numpy as np
Pe = np.random.rand(5,5)
def nearestMin(arr, output_arr=[[0,0]], n=0, m=0):
if m==arr.shape[1]:
return output_arr
lower_vals = np.argwhere(arr < arr[n,m])
if lower_vals.size == 0: #No values anywhere less than current cell
return output_arr
#Get offset from current point, as tuple of x and y distance
smaller_adjacents = np.where(Pe < Pe[n, m])
if smaller_adjacents[0].size == 0: #No lower points anywhere
return output_arr
ind = np.where((abs(n - smaller_adjacents[0]) <= 1 ) & \
(abs(m - smaller_adjacents[1]) <= 1))[0]
if ind.size == 0: #No lower points neighboring
return output_arr
xs, ys = smaller_adjacents[0][ind], smaller_adjacents[1][ind]
lower_neighbors = np.vstack((xs, ys)).T.tolist()
output_arr.append(lower_neighbors) #Unbracket to have final output array be flat (2d)
n_depth = [Pe[x,y] for x,y in lower_neighbors] - Pe[n,m]
biggest_drop = np.where(n_depth==n_depth.min())[0][0]
lowest_neighbor = lower_neighbors[biggest_drop]
return nearestMin(arr, output_arr, n=lowest_neighbor[0], m=lowest_neighbor[1])
nearestMin(Pe)
If you want the path, the same code can be modified as shown below to return that as a second array. In this example, the values for the path are listed. Each value corresponds to the "list of lists" item in the first returned array of neighboring lower values. Again, not sure exactly what you're going for.
import numpy as np
Pe = np.random.rand(5,5)
def nearestMin(arr, output_arr=[[0,0]], path=[], n=0, m=0):
#Initialize path variable properly
if output_arr == [[0,0]]:
path.append(arr[0,0])
if m==arr.shape[1]:
return (output_arr, path)
lower_vals = np.argwhere(arr < arr[n,m])
if lower_vals.size == 0: #No values anywhere less than current cell
return (output_arr, path)
#Get offset from current point, as tuple of x and y distance
smaller_adjacents = np.where(Pe < Pe[n, m])
if smaller_adjacents[0].size == 0: #No lower points anywhere
return (output_arr, path)
ind = np.where((abs(n - smaller_adjacents[0]) <= 1 ) & \
(abs(m - smaller_adjacents[1]) <= 1))[0]
if ind.size == 0: #No lower points neighboring
return (output_arr, path)
xs, ys = smaller_adjacents[0][ind], smaller_adjacents[1][ind]
lower_neighbors = np.vstack((xs, ys)).T.tolist()
output_arr.append(lower_neighbors) #Unbracket to have final output array be flat (2d)
n_depth = [Pe[x,y] for x,y in lower_neighbors] - Pe[n,m]
biggest_drop = np.where(n_depth==n_depth.min())[0][0]
lowest_neighbor = lower_neighbors[biggest_drop]
path.append(arr[lowest_neighbor[0], lowest_neighbor[1]])
return nearestMin(arr, output_arr, path, n=lowest_neighbor[0], m=lowest_neighbor[1])
nearestMin(Pe)