简体   繁体   中英

How to implement in Python a function to compute the Euclidean distance between two arbitrary points on a torus

Given a 10x10 grid (2d-array) filled randomly with numbers, either 0, 1 or 2 . How can I find the Euclidean distance (the l2-norm of the distance vector) between two given points considering periodic boundaries?

Let us consider an arbitrary grid point called centre . Now, I want to find the nearest grid point containing the same value as centre . I need to take periodic boundaries into account, such that the matrix/grid can be seen rather as a torus instead of a flat plane. In that case, say the centre = matrix[0,2] , and we find that there is the same number in matrix[9,2] , which would be at the southern boundary of the matrix. The Euclidean distance computed with my code would be for this example np.sqrt(0**2 + 9**2) = 9.0 . However, because of periodic boundaries, the distance should actually be 1 , because matrix[9,2] is the northern neighbour of matrix[0,2] . Hence, if periodic boundary values are implemented correctly, distances of magnitude above 8 should not exist.

So, I would be interested on how to implement in Python a function to compute the Euclidean distance between two arbitrary points on a torus by applying a wrap-around for the boundaries.

import numpy as np

matrix = np.random.randint(0,3,(10,10))
centre = matrix[0,2]

#rewrite the centre to be the number 5 (to exclude itself as shortest distance)
matrix[0,2] = 5

#find the points where entries are same as centre
same = np.where((matrix == centre) == True)
idx_row, idx_col = same

#find distances from centre to all values which are of same value 
dist = np.zeros(len(same[0]))
for i in range(0,len(same[0])):
    delta_row = same[0][i] - 0 #row coord of centre
    delta_col = same[1][i] - 2 #col coord of centre
    dist[i] = np.sqrt(delta_row**2 + delta_col**2)

#retrieve the index of the smallest distance
idx = dist.argmin() 
print('Centre value: %i. The nearest cell with same value is at (%i,%i)' 
      % (centre, same[0][idx],same[1][idx]))

For each axis, you can check whether the distance is shorter when you wrap around or when you don't. Consider the row axis, with rows i and j .

  • When not wrapping around, the difference is abs(i - j) .
  • When wrapping around, the difference is "flipped", as in 10 - abs(i - j) . In your example with i == 0 and j == 9 you can check that this correctly produces a distance of 1.

Then simply take whichever is smaller:

delta_row = same[0][i] - 0 #row coord of centre
delta_row = min(delta_row, 10 - delta_row)

And similarly for delta_column .

The final dist[i] calculation needs no changes.

I have a working 'sketch' of how this could work. In short, I calculate the distance 9 times, 1 for the normal distance, and 8 shifts to possibly correct for a closer 'torus' distance.

As n is getting larger, the calculation costs can go sky high as the numbers go up. But, the torus effect, is probably not needed as there is always a point nearby without 'wrap around'.

You can easily test this, because for a grid of size 1, if a point is found of distance 1/2 or closer, you know there is not a closer torus point (right?)

import numpy as np

n=10000

np.random.seed(1)

A = np.random.randint(low=0, high=10, size=(n,n))

I create 10000x10000 points, and store the location of the 1's in ONES .

ONES = np.argwhere(A == 0)

Now I define my torus distance, which is trying which of the 9 mirrors is the closest.

def distance_on_torus( point=[500,500] ):
    index_diff = [[1],[1],[0],[0],[0,1],[0,1],[0,1],[0,1]]
    coord_diff = [[-1],[1],[-1],[1],[-1,-1],[-1,1],[1,-1],[1,1]]
    
    tree = BallTree( ONES, leaf_size=5*n, metric='euclidean')
    
    dist, indi = tree.query([point],k=1, return_distance=True )

    distances = [dist[0]]

    for indici_to_shift, coord_direction in zip(index_diff, coord_diff):
        MIRROR = ONES.copy()
        for i,shift in zip(indici_to_shift,coord_direction):
            MIRROR[:,i] = MIRROR[:,i] + (shift * n)

        tree = BallTree( MIRROR, leaf_size=5*n, metric='euclidean')
        dist, indi = tree.query([point],k=1, return_distance=True )
        
        distances.append(dist[0])
        
    
    return np.min(distances)
%%time

distance_on_torus([2,3])

It is slow, the above takes 15 minutes.... For n = 1000 less than a second.


A optimisation would be to first consider the none-torus distance, and if the minimum distance is possibly not the smallest, calculate with only the minimum set of extra 'blocks' around. This will greatly increase speed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM