rubylabs 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|