Physics simulator in water python - python

I'm having issues understanding how to simulate a situation like this: http://phet.colorado.edu/sims/density-and-buoyancy/buoyancy_en.html
The point of the program is to make a simulator - like the one in the link. I want to keep it realistic and use Python. I want to draw the simulation in Pygame.
In my program I ask for a couple of variables; the mass and the radius. The radius will be used to calculate the volume of a sphere and the mass will be used to calculate the buoyancy, gravity force and acceleration.
But the thing is, to keep everything in SI-units I ask for the radius in metre. Doing this while keeping my radius under 10cm, makes for a really small number. And when I use the Pygame module to draw a sphere at the size of 0.1m, it fails. So instead of doing that, I needed to use a bigger scale.
So here comes my main problem. How exacly should I scale the sphere? Say I wanted to define 100 pixels to be 1 metre. I would then have to multiply my radius by 100, since that would be the scale, but now that the sphere is bigger should the velocity also be multiplied by 100?
I've gotten really confused over this! Thanks for your time.
Don't know if you need to see this, anyhow.
Calculations.py
import math
class Formulas():
def __init__(self):
self.pi = 3.1415926535
self.gravity = 9.82 #m/s^2
self.density_water = 1000.0 #kg/m^3
self.density_air = 1.29 #kg/m^3
self.drag_sphere = 0.47
def object_buoyancy(self, volume, medium_density):
buoyancy = volume * medium_density * self.gravity #N
return buoyancy
def object_gravity(self, mass):
gravity_force = mass * self.gravity #N
return gravity_force
def object_volume_sphere(self, radius):
volume = 1.3333333 * self.pi * math.pow(radius, 3) #m^3
return volume
def object_mass(self, density, volume):
mass = volume * density #kg
return mass
def object_acceleration(self, gravity_force, buoyancy, mass):
total_force = gravity_force - buoyancy #N
acceleration = total_force / mass #m/s^2
return acceleration
def object_speed(self, acceleration, time, scale):
speed = acceleration * (float(time)/1000.0) #m/s
return speed
def surface_area(self, radius):
area = 4 * self.pi * math.pow(radius, 2)
return area

Like so many problems in physics, this one can be solved using dimensional analysis.
Let us take a look at those constants you defined:
self.pi = 3.1415926535
self.gravity = 9.82 #m/s^2
self.density_water = 1000.0 #kg/m^3
self.density_air = 1.29 #kg/m^3
self.drag_sphere = 0.47
In order to be consistent and scale all of your distances so that 1 m = 100 px, we need to scale your constants:
self.pi = 3.1415926535
self.gravity = 982.0 #px/s^2
self.density_water = 0.001 #kg/px^3
self.density_air = 0.00000129 #kg/px^3
self.drag_sphere = 0.47
The only other thing you have to do is increase your radius variable by a factor of 100. After that, the rest of your calculations will fall in line:
Your volume calculation will be correct, so
Your mass and surface area calculations will change, so
Your gravity and bouyancy will change, so
Your acceleration will change, so
Finally, your velocity will be correct.
In your equations/methods, I do not see where your medium_density is set, so that my have to change as well. But, in the end, all you have to do is scale all of your inputs that have a unit of "distance" and your output variable will be scaled correctly.

Related

How to create a (math) function for the position of a billiard ball when considering friction

