I'm trying to make a function that generates a 2D dataset. The steps I want the code to do are as such:
Make a list of 100 lists, with coordinates and class 0 (for uninitialised) in each list
Generate n random coordinates and set their classes, update list
Calculate distance between points with class 0 and points with assigned classes. Take sum of 1/distance to points with class 1, sum of 1/distance to points with class 2... etc and the class of that point will be the class that has the largest sum
However, the code below does not work, when the list points is printed, almost all the points still have class 0.
import random
def dataset_maker(n): # 2D plane with 0 through 9 for x and y axis
points = []
for i in range(0,10):
for j in range(0,10):
points.append([i, j, "0"]) # 0 = class not initialised, 1 = class 1, 2 = class 2 etc
centriods = []
for i in range(n):
centriods.append((random.randint(0,9), random.randint(0,9)))
for i in range(0,len(points)):
for j in range(0,len(centriods)):
if points[i][0] == centriods[j][0] and points[i][1] == centriods[j][1]:
points[i][2] = str(j+1)
# All neighbours NN to determine classes of other points
distances = [0] * n
for i in range(0,len(points)):
if points[i][2] == "0":
for j in range(0,len(points)):
if points[j][2] != "0":
if((points[i][0] - points[j][0]) ** 2 + (points[i][1] - points[j][1]) ** 2) != 0: # prevent division of 0
tmp = 1 / ((points[i][0] - points[j][0]) ** 2 + (points[i][1] - points[j][1]) ** 2)
tmp2 = int(points[j][2]) - 1
distances[tmp2] += tmp
tmp3 = distances[0]
for i in range(0,len(distances)):
if distances[i] > tmp3:
tmp3 = distances[i]
points[i][2] = str(distances.index(tmp3) + 1)
distances = [0] * n
print(points)
dataset_maker(5)
Related
I have a python code that implements Dynamic Time Wrapping, which I use to compare the predicted curve to my actual curve. I care about the shape of the curve but also about the distance between the 2 curves. I z-normalized the 2 curves before calling the function that returns the cost. However, I got weird results. For example:
I got cost of 0.28 for this example:
While I got 0.38 for the below example:
In the first plot, the prediction is very far away compared to the second plot. I even got the same value of 0.28 with even very far away prediction such as 5000 points further. What is wrong here?
Below is my code from this source:
#Dynamic Time Wrapping Algorithm
def dp(dist_mat):
N, M = dist_mat.shape
# Initialize the cost matrix
cost_mat = numpy.zeros((N + 1, M + 1))
for i in range(1, N + 1):
cost_mat[i, 0] = numpy.inf
for i in range(1, M + 1):
cost_mat[0, i] = numpy.inf
# Fill the cost matrix while keeping traceback information
traceback_mat = numpy.zeros((N, M))
for i in range(N):
for j in range(M):
penalty = [
cost_mat[i, j], # match (0)
cost_mat[i, j + 1], # insertion (1)
cost_mat[i + 1, j]] # deletion (2)
i_penalty = numpy.argmin(penalty)
cost_mat[i + 1, j + 1] = dist_mat[i, j] + penalty[i_penalty]
traceback_mat[i, j] = i_penalty
# Traceback from bottom right
i = N - 1
j = M - 1
path = [(i, j)] #Path is commented because I am not interested in the path
# while i > 0 or j > 0:
# tb_type = traceback_mat[i, j]
# if tb_type == 0:
# # Match
# i = i - 1
# j = j - 1
# elif tb_type == 1:
# # Insertion
# i = i - 1
# elif tb_type == 2:
# # Deletion
# j = j - 1
# path.append((i, j))
# Strip infinity edges from cost_mat before returning
cost_mat = cost_mat[1:, 1:]
return (path[::-1], cost_mat)
I use the above code as below:
z_actual=stats.zscore(actual)
z_pred=stats.zscore(mean_predictions)
N = actual.shape[0]
M = mean_predictions.shape[0]
dist_mat = numpy.zeros((N, M))
for i in range(N):
for j in range(M):
dist_mat[i, j] = abs(z_actual[i] - z_pred[j])
path,cost_mat=dp(dist_mat)
mape=cost_mat[N - 1, M - 1]/(N + M)
I am working on creating an eigenvalue calculator using the Jacobi method and it runs without errors. However, it does not find the correct eigenvalues nor does it find the correct eigenvectors. For some reason, I always get eigenvalues of 0. I think it may not be saving the matrix I input for MatrixA.
(Link to Jacobi method in case you are not familiar: http://fourier.eng.hmc.edu/e176/lectures/ch1/node1.html)
import numpy as np
import bettertimeit as time
import matplotlib as plt
def Jacobi(A):
n = A.shape[0] # matrix size #columns = #lines
maxit = 100 # maximum number of iterations
eps = 1.0e-15 # accuracy goal
pi = np.pi
info = 0 # return flag
ev = np.zeros(n,float) # initialize eigenvalues
U = np.zeros((n,n),float) # initialize eigenvector
for i in range(0,n): U[i,i] = 1.0
for t in range(0,maxit):
s = 0 # compute sum of off-diagonal elements in A(i,j)
for i in range(0,n): s = s + np.sum(np.abs(A[i,(i+1):n]))
if (s < eps): # diagonal form reached
info = t
for i in range(0,n):ev[i] = A[i,i]
break
else:
limit = s/(n*(n-1)/2.0) # average value of off-diagonal elements
for i in range(0,n-1): # loop over lines of matrix
for j in range(i+1,n): # loop over columns of matrix
if (np.abs(A[i,j]) > limit): # determine (ij) such that |A(i,j)| larger than average
# value of off-diagonal elements
denom = A[i,i] - A[j,j] # denominator of Eq. (3.61)
if (np.abs(denom) < eps): phi = pi/4 # Eq. (3.62)
else: phi = 0.5*np.arctan(2.0*A[i,j]/denom) # Eq. (3.61)
si = np.sin(phi)
co = np.cos(phi)
for k in range(i+1,j):
store = A[i,k]
A[i,k] = A[i,k]*co + A[k,j]*si # Eq. (3.56)
A[k,j] = A[k,j]*co - store *si # Eq. (3.57)
for k in range(j+1,n):
store = A[i,k]
A[i,k] = A[i,k]*co + A[j,k]*si # Eq. (3.56)
A[j,k] = A[j,k]*co - store *si # Eq. (3.57)
for k in range(0,i):
store = A[k,i]
A[k,i] = A[k,i]*co + A[k,j]*si
A[k,j] = A[k,j]*co - store *si
store = A[i,i]
A[i,i] = A[i,i]*co*co + 2.0*A[i,j]*co*si +A[j,j]*si*si # Eq. (3.58)
A[j,j] = A[j,j]*co*co - 2.0*A[i,j]*co*si +store *si*si # Eq. (3.59)
A[i,j] = 0.0 # Eq. (3.60)
for k in range(0,n):
store = U[k,j]
U[k,j] = U[k,j]*co - U[k,i]*si # Eq. (3.66)
U[k,i] = U[k,i]*co + store *si # Eq. (3.67)
info = -t # in case no convergence is reached set info to a negative value "-t"
return ev,U,t
n = int(input("Enter the matrix size: "))
A = np.zeros((n, n))
for i in range(n):
A[i] = input().split(" ")
MatrixA = np.array(A)
print("A= ")
print(A)
for i in range(A.shape[0]):
row = ["{}*x{}".format(A[i, j], j + 1) for j in range(A.shape[1])]
# Jacobi-method
ev,U,t = Jacobi(A)
print ("JACOBI METHOD: Number of rotations = ",t)
print ("Eigenvalues = ",ev)
print ("Eigenvectors = ")
print (U)
I want to generate two linear chains of 20 monomers each at some distance to each other. The following code generates a single chain. Could someone help me with how to generate the second chain?
The two chains are fixed to a surface i.e the first monomer of the chain is fixed and the rest of the monomers move freely in x-y-z directions but the z component of the monomers should be positive.
Something like this:
import numpy as np
import numba as nb
#import pandas as pd
#nb.jit()
def gen_chain(N):
x = np.zeros(N)
y = np.zeros(N)
z = np.linspace(0, (N)*0.9, num=N)
return np.column_stack((x, y, z)), np.column_stack((x1, y1, z1))
#coordinates = np.loadtxt('2GN_50_T_10.txt', skiprows=199950)
#return coordinates
#nb.jit()
def lj(rij2):
sig_by_r6 = np.power(sigma**2 / rij2, 3)
sig_by_r12 = np.power(sigma**2 / rij2, 6)
lje = 4 * epsilon * (sig_by_r12 - sig_by_r6)
return lje
#nb.jit()
def fene(rij2):
return (-0.5 * K * np.power(R, 2) * np.log(1 - ((np.sqrt(rij2) - r0) / R)**2))
#nb.jit()
def total_energy(coord):
# Non-bonded energy.
e_nb = 0.0
for i in range(N):
for j in range(i - 1):
ri = coord[i]
rj = coord[j]
rij = ri - rj
rij2 = np.dot(rij, rij)
if (rij2 < rcutoff_sq):
e_nb += lj(rij2)
# Bonded FENE potential energy.
e_bond = 0.0
for i in range(1, N):
ri = coord[i]
rj = coord[i - 1] # Can be [i+1] ??
rij = ri - rj
rij2 = np.dot(rij, rij)
e_bond += fene(rij2)
return e_nb + e_bond
#nb.jit()
def move(coord):
trial = np.ndarray.copy(coord)
for i in range(1, N):
while True:
delta = (2 * np.random.rand(3) - 1) * max_delta
trial[i] += delta
#while True:
if trial[i,2] > 0.0:
break
trial[i] -= delta
return trial
#nb.jit()
def accept(delta_e):
beta = 1.0 / T
if delta_e < 0.0:
return True
random_number = np.random.rand(1)
p_acc = np.exp(-beta * delta_e)
if random_number < p_acc:
return True
return False
if __name__ == "__main__":
# FENE potential parameters.
K = 40.0
R = 0.3
r0 = 0.7
# L-J potential parameters
sigma = 0.5716
epsilon = 1.0
# MC parameters
N = 20 # Numbers of monomers
rcutoff = 2.5 * sigma
rcutoff_sq = rcutoff * rcutoff
max_delta = 0.01
n_steps = 100000
T = 10
# MAIN PART OF THE CODE
coord = gen_chain(N)
energy_current = total_energy(coord)
traj = open('2GN_20_T_10.xyz', 'w')
traj_txt = open('2GN_20_T_10.txt', 'w')
for step in range(n_steps):
if step % 1000 == 0:
traj.write(str(N) + '\n\n')
for i in range(N):
traj.write("C %10.5f %10.5f %10.5f\n" % (coord[i][0], coord[i][1], coord[i][2]))
traj_txt.write("%10.5f %10.5f %10.5f\n" % (coord[i][0], coord[i][1], coord[i][2]))
print(step, energy_current)
coord_trial = move(coord)
energy_trial = total_energy(coord_trial)
delta_e = energy_trial - energy_current
if accept(delta_e):
coord = coord_trial
energy_current = energy_trial
traj.close()
I except the chain of particles to collapse into a globule.
There is some problem with the logic of the MC you are implementing.
To perform a MC you need to ATTEMPT a move, evaluate the energy of the new state and then accept/reject according to a random number.
In your code there is not the slightest sign of the attempt to move a particle.
You need to move one (or more of them), evaluate the energy, and then update your coordinates.
By the way, I suppose this is not your entire code. There are many parameters that are not defined like the "k" and the "R0" in your fene potential
The FENE potential models bond interactions. What your code is saying is that all particles within the cutoff are bonded by FENE springs, and that the bonds are not fixed but rather defined by the cutoff. With a r_cutoff = 3.0, larger than equilibrium distance of the LJ well, you are essentially considering that each particle is bonded to potentially many others. You are treating the FENE potential as a non-bonded one.
For the bond interactions you should ignore the cutoff and only evaluate the energy for the actual pairs that are bonded according to your topology, which means that first you need to define a topology. I suggest generating a linear molecule of N atoms in a box big enough to contain the whole stretched molecule, and consider the i-th atom as bonded to the (i-1)-th atom, with i = 2, ..., N. In this way the topology is well defined and persistent. Then consider both interactions separately, non-bonded and bond, and add them at the end.
Something like this, in pseudo-code:
e_nb = 0
for particle i = 1 to N:
for particle j = 1 to i-1:
if (dist(i, j) < rcutoff):
e_nb += lj(i, j)
e_bond = 0
for particle i = 2 to N:
e_bond += fene(i, i-1)
e_tot = e_nb + e_bond
Below you can find a modified version of your code. To make things simpler, in this version there is no box and no boundary conditions, just a chain in free space. The chain is initialized as a linear sequence of particles each distant 80% of R0 from the next, since R0 is the maximum length of the FENE bond. The code considers that particle i is bonded with i+1 and the bond is not broken. This code is just a proof of concept.
#!/usr/bin/python
import numpy as np
def gen_chain(N, R):
x = np.linspace(0, (N-1)*R*0.8, num=N)
y = np.zeros(N)
z = np.zeros(N)
return np.column_stack((x, y, z))
def lj(rij2):
sig_by_r6 = np.power(sigma/rij2, 3)
sig_by_r12 = np.power(sig_by_r6, 2)
lje = 4.0 * epsilon * (sig_by_r12 - sig_by_r6)
return lje
def fene(rij2):
return (-0.5 * K * R0**2 * np.log(1-(rij2/R0**2)))
def total_energy(coord):
# Non-bonded
e_nb = 0
for i in range(N):
for j in range(i-1):
ri = coord[i]
rj = coord[j]
rij = ri - rj
rij2 = np.dot(rij, rij)
if (rij2 < rcutoff):
e_nb += lj(rij2)
# Bonded
e_bond = 0
for i in range(1, N):
ri = coord[i]
rj = coord[i-1]
rij = ri - rj
rij2 = np.dot(rij, rij)
e_bond += fene(rij2)
return e_nb + e_bond
def move(coord):
trial = np.ndarray.copy(coord)
for i in range(N):
delta = (2.0 * np.random.rand(3) - 1) * max_delta
trial[i] += delta
return trial
def accept(delta_e):
beta = 1.0/T
if delta_e <= 0.0:
return True
random_number = np.random.rand(1)
p_acc = np.exp(-beta*delta_e)
if random_number < p_acc:
return True
return False
if __name__ == "__main__":
# FENE parameters
K = 40
R0 = 1.5
# LJ parameters
sigma = 1.0
epsilon = 1.0
# MC parameters
N = 50 # number of particles
rcutoff = 3.5
max_delta = 0.01
n_steps = 10000000
T = 1.5
coord = gen_chain(N, R0)
energy_current = total_energy(coord)
traj = open('traj.xyz', 'w')
for step in range(n_steps):
if step % 1000 == 0:
traj.write(str(N) + '\n\n')
for i in range(N):
traj.write("C %10.5f %10.5f %10.5f\n" % (coord[i][0], coord[i][1], coord[i][2]))
print(step, energy_current)
coord_trial = move(coord)
energy_trial = total_energy(coord_trial)
delta_e = energy_trial - energy_current
if accept(delta_e):
coord = coord_trial
energy_current = energy_trial
traj.close()
The code prints the current configuration at each step, you can just load it up on VMD and see how it behaves. The bonds will not show correctly at first on VMD, you must use a bead representation for the particles and define the bonds manually or with a script within VMD. In any case, you don't need to see the bonds to notice that the chain does not collapse.
Please bear in mind that if you want to simulate a chain at a certain density, you need to be careful to generate the correct topology. I recommend the EMC package to efficiently generate polymers at the desired thermodynamic conditions. It is by no means a trivial problem, especially for larger chains.
By the way, your code had an error in the FENE energy evaluation. rij2 is already squared, you squared it again.
Below you can see how the total energy as a function of the number of steps behaves for T = 1.0, N = 20, rcutoff = 3.5, and also the last current configuration after 10 thousand steps.
And below for N = 50, T = 1.5, max_delta = 0.01, K = 40, R = 1.5, rcutoff = 3.5, and 10 million steps. This is the last current configuration.
The full "trajectory", which isn't really a trajectory since this is MC, you can find here (it's under 6 MB).
The below code generates set of random x,y coordinates and uses the equation of an ellipse to compare how many of those points lie inside ellipse centered at (1,1) and a rectangle of area 2a*2b constructed around the ellipse whose semimajor and semiminor axis are a and b but b is variable and takes a value from the list b every single time. I want to have all the values of b for which the ratio of all the points lying inside the ellipse to the points lying inside the rectangle is greater than 0.5.
The problem I'm facing is If I check for a single value of b = 0.63. the condition ellipse_points/rectangle_points is approximately equal to 0.5 but when I loop throught the list b and use the If statement to get all the points for which ellipse_points/rectangle_points > 0.5, I do not see any value close to 0.63 instead I see values from 1.2 till 1.9, I do not understand why when I loop through a list of values for b the if statement seems to give faulty values. please refer to the next set of code where I set value of b = 0.63 and find ratio ellipse_points/rectangle_points
import numpy as np
x = np.random.uniform(0, 2, 10000) #generates random x coordinates
y = np.random.uniform(0, 2, 10000) #generates random y coordinates
ellipse_points, rectangle_points = 0, 0
a = 1
b = []
for i in range(1, 200):
b.append(i/100)
#print(b)
for p in b:
for i, j in zip(x, y):
if (((i - 1) ** 2) / a ** 2 + ((j - 1) ** 2) / p ** 2) < 1:
ellipse_points += 1
rectangle_points += 1
if ellipse_points/rectangle_point > 0.5:
print(p)
OUTPUT: 1.2, 1.21.............1.9
#
x = np.random.uniform(0, 2, 10000) #generates random x coordinates
y = np.random.uniform(0, 2, 10000) #generates random y coordinates
ellipse_points, rectangle_points = 0, 0
a = 1
b = 0.63
for i, j in zip(x, y):
if (((i - 1) ** 2) / a ** 2 + ((j - 1) ** 2) / b ** 2) < 1:
ellipse_points += 1
rectangle_points += 1
print(ellipse_points/rectangle_points)
OUTPUT 0.5001
If I understood your problem correctly, here's a vectorized solution.
It creates a binary mask for points inside the ellipse, counts where the mask is True and divides it by the total number of points.
# np.random.seed(42)
N = 10000
x = np.random.uniform(0, 2, N) #generates random x coordinates
y = np.random.uniform(0, 2, N) #generates random y coordinates
a = 1
b = 0.63
ratio = ((((x - 1)/a)**2 + ((y - 1)/b)**2) < 1).sum()/N
>>> print(ratio)
0.4954
I am trying to calculate the euclidean distance and direction from a source coordinate within a numpy array.
Graphic Example
Here is what I was able to come up with, however it is relatively slow for large arrays. Euclidean Distance and Direction based on source coordinates rely heavily on the index of each cell. that is why I am looping each row and column. I have looked into scipy cdist, pdist, and np linalg.
import numpy as np
from math import atan, degrees, sqrt
from timeit import default_timer
def euclidean_from_source(input_array, y_index, x_index):
# copy arrays
distance = np.empty_like(input_array, dtype=float)
direction = np.empty_like(input_array, dtype=int)
# loop each row
for i, row in enumerate(X):
# loop each cell
for c, cell in enumerate(row):
# get b
b = x_index - i
# get a
a = y_index - c
hypotenuse = sqrt(a * a + b * b) * 10
distance[i][c] = hypotenuse
direction[i][c] = get_angle(a, b)
return [distance, direction]
def calibrate_angle(a, b, angle):
if b > 0 and a > 0:
angle+=90
elif b < 0 and a < 0:
angle+=270
elif b > 0 > a:
angle+=270
elif a > 0 > b:
angle+=90
return angle
def get_angle(a, b):
# get angle
if b == 0 and a == 0:
angle = 0
elif b == 0 and a >= 0:
angle = 90
elif b == 0 and a < 0:
angle = 270
elif a == 0 and b >= 0:
angle = 180
elif a == 0 and b < 0:
angle = 360
else:
theta = atan(b / a)
angle = degrees(theta)
return calibrate_angle(a, b, angle)
if __name__ == "__main__":
dimension_1 = 5
dimension_2 = 5
X = np.random.rand(dimension_1, dimension_2)
y_index = int(dimension_1/2)
x_index = int(dimension_2/2)
start = default_timer()
distance, direction = euclidean_from_source(X, y_index, x_index)
print('Total Seconds {{'.format(default_timer() - start))
print(distance)
print(direction)
UPDATE
I was able to use the broadcasting function to do exactly what I needed, and at a fraction of the speed. however I am still figuring out how to calibrate the angle to 0, 360 throughout the matrix (modulus will not work in this scenario).
import numpy as np
from math import atan, degrees, sqrt
from timeit import default_timer
def euclidean_from_source_update(input_array, y_index, x_index):
size = input_array.shape
center = (y_index, x_index)
x = np.arange(size[0])
y = np.arange(size[1])
# use broadcasting to get euclidean distance from source point
distance = np.multiply(np.sqrt((x - center[0]) ** 2 + (y[:, None] - center[1]) ** 2), 10)
# use broadcasting to get euclidean direction from source point
direction = np.rad2deg(np.arctan2((x - center[0]) , (y[:, None] - center[1])))
return [distance, direction]
def euclidean_from_source(input_array, y_index, x_index):
# copy arrays
distance = np.empty_like(input_array, dtype=float)
direction = np.empty_like(input_array, dtype=int)
# loop each row
for i, row in enumerate(X):
# loop each cell
for c, cell in enumerate(row):
# get b
b = x_index - i
# get a
a = y_index - c
hypotenuse = sqrt(a * a + b * b) * 10
distance[i][c] = hypotenuse
direction[i][c] = get_angle(a, b)
return [distance, direction]
def calibrate_angle(a, b, angle):
if b > 0 and a > 0:
angle+=90
elif b < 0 and a < 0:
angle+=270
elif b > 0 > a:
angle+=270
elif a > 0 > b:
angle+=90
return angle
def get_angle(a, b):
# get angle
if b == 0 and a == 0:
angle = 0
elif b == 0 and a >= 0:
angle = 90
elif b == 0 and a < 0:
angle = 270
elif a == 0 and b >= 0:
angle = 180
elif a == 0 and b < 0:
angle = 360
else:
theta = atan(b / a)
angle = degrees(theta)
return calibrate_angle(a, b, angle)
if __name__ == "__main__":
dimension_1 = 5
dimension_2 = 5
X = np.random.rand(dimension_1, dimension_2)
y_index = int(dimension_1/2)
x_index = int(dimension_2/2)
start = default_timer()
distance, direction = euclidean_from_source(X, y_index, x_index)
print('Total Seconds {}'.format(default_timer() - start))
start = default_timer()
distance2, direction2 = euclidean_from_source_update(X, y_index, x_index)
print('Total Seconds {}'.format(default_timer() - start))
print(distance)
print(distance2)
print(direction)
print(direction2)
Update 2
Thanks everyone for the responses, after testing methods, these two methods were the fastest and produced the results I needed. I am still open to any optimizations you guys can think of.
def get_euclidean_direction(input_array, y_index, x_index):
rdist = np.arange(input_array.shape[0]).reshape(-1, 1) - x_index
cdist = np.arange(input_array.shape[1]).reshape(1, -1) - y_index
direction = np.mod(np.degrees(np.arctan2(rdist, cdist)), 270)
direction[y_index:, :x_index]+= -90
direction[y_index:, x_index:]+= 270
direction[y_index][x_index] = 0
return direction
def get_euclidean_distance(input_array, y_index, x_index):
size = input_array.shape
center = (y_index, x_index)
x = np.arange(size[0])
y = np.arange(size[1])
return np.multiply(np.sqrt((x - center[0]) ** 2 + (y[:, None] - center[1]) ** 2), 10)
This operation is extremely easy to vectorize. For one thing, a and b don't need to be computed in 2D at all, since they only depend on one direction in the array. The distance can be computed with np.hypot. Broadcasting will convert the shape into the correct 2D form.
Your angle function is almost exactly equivalent to applying np.degrees to np.arctan2.
It's unclear why you label your rows with xand columns with y instead of the standard way of doing it, but as long as you're consistent, it should be fine.
So here's the vectorized version:
def euclidean_from_source(input_array, c, r):
rdist = np.arange(input_array.shape[0]).reshape(-1, 1) - r
# Broadcasting doesn't require this second reshape
cdist = np.arange(input_array.shape[1]).reshape(1, -1) - c
distance = np.hypot(rdist, cdist) * 10
direction = np.degrees(np.arctan2(rdist, cdist))
return distance, direction
I will leave it as an exercise for the reader to determine if any additional processing is necessary to fine-tune the angle, and if so, to implement it in a vectorized manner.
Might be easier to just pass the corrdinate you want to measure as an array or tuple. Also, while it might take a bit more memory, I think using np.indices might be a bit faster for the calculation (as it allows np.einsum to do its magic).
def euclidean_from_source(input_array, coord):
grid = np.indices(input_array.shape)
grid -= np.asarray(coord)[:, None, None]
distance = np.einsum('ijk, ijk -> jk', grid, grid) ** .5
direction = np.degrees(np.arctan2(grid[0], grid[1]))
return distance, direction
This method is also a bit more extensible to n-d (although obviously the angle calculations would be a bit trickier