Polyergic

see http://bit.ly/SZfe3U

Jul 31st, 2012
90
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.41 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. #
  4. # Honors project for PHYS 4700 - Electricity & Magnetism (milestone 9)
  5. # Shad Sterling <[email protected]>
  6. #
  7. # Green particles have negative charge, Red particles have positive charge
  8. # Blue arrows represent velocity, Yellow arrows represent acceleration
  9. # Grey grid arrows are scaled electric field vectors
  10. #
  11. # Add particles by clicking on empty space (new particles will alternate charge)
  12. # Move particles by drag & drop (next new particle will have opposite charge as moved particle)
  13. # Remove particles by clicking without dragging (next new particle will have same charge)
  14. # Advance motion by clicking on any motion arrow (velocity or acceleration)
  15. # Continuously advance motion by dragging on a motion arrow and holding button down
  16. #
  17.  
  18.  
  19. from __future__ import division
  20. from visual import *
  21. import random
  22. import os
  23. import inspect
  24. import pprint
  25. import gc
  26. import time
  27.  
  28. ## - use for real units
  29. #arena_size = 65   # arena size in meters (significantly smaller sizes are problematic)
  30. #arena_grid = 24   # number of gridpoints across each dimension of the arena
  31. #step_time = 0.1   # simulation time per step in seconds
  32. #frame_time = 0.5  # minimum real time per step in seconds when continuously stepping
  33.  
  34. ## - use for simplified units
  35. arena_size = 26
  36. arena_grid = 24
  37. step_time = 0.5
  38. frame_time = 0.5
  39.  
  40.  
  41.  
  42. class Arena:
  43.     grid_base = 0.25     # minimum length for grid vectors as proportion of grid spacing
  44.     grid_max = 1/4       # maximum length for grid vectors as proportion of arena dimension
  45.     rebound_ratio = 0.1  # proportion of velocity reflected from boundary
  46.     coulomb_constant = 1 # 8.9875517873681764 * 10**9 # N*m*m/C/C # simplified: 1
  47.  
  48.     def __init__( self, size, grid, scene ):
  49.         self.size = size # size of arena in each dimension in meters
  50.         self.boundary = size/2
  51.         self.particles = []
  52.         self.grid = []
  53.         scene.autoscale = false
  54.         scene.autocenter = false
  55.         scene.range = self.boundary
  56.         self.regrid( grid ) # setup indicator grid
  57.         self.accel_limit = self.size
  58.  
  59.     def regrid( self, grid ):
  60.         for point in self.grid:
  61.             point.hide()
  62.         self.grid_size = ceil(abs(grid)) # gridpoint count in each dimension
  63.         self.grid_spacing = self.size/(self.grid_size+1) # distance in meters between gridpoints (no points on edges)
  64.         self.grid_base = self.grid_spacing * Arena.grid_base
  65.         self.grid_max = self.size * Arena.grid_max
  66.         start = -self.boundary + self.grid_spacing
  67.         stop = self.boundary - self.grid_spacing*1.5
  68.         x = start
  69.         y = self.boundary - self.grid_spacing
  70.         i = self.grid_size ** 2 #number of gridpoints
  71.         while i > 0:
  72.             self.grid.append( GridPoint( self, vector( x, y, 0 ) ) )
  73.             if x < stop:
  74.                 x += self.grid_spacing
  75.             else:
  76.                 x = start
  77.                 y -= self.grid_spacing
  78.             i = i - 1
  79.         for particle in self.particles:
  80.             show = particle.Avatar.visible
  81.             particle.Avatar.hide()
  82.             particle.avatar = Avatar( particle )
  83.             if show:
  84.                 particle.Avatar.show()
  85.  
  86.     def capacitor( self, count, length, width, pos, angle ):
  87.         c = cos( angle )
  88.         s = sin( angle )
  89.         count = count - 1
  90.         next = vector( length*s/count, length*c/count, 0 )
  91.         gap = vector( width*c/2, -width*s/2, 0 )
  92.         pos = vector(pos) - count*next/2
  93.         while( 0 <= count ):
  94.             self.electron( pos-gap )
  95.             self.positron( pos+gap )
  96.             pos = pos + next
  97.             count = count - 1
  98.  
  99.     def electron( self, position, velocity=(0,0,0) ):
  100.         new = Particle.electron( self, position, velocity )
  101.         self.add( new )
  102.         return new
  103.  
  104.     def positron( self, position, velocity=(0,0,0) ):
  105.         new = Particle.positron( self, position, velocity )
  106.         self.add( new )
  107.         return new
  108.  
  109.     def add( self, particle ):
  110.         self.particles.append( particle )
  111.         particle.reveal()
  112.  
  113.     def remove( self, particle ):
  114.         particle.hide()
  115.         self.particles.remove( particle )
  116.  
  117.     def bound( self, particle ):
  118.         i = 0
  119.         while i < 2:
  120.             if particle.position[i] > self.boundary:
  121.                 particle.position[i] = self.boundary
  122.                 particle.velocity[i] = -abs( particle.velocity[i] * self.rebound_ratio )
  123.             elif particle.position[i] < -self.boundary:
  124.                 particle.position[i] = -self.boundary
  125.                 particle.velocity[i] = abs( particle.velocity[i] * self.rebound_ratio )
  126.             i = i + 1
  127.  
  128.     def update( self ):
  129.         for point in self.grid:
  130.             point.update()
  131.         for particle in self.particles:
  132.             particle.update() # update acceleration
  133.  
  134.     def step( self, step_time ):
  135.         for particle in self.particles:
  136.             particle.step( step_time ) # update position & velocity
  137.             self.bound( particle )     # constrain to arena bounds
  138.         self.update()
  139.  
  140.     def sync( self ):
  141.         for particle in self.particles:
  142.             particle.sync()
  143.         for point in self.grid:
  144.             point.sync()
  145.  
  146. class GridPoint:
  147.     arrow_radius = 0.1
  148.     arrow_scale = 1 # 10 ** 10 # simplified: 1
  149.     color_electric = color.gray(0.7)
  150.  
  151.     def __init__( self, arena, position ):
  152.         self.arena = arena
  153.         self.position = position
  154.         self.avatar = arrow( pos = position, fixedwidth = true, color = GridPoint.color_electric,
  155.                              shaftwidth = GridPoint.arrow_radius * self.arena.grid_spacing,
  156.                              role = "grid" )
  157.         self.electric = vector( 0,0,0 )
  158.    
  159.     def update( self ):
  160.         electric = vector( 0,0,0 )
  161.         for particle in self.arena.particles:
  162.             range = self.position - particle.position
  163.             if 0 != particle.charge:
  164.                 electric += Arena.coulomb_constant * range * particle.charge / (range.mag**3)
  165.         electric.mag = self.arena.grid_base + sqrt(electric.mag)*GridPoint.arrow_scale # simplified
  166.         electric.mag = self.arena.grid_base + electric.mag * GridPoint.arrow_scale # real
  167.         if electric.mag > self.arena.grid_max:
  168.             electric.mag = 0
  169.         self.electric = electric
  170.         self.in_sync = false
  171.  
  172.     def sync( self ):
  173.         self.avatar.axis = self.electric
  174.  
  175. class Particle:
  176.     electron_mass = 1 # kilograms; real: 9.10938291 * 10**(-31) # kilograms # simplified: 1
  177.     electron_charge = -1 # -1.602176565 * 10**(-19) # Coulombs # simplified: -1
  178.  
  179.     @classmethod
  180.     def electron( self, arena, position, velocity=(0,0,0) ):
  181.         return self( arena, Particle.electron_mass, Particle.electron_charge, position, velocity )
  182.    
  183.     @classmethod
  184.     def positron( self, arena, position, velocity=(0,0,0) ):
  185.         return self( arena, Particle.electron_mass, -Particle.electron_charge, position, velocity )
  186.  
  187.     def __init__( self, arena, mass, charge, position, velocity, acceleration=(0,0,0) ):
  188.         self.arena = arena
  189.         self.mass = mass
  190.         self.charge = charge
  191.         self.position = vector( position )
  192.         self.velocity = vector( velocity )
  193.         self.acceleration = vector( acceleration )
  194.         self.avatar = Avatar( self )
  195.         self.in_sync = true #true when avatar matches
  196.  
  197.     def sync( self ): # synchronize avatar
  198.         self.avatar.update()
  199.         self.in_sync = true
  200.  
  201.     def step( self, step_time ): # step position & velocity
  202.         self.position += self.velocity * step_time
  203.         self.velocity += self.acceleration * step_time
  204.         self.in_sync = false #avatar is outdated
  205.  
  206.     def update( self ): # recalculate acceleration
  207.         electric = vector( 0, 0, 0 )
  208.         for particle in self.arena.particles:
  209.             if 0 != particle.charge and self is not particle:
  210.                 range = self.position - particle.position
  211.                 force = Arena.coulomb_constant * range * self.charge * particle.charge / (range.mag**3)
  212.                 electric += force
  213.         self.acceleration = max( self.arena.accel_limit, electric / self.mass )
  214.         self.in_sync = false #avatar is outdated
  215.  
  216.     def move( self, new_position ):
  217.         self.position = new_position
  218.         self.in_sync = false #avatar is outdated
  219.  
  220.     def hide( self ):
  221.         self.avatar.hide()
  222.  
  223.     def reveal( self ):
  224.         self.avatar.reveal()
  225.  
  226.     def dim( self ):
  227.         self.avatar.dim()
  228.  
  229.     def bright( self ):
  230.         self.avatar.bright()
  231.  
  232. class Avatar:
  233.     avatar_radius = 0.5 # for avatars of point particles
  234.     color_negative = (0,.9,0) # negative particles are (almost) green
  235.     color_positive = (1,.1,.1) # positive particles are (almost) red
  236.     arrow_radius = 0.35
  237.     arrow_scale = 3 # 1 # proportional length of velocity & acceleration arrows # simplified: 3
  238.     color_velocity = (0,.2,1) #velocity arrows are (almost) blue
  239.     color_acceleration = color.yellow #acceleration arrows are yellow
  240.  
  241.     @classmethod
  242.     def color_of( self, charge ):
  243.         if charge < 0:
  244.             return Avatar.color_negative
  245.         elif charge > 0:
  246.             return Avatar.color_positive
  247.  
  248.     @classmethod
  249.     def arrow_of( self, base, motion ): # velocity arrow
  250.         m = vector( motion )
  251.         m.mag = m.mag * Avatar.arrow_scale + base
  252.         return m
  253.  
  254.     def __init__( self, particle ):
  255.         self.radius = Avatar.avatar_radius * particle.arena.grid_spacing
  256.         self.arrow_width = Avatar.arrow_radius * particle.arena.grid_spacing
  257.         self.position = visual.sphere( color = Avatar.color_of( particle.charge ),
  258.                                        radius = self.radius, role = "particle", real = particle )
  259.         self.velocity = visual.arrow( shaftwidth = self.arrow_width, fixedwidth = true,
  260.                                       color = Avatar.color_velocity, role = "motion" )
  261.         self.acceleration = visual.arrow( shaftwidth = self.arrow_width, fixedwidth = true,
  262.                                           color = Avatar.color_acceleration, role = "motion" )
  263.         self.represents = particle
  264.         self.represents.avatar = self
  265.         self.update()
  266.  
  267.     def update( self ):
  268.         self.position.pos = self.represents.position
  269.         self.velocity.pos = self.represents.position
  270.         self.velocity.axis = Avatar.arrow_of( self.radius, self.represents.velocity )
  271.         self.acceleration.pos = self.represents.position
  272.         self.acceleration.axis = Avatar.arrow_of( self.radius, self.represents.acceleration )
  273.  
  274.     def hide( self ):
  275.         self.position.visible = false
  276.         self.velocity.visible = false
  277.         self.acceleration.visible = false
  278.         self.represents.visible = false
  279.  
  280.     def reveal( self ):
  281.         self.position.visible = true
  282.         self.velocity.visible = true
  283.         self.acceleration.visible = true
  284.         self.represents.visible = true
  285.  
  286.     def dim( self ):
  287.         self.position.opacity = 0.75
  288.         self.velocity.opacity = 0.75
  289.         self.acceleration.opacity = 0.75
  290.  
  291.     def bright( self ):
  292.         self.position.opacity = 1
  293.         self.velocity.opacity = 1
  294.         self.acceleration.opacity = 1
  295.  
  296.  
  297. arena = Arena( arena_size, arena_grid, scene )
  298. arena.capacitor( ceil(arena_grid/3), arena_size*2/3, arena_size/2, (0,0,0), 2*math.pi*random.random() )
  299. arena.positron( (0,0,0), rotate( (arena_size/4/Avatar.arrow_scale,0,0), 2*math.pi*random.random(), (0,0,1) ) )
  300.  
  301. negative = true
  302. target = None
  303. event = None
  304. step = false
  305. changed = true
  306. run = false
  307. new = false
  308.  
  309. ticksum = 0
  310. tickcount = 0
  311. tickmisscount = 0
  312. tickinterval = frame_time
  313.  
  314. while true:
  315.     if changed:
  316.         tickstart = time.time()
  317.         if step:
  318.             #print "stepping..."
  319.             arena.step( step_time )
  320.         arena.update() #recalculate forces
  321.         gc.collect()
  322.         changed = false
  323.         tickend = time.time()
  324.         ticktime = tickend - tickstart # tick time (not including waiting)
  325.         tickcount = tickcount + 1
  326.         ticksum = ticksum + ticktime
  327.         tickavg = ticksum/tickcount # average seconds per tick
  328.         #if ticktime > tickinterval:
  329.         #   tickmisscount += 1
  330.         #tickmissrate = tickmisscount / tickcount
  331.         tickinterval = max( frame_time, tickavg * 2 )
  332.         tickwait = max( 0, tickinterval - ticktime )
  333.         #print "tick", tickcount, "took", ticktime, "avg", tickavg, "sum", ticksum, "interval", \
  334.         #      tickinterval, "missed", tickmisscount, "rate", tickmissrate, "wait", tickwait
  335.         time.sleep( tickwait )
  336.         arena.sync() #update avatars
  337.     #else:
  338.         #print "refresh skipped"
  339.     if 0 == scene.mouse.events and ( run or None != target ) and None != event and event.drag:
  340.         #print "dragging"
  341.         newpos = vector( scene.mouse.pos[0], scene.mouse.pos[1], 0 )
  342.     else:
  343.         #print "awaiting event..."
  344.         event = scene.mouse.getevent()
  345.         newpos = vector( event.pos[0], event.pos[1], 0 )
  346.     if event.press:
  347.         if None != event.pick:
  348.             role = event.pick.role
  349.         else:
  350.             role = None
  351.         if "particle" == role:
  352.             target = event.pick.real
  353.             #print "press:  targeted", target, "at", target.position
  354.             startpos = vector( target.position )
  355.         elif "motion" == role:
  356.                 #print "press:  step"
  357.                 step = true
  358.                 changed = true
  359.                 run = true
  360.         elif None != target:
  361.                 #print "press:  replace at", newpos, "from", startpos
  362.                 target.move( newpos )
  363.                 arena.add( target )
  364.                 negative = target.charge > 0
  365.                 changed = true
  366.                 new = true
  367.         elif negative:
  368.             #print "press:  electron at", newpos
  369.             target = arena.electron( newpos )
  370.             startpos = vector(target.position)
  371.             negative = false
  372.             changed = true
  373.             new = true
  374.         else:
  375.             #print "press:  positron at", newpos
  376.             target = arena.positron( newpos )
  377.             startpos = vector(target.position)
  378.             negative = true
  379.             changed = true
  380.             new = true
  381.     elif event.drag:
  382.         if None != target:
  383.             #print "drag:  dragged to", newpos, "via", target.position, "from", startpos
  384.             target.move( newpos )
  385.             target.dim()
  386.             changed = true
  387.             drag = true
  388.         elif run:
  389.             #print "drag:  continue stepping"
  390.             step = true
  391.             changed = true
  392.         else: # how would this happen?
  393.             #print "drag:  no object targeted"
  394.             changed = false
  395.     elif event.release:
  396.         if None != target:
  397.             if false == new and target.position == startpos:
  398.                 #print "release: removed from", startpos
  399.                 arena.remove( target )
  400.                 changed = true
  401.             else:
  402.                 #print "release: dropped at", newpos, "from", startpos
  403.                 target.move( newpos )
  404.                 target.bright()
  405.                 negative = target.charge > 0
  406.                 target = None
  407.                 startpos = None
  408.                 changed = true
  409.         elif run:
  410.             #print "release:  end stepping"
  411.             step = false
  412.             run = false
  413.         else:
  414.             #print "release:  nothing in progress"
  415.             changed = false
  416.     else:
  417.         print "Unknown Event!", pprint.pprint( inspect.getmembers( event ) )
  418.         changed = false
Add Comment
Please, Sign In to add comment