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/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
- Definition of Vector and Body objects used for n-body simulations.
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
- module RubyLabs
11
-
15
+
12
16
  module SphereLab
13
17
 
14
- =begin rdoc
15
- These class variables maintain the state of the display and other miscellaneous
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 constant G is the universal gravitational constant, assuming mass is
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
- A Vector is a 3-tuple of (x,y,z) coordinates. Operators defined for
56
- vector objects (where v is a vector and a is a scalar):
57
- v == v
58
- v + v
59
- v - v
60
- v * a
61
- v.add(v) (equivalent of v += v)
62
- v.sub(v) (equivalent of v -= v)
63
- v.scale(a) (equivalent of v *= a)
64
- v.norm
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
- =begin rdoc
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
- =begin rdoc
84
- v1 == v2 if the three components are the same
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
- =begin rdoc
92
- Arithmetic methods are invoked for a vector v1 when Ruby evaluates an expression of the
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
- =begin rdoc
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
- =begin rdoc
123
- v1.sub(v2) subtracts the components of v2 from v1 -- would be v1 -= v2 if Ruby
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
- =begin rdoc
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
- =begin rdoc
145
- The magnitude of a vector is the Euclidean norm: sqrt(x**2 + y**2 + z**2)
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
- =begin rdoc
153
- Compute the angle between this vector and v -- used when drawing 2D vectors, so
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
- =begin rdoc
162
- Accessor functions for the coordinates as a vector of three floats
163
- =end
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
- A Body object represents the state of a celestial body. A body has mass (a scalar),
180
- position (a vector), and velocity (a vector). A third vector, named force, is used
181
- when calculating forces acting on a body. The size and color attributes are used by
182
- the visualization methods.
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[1].class == Vector
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
- =begin rdoc
229
- Reset the force vector to 0 (to get ready for a new round of interaction calculations).
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
- =begin rdoc
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
- =begin rdoc
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
- =begin rdoc
260
- Class method to compute the interaction between bodies b1 and b2 and update
261
- their force vectors.
262
- =end
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
- A class for rudimentary Turtle graphics. All we need is enough functionality to
275
- implement a robot that draws a circle by moving forward, then correcting its direction.
276
- Attributes: location and velocity vectors.
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
- @velocity = Vector.new(-1.0 * options[:speed] * cos(alpha), options[:speed] * sin(alpha), 0.0)
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
- Canvas.rotate(@graphic,alpha)
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
- =begin rdoc
372
- Set up an experiment with a robot/turtle. Initialize the canvas, make the turtle,
373
- assign it a graphic. These methods are all defined with respect to a canvas, and
374
- they will print an error message if the experiment has not been set up with a call
375
- to init_robot (which sets @@drawing).
376
- =end
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
- =begin rdoc
431
- These methods create a set of bodies with random size, location, and initial
432
- velocity.
433
- =end
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 random_velocity(v, r)
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
- =begin rdoc
496
- Initialize a new n-body system, returning an array of body objects. The
497
- parameter is the name of a predefined system, the name of a file, or the
498
- symbol :random.
499
- =end
500
-
501
- def make_system(*args)
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
- begin
504
- raise "usage: make_system(id)" unless args.length > 0
505
- if args[0] == :random
506
- raise "usage: make_system(:random, n, m)" unless args.length >= 2 && args[1].class == Fixnum
507
- return random_bodies(args[1], args[2])
508
- elsif args[0] == :falling
509
- raise "usage: make_system(:falling, n)" unless args.length >= 1 && args[1].class == Fixnum
510
- return falling_bodies(args[1])
511
- end
512
- filename = args[0]
513
- if filename.class == Symbol
514
- filename = File.join(@@sphereDirectory, filename.to_s + ".txt")
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
- rescue
540
- puts "error: #{$!}"
541
- return nil
542
- end
543
- return bodies
544
- end
545
-
546
- =begin rdoc
547
- Write the mass, position, and velocity for each body to a file. Not intended to
548
- be used by students, but used to save interesting data sets they can load and use.
549
- =end
550
-
551
- # d 4.5e16 300 -75 0 -5 2 0 6 #ff6666
552
-
553
-
554
- def save_system(b, fn)
555
- raise "file exists" if File.exists?(fn)
556
- File.open(fn, "w") do |f|
557
- b.each do |x|
558
- f.printf "%s %g %g %g %g %g %g %g %d %s\n",
559
- x.name, x.mass,
560
- x.position.x, x.position.y, x.position.z,
561
- x.velocity.x, x.velocity.y, x.velocity.z,
562
- x.size, x.color
563
- end
564
- end
565
- end
566
-
567
- =begin rdoc
568
- Initialize the drawing canvas by drawing a circle for each body in list b.
569
- =end
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
- =begin rdoc
596
- Demonstrate adding force vectors by moving only one body
597
- =end
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
- =begin rdoc
623
- Call SphereLab::step(bodies, time) to calculate the pairwise interactions between all
624
- Body objects in the array +bodies+ and then compute their new positions after +time+ seconds.
625
- =end
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) # compute all pairwise interactions
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) # apply the accumulated forces
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
- =begin rdoc
645
- After making a canvas with view_system, call this method to move the bodies on the canvas.
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
- =begin rdoc
668
- Map a simulation's (x,y) coordinates to screen coordinates using origin
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
- =begin rdoc
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
- =begin rdoc
693
- Set the scale factor. Use the parameter passed by the user, or find the
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
- =begin rdoc
711
- Initialize the drawing for a "2-body system" with a small object and the
712
- earth. The parameters are the list of Body objects and viewing options.
713
- =end
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
- =begin rdoc
735
- Position the melon (body 0) at a specified height. If no drawing, save the current
736
- position in prevy, but if drawing, get the earth's surface from the drawing (this allows
737
- students to move the melon up or down in the middle of an experiment)
738
- =end
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
- =begin rdoc
795
- Methods used in IRB sessions.
796
- =end
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
- # :begin :falling
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