Class: PendulumAnimationDemo
- Defined in:
- sample/demos-en/pendulum.rb,
sample/demos-jp/pendulum.rb
Overview
animated wave
Instance Method Summary collapse
-
#initialize(frame) ⇒ PendulumAnimationDemo
constructor
A new instance of PendulumAnimationDemo.
-
#recomputeAngle ⇒ Object
This procedure is the “business” part of the simulation that does simple numerical integration of the formula for a simple rotational pendulum.
-
#repeat ⇒ Object
This method ties together the simulation engine and the graphical display code that visualizes it.
-
#showPendulum(x = nil, y = nil) ⇒ Object
This procedure makes the pendulum appear at the correct place on the canvas.
-
#showPhase ⇒ Object
Update the phase-space graph according to the current angle and the rate at which the angle is changing (the first derivative with respect to time.).
Constructor Details
#initialize(frame) ⇒ PendulumAnimationDemo
Returns a new instance of PendulumAnimationDemo.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'sample/demos-en/pendulum.rb', line 52 def initialize(frame) # Create some structural widgets @pane = TkPanedWindow.new(frame, :orient=>:horizontal).pack(:fill=>:both, :expand=>true) # @pane.add(@lf1 = TkLabelFrame.new(@pane, :text=>'Pendulum Simulation')) # @pane.add(@lf2 = TkLabelFrame.new(@pane, :text=>'Phase Space')) @lf1 = TkLabelFrame.new(@pane, :text=>'Pendulum Simulation') @lf2 = TkLabelFrame.new(@pane, :text=>'Phase Space') # Create the canvas containing the graphical representation of the # simulated system. @c = TkCanvas.new(@lf1, :width=>320, :height=>200, :background=>'white', :borderwidth=>2, :relief=>:sunken) TkcText.new(@c, 5, 5, :anchor=>:nw, :text=>'Click to Adjust Bob Start Position') # Coordinates of these items don't matter; they will be set properly below @plate = TkcLine.new(@c, 0, 25, 320, 25, :width=>2, :fill=>'grey50') @rod = TkcLine.new(@c, 1, 1, 1, 1, :width=>3, :fill=>'black') @bob = TkcOval.new(@c, 1, 1, 2, 2, :width=>3, :fill=>'yellow', :outline=>'black') TkcOval.new(@c, 155, 20, 165, 30, :fill=>'grey50', :outline=>'') # pack @c.pack(:fill=>:both, :expand=>true) # Create the canvas containing the phase space graph; this consists of # a line that gets gradually paler as it ages, which is an extremely # effective visual trick. @k = TkCanvas.new(@lf2, :width=>320, :height=>200, :background=>'white', :borderwidth=>2, :relief=>:sunken) @y_axis = TkcLine.new(@k, 160, 200, 160, 0, :fill=>'grey75', :arrow=>:last) @x_axis = TkcLine.new(@k, 0, 100, 320, 100, :fill=>'grey75', :arrow=>:last) @graph = {} 90.step(0, -10){|i| # Coordinates of these items don't matter; # they will be set properly below @graph[i] = TkcLine.new(@k, 0, 0, 1, 1, :smooth=>true, :fill=>"grey#{i}") } # labels @label_theta = TkcText.new(@k, 0, 0, :anchor=>:ne, :text=>'q', :font=>'Symbol 8') @label_dtheta = TkcText.new(@k, 0, 0, :anchor=>:ne, :text=>'dq', :font=>'Symbol 8') # pack @k.pack(:fill=>:both, :expand=>true) # Initialize some variables @points = [] @theta = 45.0 @dTheta = 0.0 @length = 150 # animation loop @timer = TkTimer.new(15){ repeat } # binding @c.(btag = TkBindTag.new) btag.bind('Destroy'){ @timer.stop } btag.bind('1', proc{|x, y| @timer.stop; showPendulum(x.to_i, y.to_i)}, '%x %y') btag.bind('B1-Motion', proc{|x, y| showPendulum(x.to_i, y.to_i)}, '%x %y') btag.bind('ButtonRelease-1', proc{|x, y| showPendulum(x.to_i, y.to_i); @timer.start }, '%x %y') btag.bind('Configure', proc{|w| @plate.coords(0, 25, w.to_i, 25)}, '%w') @k.bind('Configure', proc{|h, w| h = h.to_i w = w.to_i @psh = h/2; @psw = w/2 @x_axis.coords(2, @psh, w-2, @psh) @y_axis.coords(@psw, h-2, @psw, 2) @label_theta.coords(@psw-4, 6) @label_dtheta.coords(w-6, @psh+4) }, '%h %w') # add Tk.update @pane.add(@lf1) @pane.add(@lf2) # init display showPendulum # animation start @timer.start(500) end |
Instance Method Details
#recomputeAngle ⇒ Object
This procedure is the “business” part of the simulation that does simple numerical integration of the formula for a simple rotational pendulum.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'sample/demos-en/pendulum.rb', line 191 def recomputeAngle scaling = 3000.0/@length/@length # To estimate the integration accurately, we really need to # compute the end-point of our time-step. But to do *that*, we # need to estimate the integration accurately! So we try this # technique, which is inaccurate, but better than doing it in a # single step. What we really want is bound up in the # differential equation: # .. - sin theta # theta + theta = ----------- # length # But my math skills are not good enough to solve this! # first estimate firstDDTheta = -Math.sin(@theta * Math::PI/180) * scaling midDTheta = @dTheta + firstDDTheta midTheta = @theta + (@dTheta + midDTheta)/2 # second estimate midDDTheta = -Math.sin(midTheta * Math::PI/180) * scaling midDTheta = @dTheta + (firstDDTheta + midDDTheta)/2 midTheta = @theta + (@dTheta + midDTheta)/2 # Now we do a double-estimate approach for getting the final value # first estimate midDDTheta = -Math.sin(midTheta * Math::PI/180) * scaling lastDTheta = midDTheta + midDDTheta lastTheta = midTheta + (midDTheta+ lastDTheta)/2 # second estimate lastDDTheta = -Math.sin(lastTheta * Math::PI/180) * scaling lastDTheta = midDTheta + (midDDTheta + lastDDTheta)/2 lastTheta = midTheta + (midDTheta + lastDTheta)/2 # Now put the values back in our globals @dTheta = lastDTheta @theta = lastTheta end |
#repeat ⇒ Object
This method ties together the simulation engine and the graphical display code that visualizes it.
229 230 231 232 233 234 235 236 |
# File 'sample/demos-en/pendulum.rb', line 229 def repeat # Simulate recomputeAngle # Update the display showPendulum showPhase end |
#showPendulum(x = nil, y = nil) ⇒ Object
This procedure makes the pendulum appear at the correct place on the canvas. If the additional arguments x, y are passed instead of computing the position of the pendulum from the length of the pendulum rod and its angle, the length and angle are computed in reverse from the given location (which is taken to be the centre of the pendulum bob.)
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'sample/demos-en/pendulum.rb', line 149 def showPendulum(x=nil, y=nil) if x && y && (x != 160 || y != 25) @dTheta = 0.0 x2 = x - 160 y2 = y - 25 @length = Math.hypot(x2, y2) @theta = Math.atan2(x2,y2)*180/Math::PI else angle = @theta*Math::PI/180 x = 160 + @length*Math.sin(angle) y = 25 + @length*Math.cos(angle) end @rod.coords(160, 25, x, y) @bob.coords(x-15, y-15, x+15, y+15) end |
#showPhase ⇒ Object
Update the phase-space graph according to the current angle and the rate at which the angle is changing (the first derivative with respect to time.)
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'sample/demos-en/pendulum.rb', line 169 def showPhase unless @psw && @psh @psw = @k.width/2 @psh = @k.height/2 end @points << @theta + @psw << -20*@dTheta + @psh if @points.length > 100 @points = @points[-100..-1] end (0...100).step(10){|i| first = - i last = 11 - i last = -1 if last >= 0 next if first > last lst = @points[first..last] @graph[i].coords(lst) if lst && lst.length >= 4 } end |