A naive simulator of gravity, in less than 200 Python lines...
(Coded during a 2010 summer night...)
I've always been fascinated by space - ever since I read
"The Family of the Sun", when I was young. And I always wanted
to simulate what I've read about Newton's law of gravity, and see
what happens in... a universe of my own making... :-)
See this in full-screen (it's HD) - and wait for 30 seconds to see the action unfold...
Here's what I did:
The following code "sprays" 200 "particles" randomly, inside a
900x600 window (the values are in the code, change them at will).
It then proceeds to simulate two very simple laws:
Gravity, inversely proportional to the square of the distance,
and linearly proportional to the product of the two masses
Elastic "mergings" of two objects if they are close enough to
touch: they are replaced by a merged object that maintains their
momentum (mass times velocity) and has the accumulated mass of the two objects.
Well, the result appears a bit goofy at start, but 30 seconds later,
the simulation becomes alive! The resulting motions appear very natural,
and more importantly, the remaining objects have such momentum,
that they leave the original "window" with great speeds, travelling
into space... (When you reach this stage, use the 'A'/'Z' keys to zoom
in/out and see the "expansion of the universe"...)
Yes, I've proven the Big Bang!
I want my Nobel prize now, thank you :-)
(More of my "semi-scientific" models of natural processes here... Enjoy!)
#!/usr/bin/env python"""A naive simulator of gravity, in less than 200 linesCoded by Thanassis Tsiodras, during a 2010 summer night...(http://users.softlab.ntua.gr/~ttsiod)Well, I've always been fascinated by space - ever since I read 'The Family of the Sun', when I was young. And I always wantedto simulate what I've read about Newton's gravity law, and seewhat happens in... a universe of my own making...Well, here's what I did: The following code 'sprays' 200 'particles' randomly, inside a900x600 window (the values are below, change them at will).Afterwards, it applies a very simple set of laws:- Gravity, inversely proportional to the square of the distance, and linearly proportional to the product of the two masses- Elastic collissions of two objects if they are close enough to touch: a merged object is then created, that maintains the momentum (mass*velocity) and the mass of the two merged ones.Well, I probably goofed at something, but I like the results -they appear very natural to me, and more importantly, after half a minute or so, the remaining objects have such momentum,that they leave the original 'window' with great speeds, travellinginto space...Yes, I've proven the Big Bang!I want my Nobel prize now, thank you :-)(When you reach this stage, use the 'A' and 'Z' keys to zoomin/out and see the 'expansion of the universe'...)Released under the GPL, code available at:http://users.softlab.ntua.gr/~ttsiod/gravity.html"""import sysimport mathimport pygameimport random# The window sizeWIDTH =900HEIGHT =600WIDTHD2 = WIDTH/2.HEIGHTD2 = HEIGHT/2.# The number of simulated particlesPARTICLES =200# The density of the particles - used to calculate their mass # from their volume (i.e. via their radius)DENSITY =0.001# The gravity coefficient. # It's my universe, I can pick whatever I want.GRAVITYSTRENGTH =1.e4class Particle:def__init__(self):# Position self._x =float(random.randint(0,WIDTH)) self._y =float(random.randint(0,HEIGHT))# Speed self._vx =0. self._vy =0.# Radius self._radius =1.5# Use density to calculate mass from radius self.setMassFromRadius()# When two particles are close enough to touch:# - _merged will be set to True for the lighter one# - it will no longer be simulated, since its mass# will be added to the larger one, and its momentum # (mass*velocity) will be merged to the larger one's# (see code for merging particles below, in main loop) self._merged = FalsedefresetAccel(self):# Acceleration, set to 0 in each "calculate total force" loop self._ax =0.0 self._ay =0.0defaddAccel(self, particle):# Calculate the acceleration contribution from another particle dx = particle._x - self._x dy = particle._y - self._y dsq = dx*dx + dy*dy # distance squared dr = math.sqrt(dsq)# distance force = GRAVITYSTRENGTH*self._mass*particle._mass/dsq if dsq>1e-10else0.# Accumulate acceleration... self._ax += force*dx/dr self._ay += force*dy/drdefupdatePos(self):# Afrer "addAccel" has been called to account for all other particles,# update my position and my speed... self._x += self._vx self._y += self._vy self._vx += self._ax self._vy += self._ay self.resetAccel()# prepare for next rounddefsetMassFromRadius(self):# The volume is (4/3)*Pi*(r^3), so... self._mass = DENSITY *4.* math.pi *(self._radius**3.)/3.defsetRadiusFromMass(self):# Reversing the previous formula, to calculate radius from mass# (used after merging of two particles - mass is added, and new# radius is calculated from this formula) self._radius =(3.*self._mass/(DENSITY *4.*math.pi))**(0.3333)defpanic(msg): sys.stderr.write(msg+"\n") sys.exit(1)defmain(): pygame.init() win=pygame.display.set_mode((WIDTH,HEIGHT))# And God said: Let there be lights in the firmament of the heavens... particles =[] keysPressed ={pygame.K_a:0, pygame.K_z:0, pygame.K_ESCAPE:0}for i inxrange(0,PARTICLES): particles.append(Particle())# Zoom factor, changed at runtime via the 'A' and 'Z' keys zoom =1.0while True: pygame.display.flip() win.fill((0,0,0)) win.lock()for particle in particles:ifnot particle._merged:# for those that have not been merged,# Draw a circle as big as their radius, but take zoom # factor into account pygame.draw.circle(win,(255,255,255),(int(WIDTHD2 + zoom*WIDTHD2*(particle._x-WIDTHD2)/WIDTHD2),int(HEIGHTD2 + zoom*HEIGHTD2*(particle._y-HEIGHTD2)/HEIGHTD2)),int(particle._radius*zoom),0) win.unlock()while True:# Update the keysPressed dictionary, so we know what keys are pressed evt = pygame.event.poll()if evt.type == pygame.NOEVENT:breakelif evt.type == pygame.KEYDOWN: keysPressed[evt.key]=1elif evt.type == pygame.KEYUP:if keysPressed.has_key(evt.key)and keysPressed[evt.key]==1: keysPressed[evt.key]=0break# Update all particle positions and speeds, by running a loop on each one...for i inxrange(0,len(particles)):if particles[i]._merged:continue# ...and calculating the contributions of all the others to its velocity# (via the gravity force) particles[i].resetAccel()for j inxrange(0,len(particles)):if i == j or particles[j]._merged:continue# not pulled by myself :-) particles[i].addAccel(particles[j]) particles[i].updatePos()# See if we should merge the ones that are close enough to touch...for i inxrange(0,len(particles)):if particles[i]._merged:continuefor j inxrange(0,len(particles)):if i == j or particles[j]._merged:continue dx = particles[i]._x - particles[j]._x dy = particles[i]._y - particles[j]._y dsq = dx*dx + dy*dy dr = math.sqrt(dsq)if dr<=(particles[i]._radius + particles[j]._radius):# Merge us, we are close enough to touch!if particles[i]._mass > particles[j]._mass: ii,jj = i,jelse: ii,jj = j,i# ii is the index of the biggest one (mass-wise) particles[jj]._merged = True# maintain the momentum - elastic collisions... newvx =( particles[ii]._vx*particles[ii]._mass +\ particles[jj]._vx*particles[jj]._mass)/\(particles[ii]._mass + particles[jj]._mass) newvy =( particles[ii]._vy*particles[ii]._mass +\ particles[jj]._vy*particles[jj]._mass)/\(particles[ii]._mass + particles[jj]._mass)# maintain the mass (just add them) particles[ii]._mass += particles[jj]._mass# based on the new mass, figure out the new radius particles[ii].setRadiusFromMass()# update velocity of merged object, to maintain momentum particles[ii]._vx = newvx particles[ii]._vy = newvy# if 'A' or 'Z' are pressed, update zoom factor:if keysPressed[pygame.K_a]: zoom /=0.99print"Zoom factor:", zoomif keysPressed[pygame.K_z]: zoom /=1.01print"Zoom factor:", zoomif keysPressed[pygame.K_ESCAPE]:breakif evt.type == pygame.NOEVENT: pygame.time.wait(20)# don't get beyond 50 frames per second...continueif __name__ =="__main__":try:import psyco psyco.profile()except:print'Psyco not found, ignoring it'main()