rubylabs 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +15 -6
- data/Rakefile +3 -0
- data/VERSION +1 -1
- data/lib/bitlab.rb +593 -328
- data/lib/demos.rb +20 -9
- data/lib/elizalab.rb +660 -507
- data/lib/hashlab.rb +289 -192
- data/lib/introlab.rb +33 -38
- data/lib/iterationlab.rb +117 -61
- data/lib/marslab.rb +608 -475
- data/lib/randomlab.rb +227 -121
- data/lib/recursionlab.rb +197 -140
- data/lib/rubylabs.rb +936 -390
- data/lib/sievelab.rb +32 -24
- data/lib/spherelab.rb +308 -220
- data/lib/tsplab.rb +634 -312
- data/test/bit_test.rb +4 -4
- data/test/tsp_test.rb +18 -0
- metadata +2 -2
data/lib/spherelab.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
+
module RubyLabs
|
1
2
|
|
2
3
|
=begin rdoc
|
3
4
|
|
4
5
|
== SphereLab
|
5
6
|
|
6
|
-
|
7
|
+
The SphereLab module has definitions of classes and methods used in the projects for Chapter 11
|
8
|
+
of <em>Explorations in Computing</em>. The module includes a simple "turtle graphics" module
|
9
|
+
for experiments with moving objects, definitions of
|
10
|
+
Vector and Body objects used for n-body simulations, and methods for updating a system of
|
11
|
+
bodies based on the gravitational forces acting between pairs of bodies.
|
7
12
|
|
8
13
|
=end
|
9
14
|
|
10
|
-
|
11
|
-
|
15
|
+
|
12
16
|
module SphereLab
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
global values.
|
17
|
-
=end
|
18
|
+
# These class variables maintain the state of the display and other miscellaneous
|
19
|
+
# global values.
|
18
20
|
|
19
21
|
@@sphereDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'spheres')
|
20
22
|
|
@@ -45,72 +47,77 @@ module SphereLab
|
|
45
47
|
MelonView = Struct.new(:bodies, :scale, :ground, :startloc, :options)
|
46
48
|
|
47
49
|
=begin rdoc
|
48
|
-
The
|
50
|
+
The universal gravitational constant, assuming mass is
|
49
51
|
in units of kilograms, distances are in meters, and time is in seconds.
|
50
52
|
=end
|
51
53
|
|
52
54
|
G = 6.67E-11
|
53
55
|
|
54
56
|
=begin rdoc
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
v
|
57
|
+
|
58
|
+
== Vector
|
59
|
+
|
60
|
+
A Vector is a 3-tuple of (x,y,z) coordinates. Operators defined for
|
61
|
+
vector objects (where +v+ is a vector and +a+ is a scalar) are:
|
62
|
+
v == v
|
63
|
+
v + v
|
64
|
+
v - v
|
65
|
+
v * a
|
66
|
+
v.add(v) (equivalent to v += v)
|
67
|
+
v.sub(v) (equivalent to v -= v)
|
68
|
+
v.scale(a) (equivalent to v *= a)
|
69
|
+
v.norm
|
70
|
+
Arithmetic methods are invoked for a vector <tt>v1</tt> when Ruby evaluates an expression of the
|
71
|
+
form <tt>v1</tt> <op> <tt>v2</tt> where <op> is +, -, or *. They create a a new vector containing the
|
72
|
+
result of the operation. + and - do element-wise addition or and subtraction, *
|
73
|
+
is a vector-scalar multiplication.
|
74
|
+
|
75
|
+
The add, sub, and scale methods modify a vector, e.g. a call to <tt>v1.add(v2)</tt> will update
|
76
|
+
<tt>v1</tt> by adding the corresponding components in <tt>v2</tt>.
|
65
77
|
=end
|
66
78
|
|
67
79
|
class Vector
|
68
80
|
attr_accessor :x, :y, :z
|
69
81
|
|
70
|
-
|
71
|
-
Make a new vector with the specified x, y, and z components.
|
72
|
-
=end
|
82
|
+
# Make a new vector with the specified +x+, +y+, and +z+ components.
|
73
83
|
|
74
84
|
def initialize(*args)
|
75
85
|
@x, @y, @z = args
|
76
86
|
end
|
87
|
+
|
88
|
+
# Create a string that displays the +x+, +y+, and +z+ coordinates of the vector.
|
77
89
|
|
78
90
|
def inspect
|
79
91
|
pos = [@x,@y,@z].map { |x| sprintf "%.5g", x }
|
80
92
|
return "(" + pos.join(",") + ")"
|
81
93
|
end
|
82
94
|
|
83
|
-
|
84
|
-
|
85
|
-
=end
|
95
|
+
# Compare this vector with another vector +v+. Two vectors are the same if
|
96
|
+
# all three components are the same.
|
86
97
|
|
87
98
|
def ==(v)
|
88
99
|
return (@x == v.x) && (@y == v.y) && (@z == v.z)
|
89
100
|
end
|
90
101
|
|
91
|
-
|
92
|
-
|
93
|
-
form v1 <op> v2 where <op> is +, -, or *. They create a a new vector containing the
|
94
|
-
result of the operation. + and - do element-wise addition or and subtraction, *
|
95
|
-
is a vector-scalar multiplication.
|
96
|
-
=end
|
97
|
-
|
102
|
+
# Create a new vector that is the sum of this vector and vector +v+.
|
103
|
+
|
98
104
|
def +(v)
|
99
105
|
Vector.new(@x + v.x, @y + v.y, @z + v.z)
|
100
106
|
end
|
107
|
+
|
108
|
+
# Create a new vector that is the difference between this vector and vector +v+.
|
101
109
|
|
102
110
|
def -(v)
|
103
111
|
Vector.new(@x - v.x, @y - v.y, @z - v.z)
|
104
112
|
end
|
113
|
+
|
114
|
+
# Create a new vector that is the product of this vector and scalar +a+.
|
105
115
|
|
106
116
|
def *(a)
|
107
117
|
Vector.new(@x * a, @y * a, @z * a)
|
108
118
|
end
|
109
119
|
|
110
|
-
|
111
|
-
v1.add(v2) adds the components of v2 to v1 -- would be v1 += v2 if Ruby
|
112
|
-
allowed us to overload +=
|
113
|
-
=end
|
120
|
+
# Add the components of vector +v+ to this vector.
|
114
121
|
|
115
122
|
def add(v)
|
116
123
|
@x += v.x
|
@@ -119,11 +126,8 @@ module SphereLab
|
|
119
126
|
self
|
120
127
|
end
|
121
128
|
|
122
|
-
|
123
|
-
|
124
|
-
allowed us to overload -=
|
125
|
-
=end
|
126
|
-
|
129
|
+
# Subtract the components of vector +v+ from this vector.
|
130
|
+
|
127
131
|
def sub(v)
|
128
132
|
@x -= v.x
|
129
133
|
@y -= v.y
|
@@ -131,9 +135,7 @@ module SphereLab
|
|
131
135
|
self
|
132
136
|
end
|
133
137
|
|
134
|
-
|
135
|
-
v.scale(a) -- multiply each element in v by scalar a
|
136
|
-
=end
|
138
|
+
# Multiply each component of this vector by scalar +a+.
|
137
139
|
|
138
140
|
def scale(a)
|
139
141
|
@x *= a
|
@@ -141,31 +143,31 @@ module SphereLab
|
|
141
143
|
@z *= a
|
142
144
|
end
|
143
145
|
|
144
|
-
|
145
|
-
|
146
|
-
=end
|
146
|
+
# Compute the magnitude of this vector, which is the Euclidean norm
|
147
|
+
# defined by sqrt(x**2 + y**2 + z**2)
|
147
148
|
|
148
149
|
def norm
|
149
150
|
sqrt(@x*@x + @y*@y + @z*@z)
|
150
151
|
end
|
151
152
|
|
152
|
-
|
153
|
-
|
154
|
-
the z dimension is ignored
|
155
|
-
=end
|
153
|
+
# Compute the angle between this vector and vector +v+. This method is
|
154
|
+
# only used when drawing 2D vectors, so the z dimension is ignored.
|
156
155
|
|
157
156
|
def angle(v)
|
158
157
|
acos((@x * v.x + @y * v.y) / (norm * v.norm))
|
159
158
|
end
|
160
159
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
160
|
+
# Return a vector of three Floats corresponding to the +x+, +y+, and +z+
|
161
|
+
# components of this vector.
|
162
|
+
|
165
163
|
def coords
|
166
164
|
[@x, @y, @z]
|
167
165
|
end
|
168
166
|
|
167
|
+
# Set the three components of this vector to the values in the array +a+,
|
168
|
+
# which should be an array of three Floats (it can have more values, but
|
169
|
+
# only the first three are used).
|
170
|
+
|
169
171
|
def coords=(a)
|
170
172
|
@x = a[0]
|
171
173
|
@y = a[1]
|
@@ -176,30 +178,34 @@ module SphereLab
|
|
176
178
|
|
177
179
|
|
178
180
|
=begin rdoc
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
181
|
+
|
182
|
+
== Body
|
183
|
+
|
184
|
+
A Body object represents the state of a celestial body. A body has mass (a scalar),
|
185
|
+
position (a vector), and velocity (a vector). A third vector, named force, is used
|
186
|
+
when calculating forces acting on a body. The size and color attributes are used by
|
187
|
+
the visualization methods.
|
188
|
+
|
183
189
|
=end
|
184
190
|
|
185
191
|
class Body
|
186
192
|
attr_accessor :mass, :position, :velocity, :force
|
187
193
|
attr_accessor :name, :size, :color, :prevx, :prevy, :graphic
|
194
|
+
|
195
|
+
# Create a new Body object. If the argument list is empty, the attributes (mass,
|
196
|
+
# position, etc) are all set to 0. Otherwise the arguments should be +mass+ (a
|
197
|
+
# Float), +position+ (a vector), and +velocity+ (another vector). A fourth
|
198
|
+
# argument is an optional name for the body.
|
199
|
+
#
|
200
|
+
# Example:
|
201
|
+
# >> b = Body.new
|
202
|
+
# => : 0 kg (0,0,0) (0,0,0)
|
203
|
+
# >> b = Body.new(1e6, Vector.new(0,0,0), Vector.new(0,0,0), "test")
|
204
|
+
# => test: 1e+06 kg (0,0,0) (0,0,0)
|
188
205
|
|
189
206
|
def initialize(*args)
|
190
|
-
if args
|
207
|
+
if args.length > 0
|
191
208
|
@mass, @position, @velocity, @name = args
|
192
|
-
# elsif args[0] == :random
|
193
|
-
# @mass = rand(10000) * 1e6
|
194
|
-
# @position = Vector.new(rand(300)-150, rand(300)-150, 0)
|
195
|
-
# @velocity = Vector.new(rand(20)-10, rand(10)-5, 0)
|
196
|
-
# @name = "b" + self.object_id.to_s
|
197
|
-
# elsif args[0].is_a?(Numeric) && args[1].is_a?(Numeric)
|
198
|
-
# @mass = args[0]
|
199
|
-
# @position = Vector.new(args[1], 0.0, 0.0)
|
200
|
-
# @velocity = Vector.new(0.0, 0.0, 0.0)
|
201
|
-
# @name = args[2]
|
202
|
-
# @linear = true
|
203
209
|
else
|
204
210
|
@mass = 0.0
|
205
211
|
@position = Vector.new(0.0, 0.0, 0.0)
|
@@ -209,6 +215,8 @@ module SphereLab
|
|
209
215
|
@force = Vector.new(0.0, 0.0, 0.0)
|
210
216
|
end
|
211
217
|
|
218
|
+
# Make a copy of this body.
|
219
|
+
|
212
220
|
def clone
|
213
221
|
copy = super
|
214
222
|
if graphic
|
@@ -219,15 +227,16 @@ module SphereLab
|
|
219
227
|
end
|
220
228
|
return copy
|
221
229
|
end
|
230
|
+
|
231
|
+
# Create a string that summarizes the state of this body.
|
222
232
|
|
223
233
|
def inspect
|
224
234
|
name = @name ? @name : ""
|
225
235
|
return sprintf "%s: %.3g kg %s %s", name, @mass, @position.inspect, @velocity.inspect
|
226
236
|
end
|
227
237
|
|
228
|
-
|
229
|
-
|
230
|
-
=end
|
238
|
+
# Reset the force vector to (0,0,0). Called by SphereLab#step_system at the start of each
|
239
|
+
# new round of interaction calculations.
|
231
240
|
|
232
241
|
def clear_force
|
233
242
|
@force.x = 0.0
|
@@ -235,9 +244,7 @@ module SphereLab
|
|
235
244
|
@force.z = 0.0
|
236
245
|
end
|
237
246
|
|
238
|
-
|
239
|
-
Compute force acting on this body wrt body b
|
240
|
-
=end
|
247
|
+
# Compute the force exerted on this body by body +b+ and update this body's force vector.
|
241
248
|
|
242
249
|
def add_force(b)
|
243
250
|
r = @position - b.position
|
@@ -246,9 +253,7 @@ module SphereLab
|
|
246
253
|
@force.add(r * mr)
|
247
254
|
end
|
248
255
|
|
249
|
-
|
250
|
-
Move this body by applying current force vector for dt seconds
|
251
|
-
=end
|
256
|
+
# Update this body's position by applying the current force vector for +dt+ seconds.
|
252
257
|
|
253
258
|
def move(dt)
|
254
259
|
acc = @force * G * -1.0
|
@@ -256,10 +261,10 @@ module SphereLab
|
|
256
261
|
@position.add( @velocity * dt )
|
257
262
|
end
|
258
263
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
264
|
+
# This class method will compute the interaction between bodies <tt>b1</tt> and <tt>b2</tt> and update
|
265
|
+
# their force vectors. Since the forces on the bodies are the same, but acting in the
|
266
|
+
# opposite direction, the force can be calculated just once and then used to update both
|
267
|
+
# bodies.
|
263
268
|
|
264
269
|
def Body.interaction(b1, b2)
|
265
270
|
r = b1.position - b2.position
|
@@ -271,9 +276,16 @@ module SphereLab
|
|
271
276
|
end # Body
|
272
277
|
|
273
278
|
=begin rdoc
|
274
|
-
|
275
|
-
|
276
|
-
|
279
|
+
|
280
|
+
== Turtle
|
281
|
+
|
282
|
+
This class implements a rudimentary "turtle graphics" system. A turtle is an object that
|
283
|
+
moves around on a canvas, under direction from a user/program that tells it to advance, turn,
|
284
|
+
etc. A turtle also has a "pen" that can be raised or lowered. When the pen is down, the
|
285
|
+
turtle draws a line on the canvas as it moves.
|
286
|
+
|
287
|
+
A Turtle object is used in the robot explorer experiment, where the object is to get the robot
|
288
|
+
to move in as smooth a circle as possible about a central point.
|
277
289
|
=end
|
278
290
|
|
279
291
|
class Turtle
|
@@ -289,6 +301,19 @@ module SphereLab
|
|
289
301
|
}
|
290
302
|
|
291
303
|
@@north = Vector.new(0, 10, 0)
|
304
|
+
|
305
|
+
# Create a new Turtle object. The arguments (all optional) specify the turtle's starting
|
306
|
+
# position and orientation. The possible options and their default values:
|
307
|
+
# :x => 20
|
308
|
+
# :y => 100
|
309
|
+
# :heading => 0
|
310
|
+
# :speed => 10
|
311
|
+
# :track => :off
|
312
|
+
# Example:
|
313
|
+
# >> t = Turtle.new
|
314
|
+
# => #<SphereLab::Turtle x: 20 y: 100 heading: 360 speed: 10.00>
|
315
|
+
# >> t = Turtle.new( :x => 100, :y => 100, :speed => 5 )
|
316
|
+
# => #<SphereLab::Turtle x: 100 y: 100 heading: 360 speed: 5.00>
|
292
317
|
|
293
318
|
def initialize(args = nil)
|
294
319
|
args = { } if args.nil?
|
@@ -296,29 +321,44 @@ module SphereLab
|
|
296
321
|
options = @@turtleOptions.merge(args)
|
297
322
|
alpha = Canvas.radians(options[:heading] + 90.0)
|
298
323
|
@position = Vector.new(options[:x], options[:y], 0.0)
|
299
|
-
|
324
|
+
if options[:speed] > 0
|
325
|
+
@velocity = Vector.new(-1.0 * options[:speed] * cos(alpha), options[:speed] * sin(alpha), 0.0)
|
326
|
+
else
|
327
|
+
raise "Turtle.new: speed must be greater than 0"
|
328
|
+
end
|
300
329
|
@graphic = nil
|
301
330
|
@reference = nil
|
302
331
|
@tracking = options[:track]
|
303
332
|
end
|
304
333
|
|
334
|
+
# Create a string that summarizes the status of this turtle object.
|
335
|
+
|
305
336
|
def inspect
|
306
337
|
sprintf "#<SphereLab::Turtle x: %d y: %d heading: %d speed: %.2f>", @position.x, @position.y, heading, speed
|
307
338
|
end
|
308
339
|
|
340
|
+
# Return an array of two numbers representing the +x+ and +y+ coordinates of this object.
|
341
|
+
|
309
342
|
def location
|
310
343
|
return [@position.x, @position.y]
|
311
344
|
end
|
312
345
|
|
346
|
+
# Return the current heading, in compass degrees, of this turtle object. The heading is a number between 0 and 360,
|
347
|
+
# where 0 is north, 90 is east, etc.
|
348
|
+
|
313
349
|
def heading
|
314
350
|
d = Canvas.degrees( @velocity.angle(@@north) )
|
315
351
|
return (@velocity.x < 0) ? (360 - d) : d
|
316
352
|
end
|
317
353
|
|
354
|
+
# Return the current speed (in meters per second) of this turtle object.
|
355
|
+
|
318
356
|
def speed
|
319
357
|
@velocity.norm
|
320
358
|
end
|
321
359
|
|
360
|
+
# Reorient the turtle by telling it to turn clockwise by +alpha+ degrees.
|
361
|
+
|
322
362
|
def turn(alpha)
|
323
363
|
theta = -1.0 * Canvas.radians(alpha)
|
324
364
|
x = @velocity.x * cos(theta) - @velocity.y * sin(theta)
|
@@ -326,11 +366,13 @@ module SphereLab
|
|
326
366
|
@velocity.x = x
|
327
367
|
@velocity.y = y
|
328
368
|
if @graphic
|
329
|
-
|
369
|
+
@graphic.rotate(alpha)
|
330
370
|
end
|
331
371
|
return alpha
|
332
372
|
end
|
333
373
|
|
374
|
+
# Tell the turtle to move straight ahead from its current position for +dt+ seconds.
|
375
|
+
|
334
376
|
def advance(dt)
|
335
377
|
prevx = @position.x
|
336
378
|
prevy = @position.y
|
@@ -341,6 +383,10 @@ module SphereLab
|
|
341
383
|
return dt
|
342
384
|
end
|
343
385
|
|
386
|
+
# Tell the turtle to rotate until its heading is orthogonal to a reference point.
|
387
|
+
# See the Lab Manual for details about setting a reference point and how the
|
388
|
+
# orientation is calculated.
|
389
|
+
|
344
390
|
def orient
|
345
391
|
if @reference.nil?
|
346
392
|
puts "no reference point"
|
@@ -355,6 +401,9 @@ module SphereLab
|
|
355
401
|
return alpha
|
356
402
|
end
|
357
403
|
|
404
|
+
# Turn tracking <tt>:on</tt> or <tt>:off</tt>. When tracking is <tt>:on</tt> the
|
405
|
+
# turtle draws a line on the canvas as it moves.
|
406
|
+
|
358
407
|
def track(val)
|
359
408
|
case val
|
360
409
|
when :off : @tracking = :off
|
@@ -368,13 +417,13 @@ module SphereLab
|
|
368
417
|
|
369
418
|
end # class Turtle
|
370
419
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
420
|
+
# Initialize the RubyLabs Canvas for an experiment with the robot explorer. The
|
421
|
+
# canvas will show a map of an area 400 x 400 meters and the robot (represented by
|
422
|
+
# a Turtle object) on the west edge, facing north. The two options that can be
|
423
|
+
# passed specify the canvas size and a polygon that determines its shape:
|
424
|
+
# :canvasSize => 400,
|
425
|
+
# :polygon => [4,0,0,10,4,8,8,10],
|
426
|
+
|
378
427
|
def view_robot(userOptions = {})
|
379
428
|
options = @@robotOptions.merge(userOptions)
|
380
429
|
poly = options[:polygon].clone
|
@@ -401,12 +450,29 @@ module SphereLab
|
|
401
450
|
return true
|
402
451
|
end
|
403
452
|
|
453
|
+
# Plant a "flag" at location +fx+, +fy+, which will serve as a reference point for
|
454
|
+
# calls to <tt>robot.orient</tt> to reorient the robot. The flag will be shown as
|
455
|
+
# a circle on the canvas at the specified position.
|
456
|
+
|
404
457
|
def set_flag(fx, fy)
|
405
458
|
r = 3.0
|
406
459
|
Canvas::Circle.new( fx + r/2, fy + r/2, r, :fill => 'darkblue' )
|
407
460
|
@reference = [ fx, fy ]
|
408
461
|
end
|
409
|
-
|
462
|
+
|
463
|
+
# Return a reference to the Turtle object that represents the robot explorer
|
464
|
+
# (created when the user calls view_robot).
|
465
|
+
# In experiments, users combine a call to this method with a call to a method that
|
466
|
+
# controls the robot.
|
467
|
+
#
|
468
|
+
# Example:
|
469
|
+
# >> robot
|
470
|
+
# => #<SphereLab::Turtle x: 40 y: 200 heading: 360 speed: 10.00>
|
471
|
+
# >> robot.heading
|
472
|
+
# => 360.0
|
473
|
+
# >> robot.turn(30)
|
474
|
+
# => 30
|
475
|
+
|
410
476
|
def robot
|
411
477
|
if @@drawing.nil?
|
412
478
|
puts "No robot; call view_robot to initialize"
|
@@ -415,24 +481,12 @@ module SphereLab
|
|
415
481
|
return @@drawing.turtle
|
416
482
|
end
|
417
483
|
end
|
418
|
-
|
419
|
-
# :begin :make_circle
|
420
|
-
def make_circle(nseg, time, angle)
|
421
|
-
view_robot
|
422
|
-
robot.turn( angle/2 )
|
423
|
-
nseg.times { robot.advance(time); robot.turn(angle) }
|
424
|
-
robot.turn( -angle/2 )
|
425
|
-
return true
|
426
|
-
end
|
427
|
-
# :end :make_circle
|
428
|
-
|
429
484
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
def random_vectors(r, i, n)
|
485
|
+
# The methods that make random bodies need to be fixed -- they need a more formal
|
486
|
+
# approach to define a system that "keeps together" longer. In the meantime, turn
|
487
|
+
# off documentation....
|
488
|
+
|
489
|
+
def random_vectors(r, i, n) # :nodoc:
|
436
490
|
theta = (2 * PI / n) * i + (PI * rand / n)
|
437
491
|
# radius = r + (r/3)*(rand-0.5)
|
438
492
|
radius = r + r * (rand-0.5)
|
@@ -444,13 +498,7 @@ module SphereLab
|
|
444
498
|
return Vector.new(x, y, 0), Vector.new(vx, vy, 0)
|
445
499
|
end
|
446
500
|
|
447
|
-
def
|
448
|
-
res = Vector.new(-r * cos)
|
449
|
-
end
|
450
|
-
|
451
|
-
# todo -- put mm, mr in global options
|
452
|
-
|
453
|
-
def random_bodies(n, big)
|
501
|
+
def random_bodies(n, big) # :nodoc:
|
454
502
|
big = 1 if big.nil?
|
455
503
|
moving = true if moving.nil?
|
456
504
|
res = []
|
@@ -477,7 +525,7 @@ module SphereLab
|
|
477
525
|
return res
|
478
526
|
end
|
479
527
|
|
480
|
-
def falling_bodies(n)
|
528
|
+
def falling_bodies(n) # :nodoc:
|
481
529
|
raise "n must be 5 or more" unless n >= 5
|
482
530
|
a = random_bodies(n-1, n-1)
|
483
531
|
b = Body.new(1e13, (a[0].position + a[1].position), Vector.new(0,0,0))
|
@@ -492,26 +540,33 @@ module SphereLab
|
|
492
540
|
return a
|
493
541
|
end
|
494
542
|
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
543
|
+
# Initialize a new n-body system, returning an array of body objects. If the argument
|
544
|
+
# is a symbol it is the name of a predefined system in the SphereLab data directory,
|
545
|
+
# otherwise it should be the name of a file in the local directory.
|
546
|
+
#
|
547
|
+
# Example:
|
548
|
+
# >> b = make_system(:melon)
|
549
|
+
# => [melon: 3 kg (0,6.371e+06,0) (0,0,0), earth: 5.97e+24 kg (0,0,0) (0,0,0)]
|
550
|
+
# >> b = make_system(:solarsystem)
|
551
|
+
# => [sun: 1.99e+30 kg (0,0,0) (0,0,0), ... pluto: 1.31e+22 kg (-4.5511e+12,3.1753e+11,1.2822e+12) (636,-5762.1,440.88)]
|
552
|
+
#--
|
553
|
+
# When random bodies are working again add this back in to the comment:
|
554
|
+
# ... or the symbol :random, which...
|
555
|
+
|
556
|
+
def make_system(*args)
|
502
557
|
bodies = []
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
558
|
+
begin
|
559
|
+
raise "usage: make_system(id)" unless args.length > 0
|
560
|
+
if args[0] == :random
|
561
|
+
raise "usage: make_system(:random, n, m)" unless args.length >= 2 && args[1].class == Fixnum
|
562
|
+
return random_bodies(args[1], args[2])
|
563
|
+
elsif args[0] == :falling
|
564
|
+
raise "usage: make_system(:falling, n)" unless args.length >= 1 && args[1].class == Fixnum
|
565
|
+
return falling_bodies(args[1])
|
566
|
+
end
|
567
|
+
filename = args[0]
|
568
|
+
if filename.class == Symbol
|
569
|
+
filename = File.join(@@sphereDirectory, filename.to_s + ".txt")
|
515
570
|
end
|
516
571
|
File.open(filename).each do |line|
|
517
572
|
line.strip!
|
@@ -536,37 +591,39 @@ module SphereLab
|
|
536
591
|
# end
|
537
592
|
end
|
538
593
|
end
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
594
|
+
rescue
|
595
|
+
puts "error: #{$!}"
|
596
|
+
return nil
|
597
|
+
end
|
598
|
+
return bodies
|
599
|
+
end
|
600
|
+
|
601
|
+
# Write the mass, position, and velocity for each body in array +b+ to a file named +fn+.
|
602
|
+
# The data in the file can later be read back in by passing the file name to make_system.
|
603
|
+
#--
|
604
|
+
# Not intended to be used by students, but to save interesting data sets they can load and use.
|
605
|
+
|
606
|
+
def save_system(b, fn)
|
607
|
+
raise "file exists" if File.exists?(fn)
|
608
|
+
File.open(fn, "w") do |f|
|
609
|
+
b.each do |x|
|
610
|
+
f.printf "%s %g %g %g %g %g %g %g %d %s\n",
|
611
|
+
x.name, x.mass,
|
612
|
+
x.position.x, x.position.y, x.position.z,
|
613
|
+
x.velocity.x, x.velocity.y, x.velocity.z,
|
614
|
+
x.size, x.color
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
# Initialize the RubyLabs Canvas to show the motion of a set of bodies in an
|
620
|
+
# n-body simulation and draw a circle for each body in +blist+.
|
621
|
+
#
|
622
|
+
# Example -- make a drawing with the Sun and the inner three planets:
|
623
|
+
# >> b = make_system(:solarsystem)
|
624
|
+
# => [sun: 1.99e+30 kg (0,0,0) (0,0,0), ... ]
|
625
|
+
# >> view_system(b[0..3])
|
626
|
+
# => true
|
570
627
|
|
571
628
|
def view_system(blist, userOptions = {})
|
572
629
|
Canvas.init(700, 700, "SphereLab::N-Body")
|
@@ -592,10 +649,11 @@ module SphereLab
|
|
592
649
|
return true
|
593
650
|
end
|
594
651
|
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
652
|
+
# Run one step of a simulation of an n-body system where only one body is allowed
|
653
|
+
# to move and all the others are fixed in place. The first argument is a reference
|
654
|
+
# to the moving body, the second is any array containing references to the other
|
655
|
+
# bodies in the system, and the third is the time step size.
|
656
|
+
|
599
657
|
def update_one(falling, stationary, time)
|
600
658
|
if falling.graphic.nil?
|
601
659
|
puts "display the system with view_system"
|
@@ -619,31 +677,29 @@ module SphereLab
|
|
619
677
|
return true
|
620
678
|
end
|
621
679
|
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
# :begin :step_system
|
680
|
+
# Run one time step of a full n-body simulation. Compute the pairwise interactions of all
|
681
|
+
# bodies in the system, update their force vectors, and then move them an amount determined
|
682
|
+
# by the time step size +dt+.
|
683
|
+
#--
|
684
|
+
# :begin :step_system
|
628
685
|
def step_system(bodies, dt)
|
629
686
|
nb = bodies.length
|
630
687
|
|
631
|
-
for i in 0..(nb-1)
|
688
|
+
for i in 0..(nb-1) # compute all pairwise interactions
|
632
689
|
for j in (i+1)..(nb-1)
|
633
690
|
Body.interaction( bodies[i], bodies[j] )
|
634
691
|
end
|
635
692
|
end
|
636
693
|
|
637
694
|
bodies.each do |b|
|
638
|
-
b.move(dt)
|
695
|
+
b.move(dt) # apply the accumulated forces
|
639
696
|
b.clear_force # reset force to 0 for next round
|
640
697
|
end
|
641
698
|
end
|
642
|
-
# :end :step_system
|
699
|
+
# :end :step_system
|
643
700
|
|
644
|
-
|
645
|
-
|
646
|
-
=end
|
701
|
+
# This method will call step_system to simulate the motion of a set of bodies for the
|
702
|
+
# specified amount of time +dt+ and then update their positions on the canvas.
|
647
703
|
|
648
704
|
def update_system(bodies, dt)
|
649
705
|
return false unless @@drawing
|
@@ -664,23 +720,19 @@ module SphereLab
|
|
664
720
|
return true
|
665
721
|
end
|
666
722
|
|
667
|
-
|
668
|
-
|
669
|
-
and scale factor
|
670
|
-
=end
|
723
|
+
# Map a simulation's (x,y) coordinates to screen coordinates using origin
|
724
|
+
# and scale factor
|
671
725
|
|
672
|
-
def scale(vec, origin, sf)
|
726
|
+
def scale(vec, origin, sf) # :nodoc:
|
673
727
|
loc = vec.clone
|
674
728
|
loc.scale(sf)
|
675
729
|
loc.add(origin)
|
676
730
|
return loc.x, loc.y
|
677
731
|
end
|
678
732
|
|
679
|
-
|
680
|
-
Make a vector that defines the location of the origin (in pixels).
|
681
|
-
=end
|
733
|
+
# Make a vector that defines the location of the origin (in pixels).
|
682
734
|
|
683
|
-
def setOrigin(type)
|
735
|
+
def setOrigin(type) # :nodoc:
|
684
736
|
case type
|
685
737
|
when :center
|
686
738
|
Vector.new( Canvas.width/2, Canvas.height/2, 0)
|
@@ -689,12 +741,10 @@ module SphereLab
|
|
689
741
|
end
|
690
742
|
end
|
691
743
|
|
692
|
-
|
693
|
-
|
694
|
-
largest coordinate in a list of bodies. Add 20% for a margin
|
695
|
-
=end
|
744
|
+
# Set the scale factor. Use the parameter passed by the user, or find the
|
745
|
+
# largest coordinate in a list of bodies. Add 20% for a margin
|
696
746
|
|
697
|
-
def setScale(blist, origin, scale)
|
747
|
+
def setScale(blist, origin, scale) # :nodoc:
|
698
748
|
if scale == nil
|
699
749
|
dmax = 0.0
|
700
750
|
blist.each do |b|
|
@@ -707,10 +757,22 @@ module SphereLab
|
|
707
757
|
return (sf / dmax) * 0.8
|
708
758
|
end
|
709
759
|
|
710
|
-
|
711
|
-
|
712
|
-
earth. The
|
713
|
-
|
760
|
+
# Initialize the RubyLabs Canvas to show a drawing of a "2-body system" with
|
761
|
+
# a small circle to represent a watermelon and a much larger partial circle for the
|
762
|
+
# earth. The two Body objects representing the melon and earth should be passed
|
763
|
+
# in the array +blist+. Additonal optional arguments are viewing options, which have
|
764
|
+
# the following defaults:
|
765
|
+
# :canvasSize => 400
|
766
|
+
# :mxmin => 100 maximum melon x position
|
767
|
+
# :mymin => 50, maximum melon y position
|
768
|
+
# :hmax => 100, maximum melon height
|
769
|
+
# :dash => 1, size of dashes drawn as melon falls
|
770
|
+
#
|
771
|
+
# Example:
|
772
|
+
# >> b = make_system(:melon)
|
773
|
+
# => [melon: 3 kg (0,6.371e+06,0) (0,0,0), earth: 5.97e+24 kg (0,0,0) (0,0,0)]
|
774
|
+
# >> view_melon(b)
|
775
|
+
# => true
|
714
776
|
|
715
777
|
def view_melon(blist, userOptions = {})
|
716
778
|
options = @@droppingOptions.merge(userOptions)
|
@@ -731,11 +793,16 @@ module SphereLab
|
|
731
793
|
return true
|
732
794
|
end
|
733
795
|
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
796
|
+
# Position the melon (the first body in the array +blist+) at a specified height
|
797
|
+
# above the earth.
|
798
|
+
#
|
799
|
+
# Example:
|
800
|
+
# >> position_melon(b, 50)
|
801
|
+
# => 50
|
802
|
+
#--
|
803
|
+
# If no drawing, save the current position in prevy, but if drawing, get the earth's
|
804
|
+
# surface from the drawing (this allows students to move the melon up or down in the
|
805
|
+
# middle of an experiment)
|
739
806
|
|
740
807
|
def position_melon(blist, height)
|
741
808
|
melon = blist[0]
|
@@ -758,6 +825,10 @@ module SphereLab
|
|
758
825
|
return height
|
759
826
|
end
|
760
827
|
|
828
|
+
# Execute one time step of the two-body simulation involving the two objects in
|
829
|
+
# +blist+. Similar to the general purpose method update_system, except the
|
830
|
+
# drawing position of the earth is not updated.
|
831
|
+
|
761
832
|
def update_melon(blist, dt)
|
762
833
|
melon = blist[0]
|
763
834
|
mx = melon.position.x
|
@@ -778,6 +849,21 @@ module SphereLab
|
|
778
849
|
return blist[0].height
|
779
850
|
end
|
780
851
|
|
852
|
+
# Repeatedly call the update_melon method until the +y+ position of the melon
|
853
|
+
# is less than or equal to the +y+ position of the surface of the earth. The
|
854
|
+
# two objects representing the melon and earth are passed in the array +blist+,
|
855
|
+
# and +dt+ is the size of the time step to use in calls to update_melon.
|
856
|
+
# The return value is the amount of simulated time it takes the melon to hit
|
857
|
+
# the earth; it will be the product of +dt+ and the number of time steps (number of
|
858
|
+
# calls to update_melon).
|
859
|
+
#
|
860
|
+
# Example -- to run an experiment that drops the melon from 50 meters, using a
|
861
|
+
# time step size of .01 seconds:
|
862
|
+
# >> position_melon(b, 50)
|
863
|
+
# => 50
|
864
|
+
# >> drop_melon(b, 0.01)
|
865
|
+
# => 3.19
|
866
|
+
#--
|
781
867
|
# :begin :drop_melon
|
782
868
|
def drop_melon(blist, dt)
|
783
869
|
count = 0
|
@@ -790,22 +876,24 @@ module SphereLab
|
|
790
876
|
end
|
791
877
|
# :end :drop_melon
|
792
878
|
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
# :begin :dist
|
879
|
+
# Compute the distance traveled by an object moving at velocity +r+ for
|
880
|
+
# an amount of time +t+.
|
881
|
+
#--
|
882
|
+
# :begin :dist
|
799
883
|
def dist(r, t)
|
800
884
|
return r * t
|
801
885
|
end
|
802
|
-
# :end :dist
|
803
|
-
|
804
|
-
#
|
886
|
+
# :end :dist
|
887
|
+
|
888
|
+
# Compute the distance an object will fall when it is initially stationary
|
889
|
+
# and then falls for an amount of time +dt+, accelerating
|
890
|
+
# according to the force of the Earth's gravity.
|
891
|
+
#--
|
892
|
+
# :begin :falling
|
805
893
|
def falling(t)
|
806
894
|
return 0.5 * 9.8 * t**2
|
807
895
|
end
|
808
|
-
# :end :falling
|
896
|
+
# :end :falling
|
809
897
|
|
810
898
|
end # SphereLab
|
811
899
|
|