so I'm creating a billiard ball simulation. The current version I have calculates the new position of the ball for each step, but I would like to create a math function (f(x)) for the balls position. This is not too hard, but what is really tripping me out is getting it to work with friction.
For the ball I have the following relevant information: Speed/velocity, position/startpos and friction coefficient.
I am able to calculate the distance the ball moves in a single step by raising the friction coeff to the power of x. The problem is that I can only get it to work by calculating the new position step by step. I have illustrated it in Ti-Nspire:
If this is in the end is harder and less efficient that just updating each second, please let me know. If you have a solution to how I could get it as a function or a better solution, please let me know too. Thanks for any help in advance:)
Having the velocity diminish proportionally to its current magnitude in continuous time (vs. discrete time steps) is pretty much the definition of exponential decay. That means the continuous time function corresponding to your discrete-stepped scaling would be
V(t) = V0 e-λt
where V0 is the initial velocity at time 0 and λ is the rate of decay. We need to calibrate the decay rate to correspond with your friccoef, which means we want
V(1) = friccoef * V0,
and thus
e-λ*1 = friccoef
yielding
λ = -ln(friccoef).
We can then derive the distance covered as a function of time t by integrating the velocity from 0 to t. The resulting formula for the position at time t, if movement starts at time t0 with velocity Vt0 and initial location Xt0, is
Xt = Xt0 + Vt0 (1 - e-λ(t - t0)) / λ.
To show the continuous time evolution of the function I used gnuplot with t0 = 0, V0 = 32, X0 = 123, and friccoef = 0.9:
Bringing it back around to stackoverflow relevance, the formulae above can be implemented straightforwardly in Python:
from math import log, exp
def rate(friction_coeff):
return -log(friction_coeff)
def position(elapsed_time, x_0, v_0, friction_coeff):
lmbda = rate(friction_coeff)
return x_0 + v_0 * (1.0 - exp(-lmbda * elapsed_time)) / lmbda
def velocity(elapsed_time, v_0, friction_coeff):
return v_0 * exp(-rate(friction_coeff) * elapsed_time)
def time_to(destination, x_0, v_0, friction_coeff):
lmbda = rate(friction_coeff)
return -log(1.0 - lmbda * (destination - x_0) / v_0) / lmbda
A few simple test cases...
# A few test cases
x_0 = 2
destination = 12
v_0 = 5
friction_coeff = 0.9
t = time_to(destination, x_0, v_0, friction_coeff)
print(f"Time to go from {x_0} to {destination} starting at velocity {v_0} is {t}")
print(f"Position at time {t} is calculated to be {position(t, x_0, v_0, friction_coeff)}")
print(f"Velocity at time {t} is {velocity(t, v_0, friction_coeff)}")
produce the following output:
Time to go from 2 to 12 starting at velocity 5 is 2.24595947233019
Position at time 2.24595947233019 is calculated to be 12.00000
Velocity at time 2.24595947233019 is 3.946394843421737

N-body simulation python

I am trying to code an N-body simulation code in python and have successfully managed to produce a system involving the Sun, Earth and Jupiter as below using a leapfrog approximation method.
However, when I try and extend the same code for N bodies of equal mass all with zero velocity, I don't get the expected result of a system forming. Instead, the following is produced where the bodies spread out after initially being attracted to each other.
This same pattern is replicated regardless of the number of initial particles used.
This second image is just an enlarged version of the first showing they are initially attracted to each other.
Leading me to believe the error must lie in my initial conditions:
N = 3
mass = 1e30
R = 1e10
V = np.zeros([N,3])
M = np.full([N],mass)
P = np.random.uniform(-R, R, (N,3))
epsilon = 0.1 * R
acceleration calculation:
def calc_acceleration(position, mass, softening):
G = 6.67 * 10**(-11)
N = position.shape[0] # N = number of rows in particle_positions array
acceleration = np.zeros([N,3])
#print(N)
for i in range(N):
#print(i)
for j in range(N):
if i != j:
#print("j", j)
dx = position[i,0] - position[j,0]
dy = position[i,1] - position[j,1]
dz = position[i,2] - position[j,2]
#print(dx,dy,dz)
inv_r3 = ((dx**2 + dy**2 + dz**2 + softening**2)**(-1.5))
acceleration[i,0] += - G * mass[j] * dx * inv_r3
acceleration[i,1] += - G * mass[j] * dy * inv_r3
acceleration[i,2] += - G * mass[j] * dz * inv_r3
return(acceleration)
leap frog functions:
def calc_next_v_half(position, mass, velocity, softening, dt):
half_velocity = np.zeros_like(velocity)
half_velocity = velocity + calc_acceleration(position, mass, softening) * dt/2
return(half_velocity)
def calc_next_position(position, mass, velocity, dt):
next_position = np.zeros_like(position)
next_position = position + velocity * dt
return(next_position)
actual program function:
def programe(position, mass, velocity, softening, time, dt):
no_of_time_steps = (round(time/dt))
all_positions = np.full((no_of_time_steps, len(mass), 3), 0.0)
all_velocities = []
kinetic_energy = []
potential_energy = []
total_energy = []
for i in range(no_of_time_steps):
all_positions[i] = position
all_velocities.append(velocity)
'leap frog'
velocity = calc_next_v_half(position, mass, velocity, softening, dt)
position = calc_next_position(position, mass, velocity, dt)
velocity = calc_next_v_half(position, mass, velocity, softening, dt)
return(all_positions, all_velocities, kinetic_energy, potential_energy, total_energy)
The problem is that the symplectic methods only have their special properties as long as the systems stays well away from any singularities. For a gravitational system this is the case if it is hierarchical like in a solar system with sun, planets and moons where all orbits have low eccentricities.
However, if you consider a "star cluster" with objects of about equal mass, you do not get Kepler ellipses and the likelihood for very close encounters becomes rather high. The more so as your initial condition of zero velocity results in an initial free fall of all stars towards the common center of gravity, as can also be seen in your detail picture.
Due to the potential energy falling down into a singularity, the kinetic energy increases as the distance decreases, so close encounters equal high speed. With a constant step size like in the leapfrog-Verlet method, the sampling rate becomes too small to represent the curve, capture the swing-by fully. Energy conservation is grossly violated and the high speed is kept beyond the close encounter, leading to the unphysical explosion of the system.

