I have 2 2-dimensional coordinates. Say, (x_1, y_1) be the point at time t = 1 and (x_2, y_2) be the point at time t = 10. I would like to linearly interpolate between 1 to 10 timesteps i.e. 2,3,...9.
How do I do that in python?
You can calculate the equation of the line using those two points, and then use the equation to generate as many as points. But the relationship with time depends on the motion type you want (easeOut, easeIn, linear)
(Y - y2)/(X - x2) = (y2 - y1)(x2 - x1)
Here is the equation. You can use your real values and get an equation of X and Y. Then calculate Y values to given X values.
This should work. You can give seek a value in between 0 & 1 to get intermediate coordinates
def interpolate(x1, y1, x2, y2, seek):
X = x1 + x2*seek
Y = y2 + (y2 - y1)(X - x2)/(x2 - x1)
return (X,Y)
What you want to do is called linear interpolation, which can be done with a lerp() function:
from __future__ import division # For Python 2
def lerp(t, times, points):
dx = points[1][0] - points[0][0]
dy = points[1][1] - points[0][1]
dt = (t-times[0]) / (times[1]-times[0])
return dt*dx + points[0][0], dt*dy + points[0][1]
x_1, y_1 = 1, 2
x_2, y_2 = -3, 4
times = [1, 10]
points = [(x_1, y_1), (x_2, y_2)]
for v in range(1, 11):
print('{:2d} -> ({:6.3f}, {:6.3f})'.format(v, *lerp(v, times, points)))
Output:
1 -> ( 1.000, 2.000)
2 -> ( 0.556, 2.222)
3 -> ( 0.111, 2.444)
4 -> (-0.333, 2.667)
5 -> (-0.778, 2.889)
6 -> (-1.222, 3.111)
7 -> (-1.667, 3.333)
8 -> (-2.111, 3.556)
9 -> (-2.556, 3.778)
10 -> (-3.000, 4.000)
A more efficient way that does significantly fewer computations per iteration could implemented by turning lerp() into a generator function. This approach is slightly less accurate due to cumulative error in the successive additions.
from __future__ import division # For Python 2
def lerp(times, points, steps):
divisor = steps-1
dt = (times[1] - times[0]) / divisor
dx = (points[1][0] - points[0][0]) / divisor
dy = (points[1][1] - points[0][1]) / divisor
t, x, y = (times[0],) + points[0]
for _ in range(steps):
yield t, x, y
t += dt
x += dx
y += dy
x_1, y_1 = 1, 2
x_2, y_2 = -3, 4
times = [1, 10]
points = [(x_1, y_1), (x_2, y_2)]
steps= times[1] - times[0] + 1
for t, x, y in lerp(times, points, steps):
print('{:6.2f} -> ({:6.3f}, {:6.3f})'.format(t, x, y))
Object Oriented Approach
If you're going to call it often, it might be worth the trouble to optimize it so it doesn't need to recompute so many values between calls that are the same every time it's called. One way to do that in would be to make it a class with a __call__() method. That way all the preliminary computations can be done when it's first constructed, and then just reused whenever the object is called with different time arguments later. It also makes it each to have and use multiple Lerp objects at the same time or a container of them, if so desired.
Classes like this are sometimes called "functors" since they're a combination of a function and an object (although ordinary functions are already objects in Python, they're not quite as flexible and easy to customize).
Here's what I mean:
from __future__ import division # For Python 2
class Lerp(object):
def __init__(self, times, points):
self.t0 = times[0]
self.p0 = points[0]
self.dt = times[1] - times[0]
self.dx = points[1][0] - points[0][0]
self.dy = points[1][1] - points[0][1]
def __call__(self, t):
dt = (t-self.t0) / self.dt
return dt*self.dx + self.p0[0], dt*self.dy + self.p0[1]
x_1, y_1 = 1, 2
x_2, y_2 = -3, 4
times = [1, 10]
points = [(x_1, y_1), (x_2, y_2)]
lerp = Lerp(times, points)
for v in range(1, 11):
print('{:2d} -> ({:6.3f}, {:6.3f})'.format(v, *lerp(v)))
Related
I have a simple 2D ray-casting routine that gets terribly slow as soon as the number of obstacles increases.
This routine is made up of:
2 for loops (outer loop iterates over each ray/direction, then inner loop iterates over each line obstacle)
multiple if statements (check if a value is > or < than another value or if an array is empty)
Question: How can I condense all these operations into 1 single block of vectorized instructions using Numpy ?
More specifically, I am facing 2 issues:
I have managed to vectorize the inner loop (intersection between a ray and each obstacle) but I am unable to run this operation for all rays at once.
The only workaround I found to deal with the if statements is to use masked arrays. Something tells me it is not the proper way to handle these statements in this case (it seems clumsy, cumbersome and unpythonic)
Original code:
from math import radians, cos, sin
import matplotlib.pyplot as plt
import numpy as np
N = 10 # dimensions of canvas (NxN)
sides = np.array([[0, N, 0, 0], [0, N, N, N], [0, 0, 0, N], [N, N, 0, N]])
edges = np.random.rand(5, 4) * N # coordinates of 5 random segments (x1, x2, y1, y2)
edges = np.concatenate((edges, sides))
center = np.array([N/2, N/2]) # coordinates of center point
directions = np.array([(cos(radians(a)), sin(radians(a))) for a in range(0, 360, 10)]) # vectors pointing in all directions
intersections = []
# for each direction
for d in directions:
min_dist = float('inf')
# for each edge
for e in edges:
p1x, p1y = e[0], e[2]
p2x, p2y = e[1], e[3]
p3x, p3y = center
p4x, p4y = center + d
# find intersection point
den = (p1x - p2x) * (p3y - p4y) - (p1y - p2y) * (p3x - p4x)
if den:
t = ((p1x - p3x) * (p3y - p4y) - (p1y - p3y) * (p3x - p4x)) / den
u = -((p1x - p2x) * (p1y - p3y) - (p1y - p2y) * (p1x - p3x)) / den
# if any:
if t > 0 and t < 1 and u > 0:
sx = p1x + t * (p2x - p1x)
sy = p1y + t * (p2y - p1y)
isec = np.array([sx, sy])
dist = np.linalg.norm(isec-center)
# make sure to select the nearest one (from center)
if dist < min_dist:
min_dist = dist
nearest = isec
# store nearest interesection point for each ray
intersections.append(nearest)
# Render
plt.axis('off')
for x, y in zip(edges[:,:2], edges[:,2:]):
plt.plot(x, y)
for isec in np.array(intersections):
plt.plot((center[0], isec[0]), (center[1], isec[1]), '--', color="#aaaaaa", linewidth=.8)
Vectorized version (attempt):
from math import radians, cos, sin
import matplotlib.pyplot as plt
from scipy import spatial
import numpy as np
N = 10 # dimensions of canvas (NxN)
sides = np.array([[0, N, 0, 0], [0, N, N, N], [0, 0, 0, N], [N, N, 0, N]])
edges = np.random.rand(5, 4) * N # coordinates of 5 random segments (x1, x2, y1, y2)
edges = np.concatenate((edges, sides))
center = np.array([N/2, N/2]) # coordinates of center point
directions = np.array([(cos(radians(a)), sin(radians(a))) for a in range(0, 360, 10)]) # vectors pointing in all directions
intersections = []
# Render edges
plt.axis('off')
for x, y in zip(edges[:,:2], edges[:,2:]):
plt.plot(x, y)
# for each direction
for d in directions:
p1x, p1y = edges[:,0], edges[:,2]
p2x, p2y = edges[:,1], edges[:,3]
p3x, p3y = center
p4x, p4y = center + d
# denominator
den = (p1x - p2x) * (p3y - p4y) - (p1y - p2y) * (p3x - p4x)
# first 'if' statement -> if den > 0
mask = den > 0
den = den[mask]
p1x = p1x[mask]
p1y = p1y[mask]
p2x = p2x[mask]
p2y = p2y[mask]
t = ((p1x - p3x) * (p3y - p4y) - (p1y - p3y) * (p3x - p4x)) / den
u = -((p1x - p2x) * (p1y - p3y) - (p1y - p2y) * (p1x - p3x)) / den
# second 'if' statement -> if (t>0) & (t<1) & (u>0)
mask2 = (t > 0) & (t < 1) & (u > 0)
t = t[mask2]
p1x = p1x[mask2]
p1y = p1y[mask2]
p2x = p2x[mask2]
p2y = p2y[mask2]
# x, y coordinates of all intersection points in the current direction
sx = p1x + t * (p2x - p1x)
sy = p1y + t * (p2y - p1y)
pts = np.c_[sx, sy]
# if any:
if pts.size > 0:
# find nearest intersection point
tree = spatial.KDTree(pts)
nearest = pts[tree.query(center)[1]]
# Render
plt.plot((center[0], nearest[0]), (center[1], nearest[1]), '--', color="#aaaaaa", linewidth=.8)
Reformulation of the problem – Finding the intersection between a line segment and a line ray
Let q and q2 be the endpoints of a segment (obstacle). For convenience let's define a class to represent points and vectors in the plane. In addition to the usual operations, a vector multiplication is defined by u × v = u.x * v.y - u.y * v.x.
Caution: here Coord(2, 1) * 3 returns Coord(6, 3) while Coord(2, 1) * Coord(-1, 4) outputs 9. To avoid this confusion it might have been possible to restrict * to the scalar multiplication and use ^ via __xor__ for the vector multiplication.
class Coord:
def __init__(self, x, y):
self.x = x
self.y = y
#property
def radius(self):
return np.sqrt(self.x ** 2 + self.y ** 2)
def _cross_product(self, other):
assert isinstance(other, Coord)
return self.x * other.y - self.y * other.x
def __mul__(self, other):
if isinstance(other, Coord):
# 2D "cross"-product
return self._cross_product(other)
elif isinstance(other, int) or isinstance(other, float):
# scalar multiplication
return Coord(self.x * other, self.y * other)
def __rmul__(self, other):
return self * other
def __sub__(self, other):
return Coord(self.x - other.x, self.y - other.y)
def __add__(self, other):
return Coord(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Coord({self.x}, {self.y})"
Now, I find it easier to handle a ray in polar coordinates: For a given angle theta (direction) the goal is to determine if it intersects the segment, and if so determine the corresponding radius. Here is a function to find that. See here for an explanation of why and how. I tried to use the same variable names as in the previous link.
def find_intersect_btw_ray_and_sgmt(q, q2, theta):
"""
Args:
q (Coord): first endpoint of the segment
q2 (Coord): second endpoint of the segment
theta (float): angle of the ray
Returns:
(float): np.inf if the ray does not intersect the segment,
the distance from the origin of the intersection otherwise
"""
assert isinstance(q, Coord) and isinstance(q2, Coord)
s = q2 - q
r = Coord(np.cos(theta), np.sin(theta))
cross = r * s # 2d cross-product
t_num = q * s
u_num = q * r
## the intersection point is roughly at a distance t_num / cross
## from the origin. But some cases must be checked beforehand.
## (1) the segment [PQ2] is aligned with the ray
if np.isclose(cross, 0) and np.isclose(u_num, 0):
return min(q.radius, q2.radius)
## (2) the segment [PQ2] is parallel with the ray
elif np.isclose(cross, 0):
return np.inf
t, u = t_num / cross, u_num / cross
## There is actually an intersection point
if t >= 0 and 0 <= u <= 1:
return t
## (3) No intersection point
return np.inf
For instance find_intersect_btw_ray_and_sgmt(Coord(1, 2), Coord(-1, 2), np.pi / 2) should returns 2.
Note that here for simplicity, I only considered the case where the origin of the rays is at Coord(0, 0). This can be easily extended to the general case by setting t_num = (q - origin) * s and u_num = (q - origin) * r.
Let's vectorize it!
What is very interesting here is that the operations defined in the Coord class also apply to cases where x and y are numpy arrays! Hence applying any defined operation on Coord(np.array([1, 2, 0]), np.array([2, -1, 3])) amounts applying it elementwise to the points (1, 2), (2, -1) and (0, 3). The operations of Coord are therefore already vectorized. The constructor can be modified into:
def __init__(self, x, y):
x, y = np.array(x), np.array(y)
assert x.shape == y.shape
self.x, self.y = x, y
self.shape = x.shape
Now, we would like the function find_intersect_btw_ray_and_sgmt to be able to handle the case where the parameters q and q2contains sequences of endpoints. Before the sanity checks, all the operations are working properly since, as we have mentioned, they are already vectorized. As you mentionned the conditional statements can be "vectorized" using masks. Here is what I propose:
def find_intersect_btw_ray_and_sgmts(q, q2, theta):
assert isinstance(q, Coord) and isinstance(q2, Coord)
assert q.shape == q2.shape
EPS = 1e-14
s = q2 - q
r = Coord(np.cos(theta), np.sin(theta))
cross = r * s
cross_sign = np.sign(cross)
cross = cross * cross_sign
t_num = (q * s) * cross_sign
u_num = (q * r) * cross_sign
radii = np.zeros_like(t_num)
mask = ~np.isclose(cross, 0) & (t_num >= -EPS) & (-EPS <= u_num) & (u_num <= cross + EPS)
radii[~mask] = np.inf # no intersection
radii[mask] = t_num[mask] / cross[mask] # intersection
return radii
Note that cross, t_num and u_num are multiplied by the sign of cross to ensure that the division by cross keeps the sign of the dividends. Hence conditions of the form ((t_num >= 0) & (cross >= 0)) | ((t_num <= 0) & (cross <= 0)) can be replaced by (t_num >= 0).
For simplicity, we omitted the case (1) where the radius and the segment were aligned ((cross == 0) & (u_num == 0)). This could be incorporated by carefully adding a second mask.
For a given value of theta, we are able to determine if the corresponing ray intersects with several segments at once.
## Some useful functions
def polar_to_cartesian(r, theta):
return Coord(r * np.cos(theta), r * np.sin(theta))
def plot_segments(p, q, *args, **kwargs):
plt.plot([p.x, q.x], [p.y, q.y], *args, **kwargs)
def plot_rays(radii, thetas, *args, **kwargs):
endpoints = polar_to_cartesian(radii, thetas)
n = endpoints.shape
origin = Coord(np.zeros(n), np.zeros(n))
plot_segments(origin, endpoints, *args, **kwargs)
## Data generation
M = 5 # size of the canvas
N = 10 # number of segments
K = 16 # number of rays
q = Coord(*np.random.uniform(-M/2, M/2, size=(2, N)))
p = q + Coord(*np.random.uniform(-M/2, M/2, size=(2, N)))
thetas = np.linspace(0, 2 * np.pi, K, endpoint=False)
## For each ray, find the minimal distance of intersection
## with all segments
plt.figure(figsize=(5, 5))
plot_segments(p, q, "royalblue", marker=".")
for theta in thetas:
radii = find_intersect_btw_ray_and_sgmts(p, q, theta)
radius = np.min(radii)
if not np.isinf(radius):
plot_rays(radius, theta, color="orange")
else:
plot_rays(2*M, theta, ':', c='orange')
plt.plot(0, 0, 'kx')
plt.xlim(-M, M)
plt.ylim(-M, M)
And that's not all! Thanks to the broadcasting of python, it is possible to avoid iteration on theta values. For example, recall that np.array([1, 2, 3]) * np.array([[1], [2], [3], [4]]) produces a matrix of size 4 × 3 of the pairwise products. In the same way Coord([[5],[7]], [[5],[1]]) * Coord([2, 4, 6], [-2, 4, 0]) outputs a 2 × 3 matrix containing all the pairwise cross product between vectors (5, 5), (7, 1) and (2, -2), (4, 4), (6, 0).
Finally, the intersections can be determined in the following way:
radii_all = find_intersect_btw_ray_and_sgmts(p, q, np.vstack(thetas))
# p and q have a shape of (N,) and np.vstack(thetas) of (K, 1)
# this radii_all have a shape of (K, N)
# radii_all[k, n] contains the distance from the origin of the intersection
# between k-th ray and n-th segment (or np.inf if there is no intersection point)
radii = np.min(radii_all, axis=1)
# radii[k] contains the distance from the origin of the closest intersection
# between k-th ray and all segments
do_intersect = ~np.isinf(radii)
plot_rays(radii[do_intersect], thetas[do_intersect], color="orange")
plot_rays(2*M, thetas[~do_intersect], ":", color="orange")
When defining this trajectory of a cannonball function, I would like for the loop to stop at negative values of y. IE the cannonball should not continue moving once hitting the ground.
I tried using while >= 0, but y points is a list and I don't know how to do that.
Any help?
def trajectory(v_0=1, mass=1, theta=np.pi/2, t_0=0, t_f=2, n=100):
"""
:param v_0: initial velocity, in meters per second
:param mass: Mass of the object in kg, set to 1kg as default
:param theta: Angle of the trajectory's shot, set to pi/2 as default
:param t_0: initial time, in seconds, default as 0
:param t_f: final time, in seconds, default as 2
:param n: Number of points
:return: array with x and y coordinates of the trajectory
"""
h = (t_f - t_0)/n
t_points = np.arange(t_0, t_f, h)
x_points = []
y_points = []
r = np.array([0, v_0 * np.cos(theta), 0, v_0 * np.sin(theta)], float)
for t in t_points:
x_points.append(r[0])
y_points.append(r[2])
k1 = h * F(r, t, mass)
k2 = h * F(r + 0.5 * k1, t + 0.5 * h, mass)
k3 = h * F(r + 0.5 * k2, t + 0.5 * h, mass)
k4 = h * F(r + k3, t + h, mass)
r += (k1 + 2 * k2 + 2 * k3 + k4) / 6
return np.array(x_points, float), np.array(y_points, float)
when graphing the trajectory, I get a graph that contains negative values of y, which I would like to prevent from being calculated in the first place in order to not affect the code's performance.
You don't need to convert to a while loop. The simplest way I can think of is to put an exit condition in the for loop:
for t in t_points:
if r[2] < 0:
break
...
This will exit the loop when the y position is less than zero. You might consider making the condition r[2] < 0 and r[3] < 0 so that if you have one starting below the ground, it makes its way up before coming down and colliding with the ground.
If you really have your heart set on a while loop, you can make an iterator variable, and then use it to iterate through t_points:
iterator_variable = 0
while r[2] < 0 and iterator_variable < len(t_points):
t = t_points[iterator_variable]
...
return np.array(x_points, float), np.array(y_points, float)
While I don't know what your function F() does, I think it might be easier to go without defining n, h, or t_points. You would start at the starting point, and calculate each next point until you hit the ground. This strategy lends itself nicely to a while loop.
while r[2] > 0:
//calculate next position and velocity
//add that position to the list of x_points and y_points
Instead of n as an input to your function which dictates the number of points you calculate, you could have it be a point density measure, or a maximum number of points.
I am trying to run a numerical integration code for my research project. It is a 3-atom system which undergo only Lennard-Jones force. However r_x variable remains 0 during the process. Unfortunately I couldn't figure out why. This is the output of the code:
[[ 1. 9. 15.]
[ 1. 9. 15.]
[ 1. 9. 15.]
[ 1. 9. 15.]
[ 1. 9. 15.]
[ 1. 9. 15.]
[ 1. 9. 15.]
[ 1. 9. 15.]
[ 1. 9. 15.]
[ 1. 9. 15.]]
When I check all the variables' values, I saw that r_x has just one value and it's zero during the process.
import numpy as np
np.seterr(invalid = "ignore")
m = 1
x = np.array([1, 9, 15])
y = np.array([16, 22, 26])
def GetLJForce(r, epsilon, sigma):
return 48 * epsilon * np.power(sigma, 12) / np.power(r, 13) - 24 * epsilon * np.power(sigma, 6) / np.power(r, 7)
def GetAcc(xPositions, yPositions):
global xAcc
global yAcc
xAcc = np.zeros((xPositions.size, xPositions.size), dtype=object)
yAcc = np.zeros((xPositions.size, xPositions.size), dtype=object)
for i in range(0, xPositions.shape[0]-1):
for j in range(i+1, xPositions.shape[0]-1):
global r_x
r_x = xPositions[j] - xPositions[i]
r_y = yPositions[j] - yPositions[i]
global rmag
rmag = np.sqrt(r_x*r_x + r_y*r_y)
if(rmag[0]==0 or rmag[1]==0 or rmag[2]==0):
rmag += 1
force_scalar = GetLJForce(rmag, 0.84, 2.56)
force_x = force_scalar * r_x / rmag
force_y = force_scalar * r_y / rmag
xAcc[i,j] = force_x / m
xAcc[j,i] = - force_x / m
yAcc[i,j] = force_y / m
yAcc[j,i] = - force_y / m
else:
force_scalar = GetLJForce(rmag, 0.84, 2.56)
force_x = force_scalar * r_x / rmag
force_y = force_scalar * r_y / rmag
xAcc[i,j] = force_x / m
xAcc[j,i] = - force_x / m
yAcc[i,j] = force_y / m
yAcc[j,i] = - force_y / m
return np.sum(xAcc), np.sum(yAcc)
def UpdatexPos(x, v_x, a_x, dt):
return x + v_x*dt + 0.5*a_x*dt*dt
def UpdateyPos(y, v_y, a_y, dt):
return y + v_y*dt + 0.5*a_y*dt*dt
def UpdatexVel(v_x, a_x, a1_x, dt):
return v_x + 0.5*(a_x + a1_x)*dt
def UpdateyVel(v_y, a_y, a1_y, dt):
return v_y + 0.5*(a_y + a1_y)*dt
def RunMD(dt, number_of_steps, x, y):
global xPositions
global yPositions
xPositions = np.zeros((number_of_steps, 3))
yPositions = np.zeros((number_of_steps, 3))
v_x = 0
v_y = 0
a_x = GetAcc(xPositions, yPositions)[0]
a_y = GetAcc(xPositions, yPositions)[1]
for i in range(number_of_steps):
x = UpdatexPos(x, v_x, a_x, dt)
y = UpdateyPos(y, v_y, a_y, dt)
a1_x = GetAcc(xPositions, yPositions)[0]
a1_y = GetAcc(xPositions, yPositions)[1]
v_x = UpdatexVel(v_x, a_x, a1_x, dt)
v_y = UpdateyVel(v_y, a_y, a1_y, dt)
a_x = np.array(a1_x)
a_y = np.array(a1_y)
xPositions[i, :] = x
yPositions[i, :] = y
return xPositions, yPositions
sim_xpos = RunMD(0.1, 10, x, y)[0]
sim_ypos = RunMD(0.1, 10, x, y)[1]
print(sim_xpos)
There are some details with your code, the main reason that it isn't running is because of this line return np.sum(xAcc), np.sum(yAcc), you calculated a matrix of accelerations from the interactions between all the particles where you added the acceleration of one particle and the inverse of that acceleration to the other, since the mass of all particles is the same then the accelerations are the same, then you sum all the elements of the matrix, so all the terms cancel out, and instead of returning the acceleration of each particle you return the sum of the accelerations of ALL the particles, and given that they all have the same mass, is just 0, even if it was not 0 it would have been wrong because you where mixing them all, so all the particles would have the same acceleration and move in the same direction.
Details purely about the code
1
In that same function you have also something that can be improved, you have something like
if condition:
A_statement
B_statement
else:
B_statement
that is the same as
if condition:
A_statement
B_statement
since B_statement is always going to get executed.
2
You don't have to use global on the variables you used it, where you use them they are still in scope, basically use it when you are going to use that variable in other functions, basically everything that has the same number of tabulations or more knows about that variable, and that ends in the first line that is less tabulated than that one, that leads to the next point.
3
def UpdatexVel(v_x, a_x, a1_x, dt):
return v_x + 0.5*(a_x + a1_x)*dt
def UpdateyVel(v_y, a_y, a1_y, dt):
return v_y + 0.5*(a_y + a1_y)*dt
There is no need to make this two identical functions, the only difference is the name, they make the same thing, the name of the parameters of the function go out of scope when you end the function body, so you could reuse the a single function for both x and y.
Some examples
def fun(a):
return a
x = a # NameError: name 'a' is not defined
a is not in scope when we use it for x, it only existed in the body of fun, so you could reuse that name for other variable or parameter.
a = 1
def fun(a):
return 2 + a
print( fun(7) ) # = 9
That happens because a is shadowed by the function parameter of the same name, while
a = 1
def fun():
return a
print( fun() ) # = 1
because when fun() looks for a it doesn't find it in its scope, so it looks at a higher scope, and finds a variable a that holds the value 1
4
return xPositions, yPositions
sim_xpos = RunMD(0.1, 10, x, y)[0]
sim_ypos = RunMD(0.1, 10, x, y)[1]
This is smaller, but python has tuples so this code that repeats the calculations of RunMD (which you don't want), can be simplified to
return xPositions, yPositions
sim_xpos, sim_ypos = RunMD(0.1, 10, x, y)
where the pair that is returned by RunMD is assined to the pair (a tuple of two elements for python) sim_xpos and sim_ypos.
If I where to rewrite parts of that code, I would remove numpy to test the algorithm, and then bring numpy to vectorize the operations and make the code much more efficient, it would look something like this
import math
def cart_to_polar(x, y):
rho = math.sqrt(x**2 + y**2)
phi = math.atan2(y, x)
return rho, phi
def polar_to_cart(rho, theta):
x = rho * math.cos(theta)
y = rho * math.sin(theta)
return x, y
def GetLJForce(r, epsilon, sigma):
return 48 * r * epsilon * ( ( ( sigma / r ) ** 14 ) - 0.5 * ( ( sigma / r ) ** 8 ) ) / ( sigma ** 2 )
def GetAcc(x_positions, y_positions, m):
xAcc = [0]*len(x_positions)
yAcc = [0]*len(x_positions)
for i in range(0, len(x_positions)-1):
for j in range(i+1, len(x_positions)):
# calculations are made from the point of view of i, and then flipped for j
delta_x = x_positions[j] - x_positions[i]
delta_y = y_positions[j] - y_positions[i]
radius, theta = cart_to_polar(delta_x, delta_y)
# in case two particles are at the same place
# this is some times called a cutoff radius
if radius==0: radius = 1e-10
force_mag = GetLJForce(radius, 0.84, 2.56)
# since the polar coordinates are centered in the i particle the result is readilly usable
# particle j interaction with particle i
force_x, force_y = polar_to_cart(force_mag, theta)
xAcc[i] += force_x / m[i]
yAcc[i] += force_y / m[i]
# flip the sing of the force to represent the
# particle i interaction with particle j
force_x, force_y = polar_to_cart(-force_mag, theta)
xAcc[j] += force_x / m[j]
yAcc[j] += force_y / m[j]
return xAcc, yAcc
def update_pos(x, v, a, dt):
return x + v*dt + 0.5 * a * dt ** 2
def update_vel(v, a, dt):
return v + a * dt
def runMD(dt, x, y, v_x, v_y, m):
num_particles = len(x)
a_x, a_y = GetAcc(x, y, m)
for i in range(num_particles):
v_x[i] = update_vel(v_x[i], a_x[i], dt)
v_y[i] = update_vel(v_y[i], a_y[i], dt)
for i in range(num_particles):
x[i] = update_pos(x[i], v_x[i], a_x[i], dt)
y[i] = update_pos(y[i], v_y[i], a_y[i], dt)
return x, y
# number of particles in the system
num_particles = 3
# mass of the particles
m = [1] * num_particles
# starting positions
x = [1, 9, 15]
y = [16, 22, 26]
# starting velocities
v_x = [0] * num_particles
v_y = [0] * num_particles
# number of steps of the simulation
number_of_steps = 10
# the simulation
for i in range(number_of_steps):
x, y = runMD(0.1, x, y, v_x, v_y, m)
print(x, y)
I don't know if the physics is precisely how it should be for molecular dynamics, when I was in physics I only made simulations of dynamical systems, maybe my approach is too precise or classical for your system, either way you can use it to compare.
I have an numpy array that represents my voxelgrid.. Now i want to add values to the surface of a sphere for a given radius. What is the fastest way?
My solution:
def spheric Surface (x, y, z, r, value):
while phi <= (2*math.pi):
eta = math.pi * 2 / 3
while eta <= math.pi:
xx = x + r * math.sin(eta) * math.cos(phi)
yy = y + r * math.sin(eta) * math.sin(phi)
zz = z + r * math.cos(eta)
xx = int(xx*resoultion+0.5)
yy = int(yy*resolution+0.5)
zz = int(zz*resolution+0.5)
voxelGrid[xx][yy][zz] += value
eta += 1/10 * math.pi
phi += 1/10 * math.pi
This is my first Idea: It ist not very fast and not very accurate because with bigger r, i need more angle to calculate.., not just adding 1/10pi for example but 1/5pi, but this makes the code even slower...
Resolution is the resolution of my voxelgrid.. so with Resolution 3, x=2mm would become xx= 6 in the array..
And yes i dont want the whole surface of the sphere, just from 2/3pi to pi...
Is there any better and faster way?
I tried the way with the mask like this, but it is even slower:
def sphericSurface(x, y, z, r, value):
tol = 0.6
grenz = math.pi * 2 / 3
mask = (np.logical_and(np.logical_and((sx[:, None, None] - x) ** 2 + (sy[None, :, None] - y) ** 2 + (sz[None, None, :] - z) ** 2 <= (r + tol)**2,
(sx[:, None, None] - x) ** 2 + (sy[None, :, None] - y) ** 2 + (sz[None, None, :] - z) ** 2 >= (r - tol)**2),
(sz[None, None, :] - z) <= (r*math.cos(grenz))))
x, y, z = np.where(mask==True)
z *= 2
voxelGrid[x,y,z] += value
You can select all of the elements that require modification by generating a mask. I'm not sure how compatible this is which what you already have, but this is the way. It'll basically blow the doors off of the while loop solution speed-wise.
import numpy as np
x = np.arange(0.0,5.0,0.1)
y = np.arange(0.0,5.0,0.1)
z = np.arange(0.0,5.0,0.1)
points = np.array(np.meshgrid(x,y,z)).T
def make_mask(points,a,b,c,r,tol=1e-2):
"""generates a boolean mask of positions within tol distance of the surface of the sphere
(x-a)**2 + (y-b)**2 + (z-c)**2 = r**2"""
mask = (points[...,0]-a)**2+(points[...,1]-b)**2+(points[...,2]-c)**2 < (r+tol)**2
return mask
mask = make_mask(points,2.5,2.5,2.5,1.0,tol=0.2)
# this will tell you all of the points in voxelgrid which need modification
voxelgrid[mask] #will return them all
If you want to add a value to every point near the surface of the sphere you can do
voxelgrid[mask]+=value
provided that the voxelgrid and points coordinates coincide in the sense that voxelgrid[i,j,k] is the container associated with the point points[i,j,k].. you will have to use your resolution parameter to make the x,y,z so that this is true.
Here's a lame plot showing that it works for me:
The code for this plot is
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(*points[mask].T)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
plt.savefig('works.png')
You can maybe calculate the mask more cleanly with something like:
x0 = np.array([a,b,c])
mask = np.sum((points-x0)**2,axis=-1)<(r+tol)**2
but it's a little harder to read. It may be faster ? I am not sure on this. (can anyone weigh in? )
I have written a Python script to calculate the distance between two points in 3D space while accounting for periodic boundary conditions. The problem is that I need to do this calculation for many, many points and the calculation is quite slow. Here is my function.
def PBCdist(coord1,coord2,UC):
dx = coord1[0] - coord2[0]
if (abs(dx) > UC[0]*0.5):
dx = UC[0] - dx
dy = coord1[1] - coord2[1]
if (abs(dy) > UC[1]*0.5):
dy = UC[1] - dy
dz = coord1[2] - coord2[2]
if (abs(dz) > UC[2]*0.5):
dz = UC[2] - dz
dist = np.sqrt(dx**2 + dy**2 + dz**2)
return dist
I then call the function as so
for i, coord2 in enumerate(coordlist):
if (PBCdist(coord1,coord2,UC) < radius):
do something with i
Recently I read that I can greatly increase performance by using list comprehension. The following works for the non-PBC case, but not for the PBC case
coord_indices = [i for i, y in enumerate([np.sqrt(np.sum((coord2-coord1)**2)) for coord2 in coordlist]) if y < radius]
for i in coord_indices:
do something
Is there some way to do the equivalent of this for the PBC case? Is there an alternative that would work better?
You should write your distance() function in a way that you can vectorise the loop over the 5711 points. The following implementation accepts an array of points as either the x0 or x1 parameter:
def distance(x0, x1, dimensions):
delta = numpy.abs(x0 - x1)
delta = numpy.where(delta > 0.5 * dimensions, delta - dimensions, delta)
return numpy.sqrt((delta ** 2).sum(axis=-1))
Example:
>>> dimensions = numpy.array([3.0, 4.0, 5.0])
>>> points = numpy.array([[2.7, 1.5, 4.3], [1.2, 0.3, 4.2]])
>>> distance(points, [1.5, 2.0, 2.5], dimensions)
array([ 2.22036033, 2.42280829])
The result is the array of distances between the points passed as second parameter to distance() and each point in points.
import numpy as np
bounds = np.array([10, 10, 10])
a = np.array([[0, 3, 9], [1, 1, 1]])
b = np.array([[2, 9, 1], [5, 6, 7]])
min_dists = np.min(np.dstack(((a - b) % bounds, (b - a) % bounds)), axis = 2)
dists = np.sqrt(np.sum(min_dists ** 2, axis = 1))
Here a and b are lists of vectors you wish to calculate the distance between and bounds are the boundaries of the space (so here all three dimensions go from 0 to 10 and then wrap). It calculates the distances between a[0] and b[0], a[1] and b[1], and so on.
I'm sure numpy experts could do better, but this will probably be an order of magnitude faster than what you're doing, since most of the work is now done in C.
I have found that meshgrid is very useful for generating distances. For example:
import numpy as np
row_diff, col_diff = np.meshgrid(range(7), range(8))
radius_squared = (row_diff - x_coord)**2 + (col_diff - y_coord)**2
I now have an array (radius_squared) where every entry specifies the square of the distance from the array position [x_coord, y_coord].
To circularize the array, I can do the following:
row_diff, col_diff = np.meshgrid(range(7), range(8))
row_diff = np.abs(row_diff - x_coord)
row_circ_idx = np.where(row_diff > row_diff.shape[1] / 2)
row_diff[row_circ_idx] = (row_diff[row_circ_idx] -
2 * (row_circ_idx + x_coord) +
row_diff.shape[1])
row_diff = np.abs(row_diff)
col_diff = np.abs(col_diff - y_coord)
col_circ_idx = np.where(col_diff > col_diff.shape[0] / 2)
col_diff[row_circ_idx] = (row_diff[col_circ_idx] -
2 * (col_circ_idx + y_coord) +
col_diff.shape[0])
col_diff = np.abs(row_diff)
circular_radius_squared = (row_diff - x_coord)**2 + (col_diff - y_coord)**2
I now have all the array distances circularized with vector math.