As part of a vector class/ "physics engine" I am making, I am trying to implement a velocity verlet algorithm to solve Newton's equations of motion. However, the algorithm that I have implemented is producing massive values, as you can see below. i have tried what I believe to be every option on my own, and was wondering if someone could help. Thanks.
Xpos = 0
OldPos = 1
dt = 0.02
Xaccel = 9.81
def doVerletX(currentPos, oldPos, accel, dt):
newPos = (currentPos + (currentPos-oldPos) + (accel*dt*dt))
return newPos
for a in range (1,10,1):
newPos = doVerletX(Xpos,OldPos,dt,Xaccel)
print(newPos)
OldPos = Xpos
dt+=dt
The output to the above code was:
0.9247220000000003
3.8494440000000005
7.698888000000001
15.397776000000002
30.795552000000004
61.59110400000001
123.18220800000002
246.36441600000003
492.72883200000007
I know that this is obviously wrong and was wondering what to do to fix it?
Already mentioned was the dt+=dt problem that should be t+=dt.
In the time propagation, you also need to shift the newPos value to Xpos.
The interface definition for doVerlet has dt as last argument, you have to use it that way also in the function call.
Xpos = 0
OldPos = 1
t=0
dt = 0.02
Xaccel = 9.81
def doVerletX(currentPos, oldPos, accel, dt):
newPos = (currentPos + (currentPos-oldPos) + (accel*dt*dt))
return newPos
for a in range (1,10,1):
newPos = doVerletX(Xpos,OldPos,Xaccel,dt)
t+=dt
print(t,newPos)
OldPos, Xpos = Xpos, newPos
Using that code gives the result
(0.02, -0.996076)
(0.04, -1.988228)
(0.06, -2.976456)
(0.08, -3.960760)
(0.10, -4.941140)
(0.12, -5.917596)
(0.14, -6.890128)
(0.16, -7.858736)
(0.18, -8.823420)
which is believable as the initial velocity is -50 m/s and the acceleration towards the positive.
To get the correct initial velocity and thus exact solution one would have to solve
x(t)=x0+v0*t+0.5*g*t^2
for v0 at t=-0.02 using x(0)=x0=0 and x(-0.02)=1 giving
v0=-(1-0.02^2/2*9.81)/0.02=-49.9019 and x(0.18)=-8.82342
which is exactly the value computed above. This is as it should be with an order 2 method.
Related
This is a small part of some more complicated code but basically. I've narrowed down the problem to this one bit of code.
I'm using the formula s = ut + 1/2 at^2 to get the distance traveled in one instance to time, in this case, 0.001 seconds which will then further be used to draw the rects on a window.
import pygame
run = True
clock = pygame.time.Clock()
ex_s = pygame.Rect((500, 625, 50, 50))
start_height = ex_s.top
print(start_height, "m")
g = -9.8
v = 400
t = 0.001
while run:
ex_s.top -= (v * t) + ((g / 2) * (t ** 2))
print(ex_s.top - start_height)
if (ex_s.top - start_height) * -1 <= -10:
run = False
clock.tick(30)
I want the rectangles to move upwards/downwards based on their velocity.
The rectangles move up and get stuck whenever the top reaches y = 0. This doesn't happen if the value of "t" is increased to o.o1 or above but I need it to be 0.001 for the precision. The problem is also solved when the value of v is increased by an order of magnitude, changing g does nothing.
I tried using both move and move_ip and the y coordinate did not change at all.
I've also tried manually pushing the objects above y = 0 when they hit it but it doesn't do anything. The fact that the problem changes with decimal places has led me to believe it could be something to do with the integer coordinate system of pygame but I don't see how this would cause things to move first and then get stuck only at 0.
See Pygame doesn't let me use float for rect.move, but I need it. The problem is that a pygame.Rect object can only store integer data. So when you add a value less than 1.0 to the coordinate of a rectangle, than the position stays the same. You need to store the position of the rectangle in separate variables and update the position of the rectangle using these variables.
ex_s = pygame.Rect((500, 625, 50, 50))
ex_s_y = ex_s.top
# [...]
while run:
# [...]
# change position
ex_s_y = .....
ex_s.top = round(ex_s_y)
You didn't change the time t variable, and your formula for the motion is wrong. Also your initial time, initial velocity and g are wrong.
If you want to use an exact solution for the position, do like this.
dt = 0.05
t = 0
g = 9.8 # It's positive by definition.
v = -100 # It's upward.
while run:
y = 625 + v*t + 0.5*g*t**2 # The acceleration is +g, downward.
t += dt
ex_s.top = y
...
If you want to solve the equation of motion approximately and iteratively, do like this inside the loop.
...
y = 625
while run:
v += g * dt
y += v * dt
t += dt
ex_s.top = y
...
The above method is called sympletic Euler method. There're other more accurate methods, such as Runge-Kutta method.
I try to solve projectile motion problem under air resistance and wind effect. When knowing the initial conditions includes initial velocity, position and angle of projectile, I can solve the problem using scipy.integrate.solve_ivp. In the next step, I want to find out the projectile motion knowing only initial velocity (but not initial angle) or only initial angle (but not initial velocity) of projectile and 1 more condition that this motion will go through point C(xC, yC) at time T. I did refer to Ordinary differential equation - Odeint with unknown initial value in python, jupyter but I still cannot apply it in my problem. Here are my code trying to find initial value of angle knowing initial value of velocity v0:
def dSdt(t,S):
x, vx, y, vy = S
wind_speed_x, wind_speed_y = wind_speed
g = 9.81
return [vx,
-B*np.sqrt((vx-wind_speed_x)**2+(vy-wind_speed_y)**2)*(vx-wind_speed_x),
vy,
-g-B*np.sqrt((vx-wind_speed_x)**2+(vy-wind_speed_y)**2)*(vy-wind_speed_y)]
def bvp_ode(s,u,T): return T*np.asarray(dSdt(s*T,u))
def bvp_bc(uA,uB,T):
xA, yA = uA[0],uA[2]
xB, yB = uB[0],uB[2]
return np.array([xA-posA[0],yA-posA[1],xB-posB[0],yB-posB[1], velA**2-v0**2])
if __name__ == '__main__':
posA = [0,0]
posB = [100,10]
velA = 50
v0 = 50
T = 100 # or dis(posA,posB)/v0#
timeInterval = [0,100]
time_interval = np.linspace(timeInterval[0],timeInterval[1],1000)
y_0 = np.zeros((4,1000))
y_0[0][0] = posA[0]
y_0[2][0] = posA[1]
y_0[0][999] = posB[0]
y_0[2][999] = posB[1]
B = 25
wind_speed = [10,10]
res = solve_bvp(bvp_ode,bvp_bc,time_interval,y_0, p=[T])
I'm trying to find how the vertical velocity Vy of a spherical particle falling vertically with drag changes as a function of time t. I would like to generate a list of values that state what Vy is for several points in time from the time at which is is released to some t_max as well as plot Vy vs time.
The equation I've been given to solve for ∆Vy is:
dVy = -g*dt - (b/m)*Vy*dt
and I've been given a suggested algorithm for finding the values
"The code should ask for the values of m, g, b and ∆t. Once these values are defined, the code should provide a series of values of Vy for each time t."
Here's my attempt so far:
import numpy as np
import matplotlib.pyplot as plt
import math
g = 9.81 #acceleration due to gravity
b = 1.6*10**-8 #linear drag coefficient
m = 1.04*10**-9 #mass of particle
dt = 10**-3 #time step
t_max = 10 #final time
t = 0 #initial time
Vy = 0 #initial velocity
t_list = [t]
Vy_list = [Vy]
dVy = -g*dt - (b/m)*Vy*dt
while t <= t_max:
t += dt
Vy += dVy
t_list.append(t)
Vy_list.append(Vy)
print(t, Vy)
plt.plot(t_list, Vy_list)
plt.show()
I'm not sure that the values it generates are quite right considering that no matter what size I set the mass to the change in velocity remains the same when I'd expect it to affect the velocity quite a bit for infinitesimally smaller masses.
I'll admit that I'm replicating a solution that was given to me for a completely different problem, but am I right in thinking that a while loop is the way to go?
You need to update dVy in the while loop:
while t <= t_max:
dVy = -g*dt - (b/m)*Vy*dt
t += dt
Vy += dVy
Otherwise it will be a constant and not update as you expect over time.
I have written some python code to solve the N-body problem using the Euler method. The code runs without problems and seems to give a reasonable answer (e.g. if there are two particles then the start moving towards each other). However when I run this simulation over a large number of iterations I see that the particles (say I run it with two particles) pass by each other (I do not consider collisions) and keep going in their directions indefinitely. This violates the conservation of energy and so there must be a flaw in my code but I am unable to find it. Could anyone please find it and explain my mistake.
Thank you.
Thanks to #samgak for pointing out that I was updating the particles twice. I have now fixed this but the problem still keeps coming. I have also replicated the output I get when I run this simulation with two stationary particles at (0,0) and (1,0) with a time step of 1 second and 100000 iterations:
Particle with mass: 1 and position: [234.8268420043934, 0.0] and velocity: [0.011249111128594091, 0.0]
Particle with mass: 1 and position: [-233.82684200439311, 0.0] and velocity: [-0.011249111128594091, 0.0]
Also thanks to #PM2Ring for pointing out some optimizations I could make and the perils of using the Euler method.
Code:
import math
class Particle:
"""
Class to represent a single particle
"""
def __init__(self,mass,position,velocity):
"""
Initialize the particle
"""
self.G = 6.67408*10**-11 #fixed throughout the simulation
self.time_interval = 10**0 #fixed throughout the simulation, gives the interval between updates
self.mass = mass
self.position = position #should be a list
self.velocity = velocity #should be a list
self.updated_position = position
self.updated_velocity = velocity
def __str__(self):
"""
String representation of particle
"""
return "Particle with mass: " + str(self.mass) + " and position: " + str(self.position) + " and velocity: " + str(self.velocity)
def get_mass(self):
"""
Returns the mass of the particle
"""
return self.mass
def get_position(self):
"""
returns the position of the particle
"""
return self.position
def get_velocity(self):
"""
returns the velocity of the particle
"""
return self.velocity
def get_updated_position(self):
"""
calculates the future position of the particle
"""
for i in range(len(self.position)):
self.updated_position[i] = self.updated_position[i] + self.time_interval*self.velocity[i]
def update_position(self):
"""
updates the position of the particle
"""
self.position = self.updated_position.copy()
def get_distance(self,other_particle):
"""
returns the distance between the particle and another given particle
"""
tot = 0
other = other_particle.get_position()
for i in range(len(self.position)):
tot += (self.position[i]-other[i])**2
return math.sqrt(tot)
def get_updated_velocity(self,other_particle):
"""
updates the future velocity of the particle due to the acceleration
by another particle
"""
distance_vector = []
other = other_particle.get_position()
for i in range(len(self.position)):
distance_vector.append(self.position[i]-other[i])
distance_squared = 0
for item in distance_vector:
distance_squared += item**2
distance = math.sqrt(distance_squared)
force = -self.G*self.mass*other_particle.get_mass()/(distance_squared)
for i in range(len(self.velocity)):
self.updated_velocity[i] = self.updated_velocity[i]+self.time_interval*force*(distance_vector[i])/(self.mass*(distance))
def update_velocity(self):
"""
updates the velocity of the particle
"""
self.velocity = self.updated_velocity.copy()
def update_particles(particle_list):
"""
updates the position of all the particles
"""
for i in range(len(particle_list)):
for j in range(i+1,len(particle_list)):
particle_list[i].get_updated_velocity(particle_list[j])
particle_list[j].get_updated_velocity(particle_list[i])
for i in range(len(particle_list)):
particle_list[i].update_velocity()
particle_list[i].get_updated_position()
for i in range(len(particle_list)):
particle_list[i].update_position()
#the list of particles
partList = [Particle(1,[0,0],[0,0]),Particle(1,[1,0],[0,0])]
#how many iterations I perform
for i in range(100000):
update_particles(partList)
#prints out the final position of all the particles
for item in partList:
print(item)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Further Edit:
I decided to implement the Leapfrog method and I have developed some code that once again runs and seems to work well (at least in the command line). However when I added plotting functionality and analysed it there seemed to be another problem. Again the system seemed to go too far and the energy again increased without bound. I have attached a picture of the output I get to showcase the problem. If I again had just two particles of equal mass they again pass each other and continue away from each other without stopping. Thus there must be a bug in my code I am not finding.
If anyone can help it will be much appreciated.
My Code:
import math
import matplotlib.pyplot as plt
class Particle:
"""
Represents a single particle
"""
def __init__(self,mass,position,velocity):
"""
Initialize the particle
"""
self.G = 6.67408*10**-11
self.time_step = 10**2
self.mass = mass
self.dimensions = len(position)
self.position = position
self.velocity = velocity
self.acceleration = [0 for i in range(len(position))]
self.next_position = position
self.next_velocity = velocity
self.next_acceleration = [0 for i in range(len(position))]
def __str__(self):
"""
A string representation of the particle
"""
return "A Particle with mass: " + str(self.mass) + " and position: " + str(self.position) + " and velocity:" + str(self.velocity)
def get_mass(self):
return self.mass
def get_position(self):
return self.position
def get_velocity(self):
return self.velocity
def get_acceleration(self):
return self.acceleration
def get_next_position(self):
return self.next_position
def put_next_position(self):
for i in range(self.dimensions):
self.next_position[i] = self.position[i] + self.time_step*self.velocity[i]+0.5*self.time_step**2*self.acceleration[i]
def put_next_velocity(self):
for i in range(self.dimensions):
self.next_velocity[i] = self.velocity[i] + 0.5*self.time_step*(self.acceleration[i]+self.next_acceleration[i])
def update_position(self):
self.position = self.next_position.copy()
def update_velocity(self):
self.velocity = self.next_velocity.copy()
def update_acceleration(self):
self.acceleration = self.next_acceleration.copy()
def reset_acceleration(self):
self.acceleration = [0 for i in range(self.dimensions)]
def reset_future_acceleration(self):
self.next_acceleration = [0 for i in range(self.dimensions)]
def calculate_acceleration(self,other_particle):
"""
Increments the acceleration of the particle due to the force from
a single other particle
"""
distances = []
other = other_particle.get_position()
distance_squared = 0
for i in range(self.dimensions):
distance_squared += (self.position[i]-other[i])**2
distances.append(self.position[i]-other[i])
distance = math.sqrt(distance_squared)
force = -self.G*self.mass*other_particle.get_mass()/distance_squared
acc = []
for i in range(self.dimensions):
acc.append(force*distances[i]/(distance*self.mass))
for i in range(self.dimensions):
self.acceleration[i] += acc[i]
def calculate_future_acceleration(self,other_particle):
"""
Increments the future acceleration of the particle due to the force from
a single other particle
"""
distances = []
other = other_particle.get_next_position()
distance_squared = 0
for i in range(self.dimensions):
distance_squared += (self.next_position[i]-other[i])**2
distances.append(self.next_position[i]-other[i])
distance = math.sqrt(distance_squared)
force = -self.G*self.mass*other_particle.get_mass()/distance_squared
acc = []
for i in range(self.dimensions):
acc.append(force*distances[i]/(distance*self.mass))
for i in range(self.dimensions):
self.next_acceleration[i] += acc[i]
def update_all(particleList):
for i in range(len(particleList)):
particleList[i].reset_acceleration()
for j in range(len(particleList)):
if i != j:
particleList[i].calculate_acceleration(particleList[j])
for i in range(len(particleList)):
particleList[i].put_next_position()
for i in range(len(particleList)):
particleList[i].reset_future_acceleration()
for j in range(len(particleList)):
if i != j:
particleList[i].calculate_future_acceleration(particleList[j])
for i in range(len(particleList)):
particleList[i].put_next_velocity()
for i in range(len(particleList)):
particleList[i].update_position()
particleList[i].update_velocity()
partList = [Particle(1,[0,0],[0,0]),Particle(1,[1,0],[0,0])]
Alist = [[],[]]
Blist = [[],[]]
for i in range(10000):
Alist[0].append(partList[0].get_position()[0])
Alist[1].append(partList[0].get_position()[1])
Blist[0].append(partList[1].get_position()[0])
Blist[1].append(partList[1].get_position()[1])
update_all(partList)
plt.scatter(Alist[0],Alist[1],color="r")
plt.scatter(Blist[0],Blist[1],color="b")
plt.grid()
plt.show()
for item in partList:
print(item)
Could someone please tell me where is the error I am making in my code.
The main problem in the code is that it uses the Euler method which is quite inaccurate as the number of iterations increase (just O(h) compared to other methods which can be O(h^4) or even better). To fix this would require a fundamental restructure of the code and so I would say that this code is not really accurate for an N-body simulation (it plays up for 2 particles, as I add more and more the error can only increase).
Thanks to #samgak and #PM2Ring for helping me remove a bug and optimize my code but overall this code is unusable...
EDIT: I have implemented the leapfrog method mentioned in the comments from scratch and have found it to work perfectly. It is very simple to understand and implement and it works too!
Further EDIT: I thought I had the leapfrog method working. Turns out that there was another bug in it I only saw when I added GUI functionality.
I'm creating a program to find the optimum angle to launch a projectile at a given speed, in order to hit a target a given distance away.
i have this code:
def Trajectory(angle):
position = vector(0,0,0)
poscurve.plot(pos=position)
velocity = v0 * vector(cos(angle*pi/180), sin(angle*pi/180), 0)
while (position.y > 0) or (velocity.y > 0):
dx = velocity * dt # update dx and dv
dv = g * dt
position = position + dx # apply the updates to velocity and position
velocity = velocity + dv
poscurve.plot(pos=position)
return position
The problem is, this program repeats this function multiple times, with different angles. When it repeats it, it connects the first point of the new curve to the last point of the previous curve, because the function uses the same poscurve.plot(pos=position) every time. How do I make the function generate a new curve each time it iterates this function?
(there are variables defined outside of this code snippet, the program conceptually works fine, just how the graph looks)
Add poscurve as a parameter:
def Trajectory(angle,poscurve):
position = vector(0,0,0)
poscurve.plot(pos=position)
velocity = v0 * vector(cos(angle*pi/180), sin(angle*pi/180), 0)
while (position.y > 0) or (velocity.y > 0):
dx = velocity * dt # update dx and dv
dv = g * dt
position = position + dx # apply the updates to velocity and position
velocity = velocity + dv
poscurve.plot(pos=position)
return position
Then, pass in a new poscurve every time. You can just call gcurve again to create a new object.