Atomic Simulation Using Pygame [duplicate]

I would like to make some kind of solar system in pygame. I've managed to do a fixed one but I thought it would be more interesting to do one with planets moving around the sun and moons around planets etc. Is there a way I could do that (using pygame if possible)?
What I would like is :
Sun = pygame.draw.circle(...)
planet1 = pygame.draw.circle(...)
etc.
a = [planet1, planet2, ...]
for p in a:
move p[2] to pos(x, y)
That is what I think would work but I'm not sure how to do it. Also, I've thought about deleting the ancient planet and drawing a new one right next to it, but problem is I'm using random features (like colours, distance to the sun, number of planets in the system etc.) and it would have to keep these same features. Any ideas?
Thanks in advance!
You can implement gravity with Newton's Law of Universal Gravitation and Newton's Second Law to get the accelerations of the planets. Give each planet an initial position, velocity and mass. Acceleration is change in velocity a = v * dt, velocity is change in position v = r * dt, so we can integrate to find velocity and position.
Universal gravitation: F = G * m1 * m2 / r ** 2 where F is the magnitude of the force on the object, G is the gravitational constant, m1 and m2 are the masses of the objects and r is the distance between the two objects.
Newton's Second Law: F = m1 * a where a is the acceleration.
dt = 0.01 # size of time step
G = 100 # gravitational constant
def calcGravity(sun, planet):
'Returns acceleration of planet with respect to the sun'
diff_x = sun.x - planet.x
diff_y = sun.y - planet.y
acceleration = G * sun.mass / (diff_x ** 2 + diff_y ** 2)
accel_x = acceleration * diff_x / (diff_x ** 2 + diff_y ** 2)
accel_y = acceleration * diff_y / (diff_x ** 2 + diff_y ** 2)
return accel_x, accel_y
while True:
# update position based on velocity
planet.x += planet.vel_x * dt
planet.y += planet.vel_y * dt
# update velocity based on acceleration
accel_x, accel_y = calcGravity(sun, planet)
planet.vel_x += accel_x * dt
planet.vel_y += accel_y * dt
This can produce circular and elliptical orbits. Creating an orbiting moon requires a very small timestep (dt) for the numeric integration.
Note: this approach is subtly inaccurate due to the limits of numeric integration.
Sample implementation in pygame here, including three planets revolving around a sun, a moon, and a basic orbital transfer.
https://github.com/c2huc2hu/orbital_mechanics
Coordinates of a planet rotated about the Sun through some angle with respect to the X-axis are , where r is the distance to the Sun, theta is that angle, and (a, b) are the coordinates of the sun. Draw your circle centered at (x, y).
EDIT:
General elliptical orbit:
Where
r0 is the radius of a circular orbit with the same angular momentum, and e is the "eccentricity" of the ellipse

Calculating direction after a 2d circle collision

I know how to calculate the scalar of the velocity vector after a collision with 2 circles
(as per this link: https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331)
These circles cannot rotate and do not have friction but can have different masses, however I cannot seem to find out any way to find the unit vector that I need to multiply the scalar of velocity by to get the new velocity of the particles after the collision.
I also know how to check if 2 circles are colliding.
Also, I am only dealing with this in a purely "maths-sense" (ie. the circles have a center and a radius), and would like to know how I can represent these circles on the screen in python 3.0.
The vector class:
class Vector():
def __init__(self,x,y):
self.x = x
self.y = y
def add(self, newVector):
return Vector(self.x+newVector.x, self.y+newVector.y)
def subtract(self,newVector):
return Vector(self.x-newVector.x, self.y-newVector.y)
def equals(self, newVector):
return Vector(newVector.x,newVector.y)
def scalarMult(self, scalar):
return Vector(self.x*scalar, self.y*scalar)
def dotProduct(self, newVector):
return (self.x*newVector.x)+(self.y*newVector.y
def distance(self):
return math.sqrt((self.x)**2 +(self.y)**2)
The circle class:
class Particles():
def __init__(self,currentPos, oldPos, accel, dt,mass, center, radius):
self.currentPos = currentPos
self.oldPos = oldPos
self.accel = accel
self.dt = dt
self.mass = mass
self.center = center
self.radius = radius
def doVerletPosition(currentPos, oldPos, accel, dt):
a = currentPos.subtract(oldPos)
b = currentPos.add(a)
c = accel.scalarMult(dt)
d = c.scalarMult(dt)
return d.add(b)
def doVerletVelocity(currentPos, oldPos, dt):
deltaD = (currentPos.subtract(oldPos))
return deltaD.scalarMult(1/dt)
def collisionDetection(self, center, radius):
xCenter = (self.radius).xComponent()
yCenter = (self.radius).yComponent()
xOther = radius.xComponent()
yOther = radius.yComponent()
if ((xCenter - xOther)**2 + (yCenter-yOther)**2 < (self.radius + radius)**2):
return True
else:
return False
I do know about AABBs, but I am only using around 10 particles for now, and AABBs are not necessary now.
You know that the force transmitted between the two discs has to go along the "normal vector" for this collision, which is easy to get - it's just the vector along the line connecting the centers of the two discs.
You have four constraints: conservation of momentum (which counts for two constraints since it applies in x and y), conservation of energy, and this "force along normal" constraint. And you have four unknowns, namely the x and y components of the final velocities. Four equations and four unknowns, you can solve for your answer. Due to my background in physics, I've written this out in terms of momentum instead of velocity, but hopefully that's not too hard to parse. (Note, for instance, that kinetic energy is equal to p**2/2m or 1/2 mv**2.)
## conservation of momentum
p_1_x_i + p_2_x_i = p_1_x_f + p_2_x_f ## p_1_x_i := momentum of disc _1_ in _x_ axis intially _i
p_1_y_i + p_2_x_i = p_1_y_f + p_2_y_f
## conservation of energy
(p_1_x_i**2 + p_1_y_i**2)/(2*m_1) + (p_2_x_i**2 + p_2_y_i**2)/(2*m_2) = (p_1_x_f**2 + p_1_y_f**2)/(2*m_1) + (p_2_x_f**2 + p_2_y_f**2)/(2*m_2)
## impulse/force goes along the normal vector
tan(th) := (x_2-x_1)/(y_2-y_1) # tangent of the angle of the collision
j_1_x := p_1_x_i - p_1_x_f # change in momentum aka impulse
j_1_y := p_1_y_i - p_1_y_f
tan(th) = -j_1_x/j_1_y
(I hope the notation is clear. It would be much clearer if I could use latex, but stackoverflow doesn't support it.)
Hope this helps!

Python Drawing a Circle with X Radius Using Forward()

I'm using Python Turtles to draw a circle using forward() and right().
I have a for loop counting from 0 to 359, and each time it triggers, it moves the turtle forward 1 and right 1.
But the problem is I need specific diameters. I am nearly 100% sure I'll need to use trig, but I've tried to no avail.
I can't figure out the math how to do it. We're supposed to use forward() and right(), NOT circle().
Thanks!
Here is a working example:
import turtle
import math
def circle(radius):
turtle.up()
# go to (0, radius)
turtle.goto(0,radius)
turtle.down()
turtle.color("black")
# number of times the y axis has been crossed
times_crossed_y = 0
x_sign = 1.0
while times_crossed_y <= 1:
# move by 1/360 circumference
turtle.forward(2*math.pi*radius/360.0)
# rotate by one degree (there will be
# approx. 360 such rotations)
turtle.right(1.0)
# we use the copysign function to get the sign
# of turtle's x coordinate
x_sign_new = math.copysign(1, turtle.xcor())
if(x_sign_new != x_sign):
times_crossed_y += 1
x_sign = x_sign_new
return
circle(100)
print('finished')
turtle.done()
Well, a complete circle is 360°, and you are planning on turning 360 times, so each turn should be:
right( 360 ° / 360 ), or
right(1)
The distance traveled will be one circumference, or π * diameter, so your forward might be:
forward( diameter * π / 360 )
I haven't tested this yet -- give it a try and see how it works.
This is one of the exercises in "Think Python," in chapter 4. It really is a horrible exercise to have this early in the book, especially with the "hint" given. I'm using forward and left here, but you can switch left with right.
You should have the polygon function:
def polygon(t, length, n):
for i in range(n):
bob.fd(length)
bob.lt(360 / n)
Then you create a circle function:
def circle(t):
polygon(t, 1, 360)
That will draw a circle, no radius needed. The turtle goes forward 1, then left 1 (360 / 360), 360 times.
Then, if you want to make the circle bigger, you calculate the circumference of the circle. The hint says:
Hint: figure out the circumference of the circle and make sure that
length * n = circumference.
Ok, so the formula for circumference = 2 * pi * radius. And the hint says length * n = circumference. n = 360 (number of sides/degrees). We have circumference, so we need to solve for length.
So:
def circle(t, r):
circumference = 2 * 3.14 * r
length = circumference / 360
polygon(t, length, 360)
Now, call the function with whatever radius you want:
circle(bob, 200)

Categories