driving_physics 0.0.0.3 → 0.0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec6ff08e7731aa8bcb419efee9ab1a96ef7642f3426c7b8a501dae764692d777
4
- data.tar.gz: ae217c0e452a764392e5a4df9a0b3280369e21b92d6018ef8d8753c0a6f5f594
3
+ metadata.gz: 89628c4f71193088741435eb426419edf7c9ac78cad2169c4718f2a365db0881
4
+ data.tar.gz: a229fa52ed29f6dcb8eab0513e845732e0735dd272e516e4f26603aeabe7f312
5
5
  SHA512:
6
- metadata.gz: b8844bf3f8c45aae742bc27cbfe8307b7f61f21a600d1eac7f34248aa9a980f9942f6e4ce51ec2b58834978e4ba1a06aa0153ba4323d4e3448b1f038c39495e2
7
- data.tar.gz: b41f09936f696ef4afe82f3be701409abddd6b92add4f39d5d1d42a1a412ce4204b08a411818c339bba6820858a0aae3d5324cdbabff76e2b2256e9228ae1120
6
+ metadata.gz: 42a6061d1ef6e2c989e5fb88141e8a84daaafc3c9a2f31e057d2fb7a384fbaa1c195d05deab18b2703fd5e79b539557543ec8eaf8632cd2a5f776359c7c3e340
7
+ data.tar.gz: 2860d2b6c17c5d4e9d73868de36bdcf48341d74712666c109d422c97975899b0fde2248b58c060d02e02561ba3dff471d7b66c694a44e5d143108a20f828e477
data/README.md CHANGED
@@ -6,7 +6,37 @@ Physical simulation of how to make a car go around a track quickly, in pure
6
6
  Ruby with minimal dependencies. Physics from first principles, often
7
7
  using `Vector` class from `matrix.rb` in stdlib.
8
8
 
9
- Note, this is very much a **Work In Progress**.
9
+ ## Work In Progress
10
+
11
+ This is very much a **Work In Progress**. Implemented so far:
12
+
13
+ ### mruby Support
14
+
15
+ This is big. This library is primarily a Ruby library, intended for use with
16
+ CRuby (MRI), JRuby, etc. **mruby** is the redheaded stepchild, with severe
17
+ implications for a codebase that wants to support both sides. Supporting
18
+ mruby here meant:
19
+
20
+ 1. Editing the codebase (minimally) to remove *mruby-deadends*.
21
+ 2. Creating `rake` tasks to process normal .rb files into *mruby-compatible*
22
+ .rb files
23
+ 3. Assembling all needed library .rb files into a master .rb file
24
+ 4. Compiling the library to bytecode
25
+ 5. Executing a toplevel/main script against the library
26
+ 6. Optionally executing toplevel/main bytecode against the library
27
+
28
+ See the main Rakefile near the top for mruby stuff.
29
+
30
+ ### Library Features
31
+
32
+ * Spinning: rotational inertia and friction / hysteresis
33
+ * Tires: traction force via friction (static and kinetic) and the normal force
34
+ * Vectors: 2D Vector forces and 3D Vector torques
35
+ * Motor: torque curves, rotating mass with friction
36
+ * Gearbox: gear ratios, final drive, rotating mass with friction
37
+ * Powertrain: combines motor and gearbox
38
+ * Car: 4 tires, powertrain, extra mass presents a load to the powertrain
39
+ * Acceleration: via drive traction from "first principles"
10
40
 
11
41
  ## Rationale
12
42
 
data/Rakefile CHANGED
@@ -16,6 +16,90 @@ end
16
16
 
17
17
  task default: :test
18
18
 
19
+ #
20
+ # mruby
21
+ #
22
+
23
+ MRBLIB_DIR = File.join %w[mruby mrblib]
24
+ MRBLIB_FILE = File.join(MRBLIB_DIR, 'driving_physics.rb')
25
+ MRBLIB_MRB = File.join(MRBLIB_DIR, 'driving_physics.mrb')
26
+ MRUBY_DEMO_DIR = File.join %w[demo mruby]
27
+
28
+ def write_mruby(input_file, output_file = MRBLIB_FILE, append: false)
29
+ file_obj = File.open(output_file, append ? 'a' : 'w')
30
+ line_count = 0
31
+
32
+ File.readlines(input_file).each { |line|
33
+ next if line.match /\A *(?:require|autoload)/
34
+ file_obj.write(line)
35
+ line_count += 1
36
+ }
37
+ file_obj.close
38
+ line_count
39
+ end
40
+
41
+ file MRBLIB_FILE do
42
+ line_count = write_mruby(File.join(%w[lib driving_physics.rb]), MRBLIB_FILE)
43
+ %w[mruby environment imperial power
44
+ disk tire motor gearbox powertrain car].each { |name|
45
+ file = File.join('lib', 'driving_physics', "#{name}.rb")
46
+ line_count += write_mruby(file, MRBLIB_FILE, append: true)
47
+ puts "wrote #{file} to #{MRBLIB_FILE}"
48
+ }
49
+ puts "wrote #{line_count} lines to #{MRBLIB_FILE}"
50
+ end
51
+
52
+ file MRBLIB_MRB => MRBLIB_FILE do
53
+ rb_file = File.join(%w[mruby mrblib driving_physics.rb])
54
+ mrb_file = File.join(%w[mruby mrblib driving_physics.mrb])
55
+ sh 'mrbc', rb_file
56
+ puts format("%s: %d bytes (created %s)",
57
+ mrb_file, File.size(mrb_file), File.birthtime(mrb_file))
58
+ end
59
+
60
+ file MRUBY_DEMO_DIR do
61
+ mkdir_p MRUBY_DEMO_DIR
62
+ end
63
+
64
+ desc "Map/Filter/Reduce lib/**/*.rb to mruby/mrblib/driving_physics.rb"
65
+ task mrblib: MRBLIB_FILE
66
+
67
+ desc "Compile mruby/mrblib/driving_physics.rb to .mrb bytecode"
68
+ task mrbc: MRBLIB_MRB
69
+
70
+ %w[disk tire motor gearbox powertrain car].each { |name|
71
+ demo_file = File.join(MRUBY_DEMO_DIR, "#{name}.rb")
72
+ demo_mrb = File.join(MRUBY_DEMO_DIR, "#{name}.mrb")
73
+
74
+ file demo_file => MRUBY_DEMO_DIR do
75
+ write_mruby(File.join('demo', "#{name}.rb"), demo_file)
76
+ end
77
+
78
+ file demo_mrb => demo_file do
79
+ sh 'mrbc', demo_file
80
+ end
81
+
82
+ desc "run demo/#{name}.rb via mruby"
83
+ task "demo_#{name}" => [demo_file, MRBLIB_MRB] do
84
+ sh 'mruby', '-r', MRBLIB_MRB, demo_file
85
+ end
86
+
87
+ desc "run demo/#{name}.rb via mruby bytecode"
88
+ task "mrb_#{name}" => [demo_mrb, MRBLIB_MRB] do
89
+ sh 'mruby', '-r', MRBLIB_MRB, '-b', demo_mrb
90
+ end
91
+ }
92
+
93
+ desc "Remove generated .rb and .mrb files"
94
+ task :clean do
95
+ [MRBLIB_DIR, MRUBY_DEMO_DIR].each { |dir|
96
+ Dir[File.join(dir, '*rb')].each { |file|
97
+ rm file unless File.directory?(file)
98
+ }
99
+ }
100
+ end
101
+
102
+
19
103
  #
20
104
  # METRICS
21
105
  #
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0.3
1
+ 0.0.2.1
data/demo/car.rb ADDED
@@ -0,0 +1,178 @@
1
+ require 'driving_physics/car'
2
+ require 'driving_physics/imperial'
3
+ require 'driving_physics/cli'
4
+
5
+ include DrivingPhysics
6
+
7
+ env = Environment.new
8
+ puts env
9
+
10
+ tire = Tire.new(env)
11
+ motor = Motor.new(env)
12
+ gearbox = Gearbox.new(env)
13
+ powertrain = Powertrain.new(motor, gearbox)
14
+ car = Car.new(tire: tire, powertrain: powertrain) { |c|
15
+ c.body_mass = 850.0
16
+ c.frontal_area = 2.5
17
+ c.cd = 0.5
18
+ }
19
+ puts car
20
+ CLI.pause
21
+
22
+ duration = 120
23
+
24
+ acc = 0.0
25
+ speed = 0.0
26
+ dist = 0.0
27
+
28
+ tire_alpha = 0.0
29
+ tire_omega = 0.0
30
+ tire_theta = 0.0
31
+
32
+ crank_alpha = 0.0
33
+ crank_omega = 0.0
34
+ crank_theta = 0.0
35
+
36
+ start = Timer.now
37
+ paused = 0.0
38
+ num_ticks = duration * env.hz + 1
39
+
40
+ clutch = :ok
41
+ phase = :ignition
42
+ flag = false
43
+ rpm = 0
44
+
45
+
46
+
47
+ puts <<EOF
48
+
49
+ #
50
+ # IGNITION
51
+ #
52
+
53
+ EOF
54
+
55
+ num_ticks.times { |i|
56
+ if phase == :ignition
57
+ # ignition phase
58
+ crank_alpha = motor.alpha(motor.starter_torque, omega: crank_omega)
59
+ crank_omega += crank_alpha * env.tick
60
+ crank_theta += crank_omega * env.tick
61
+
62
+ rpm = DrivingPhysics.rpm(crank_omega)
63
+
64
+ if i % 100 == 0 or rpm > motor.idle_rpm
65
+ puts Timer.display(ms: i)
66
+ puts format("%d rad %d rad/s %d rad/s/s",
67
+ crank_theta, crank_omega, crank_alpha)
68
+ puts format("%d RPM %d Nm starter torque", rpm, motor.starter_torque)
69
+ puts
70
+ end
71
+
72
+ if rpm > motor.idle_rpm
73
+ car.gear = 1
74
+ car.throttle = 1.0
75
+ phase = :running
76
+
77
+ puts <<EOF
78
+
79
+ #
80
+ # RUNNING
81
+ #
82
+
83
+ EOF
84
+ end
85
+ elsif phase == :running
86
+ # track crank_alpha/omega/theta
87
+
88
+ # cut throttle after 60 s
89
+ car.throttle = 0 if i > 60 * env.hz and car.throttle == 1.0
90
+
91
+ ar = car.air_force(speed)
92
+ rr = car.tire_rolling_force(tire_omega)
93
+ rf = car.tire_rotational_force(tire_omega)
94
+
95
+ # note, this fails if we're in neutral
96
+ force = car.drive_force(rpm) + ar + rr + rf
97
+
98
+ ir = car.tire_inertial_force(force)
99
+ force += ir
100
+
101
+ acc = DrivingPhysics.acc(force, car.total_mass)
102
+ speed += acc * env.tick
103
+ dist += speed * env.tick
104
+
105
+ tire_alpha = acc / car.tire.radius
106
+ tire_omega += tire_alpha * env.tick
107
+ tire_theta += tire_omega * env.tick
108
+
109
+ crank_alpha = tire_alpha / car.powertrain.gearbox.ratio
110
+ crank_omega += crank_alpha * env.tick
111
+ crank_theta += crank_omega * env.tick
112
+
113
+ axle_torque = car.powertrain.axle_torque(rpm)
114
+ crank_torque = car.powertrain.motor.torque(rpm)
115
+
116
+ if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
117
+ puts Timer.display(ms: i)
118
+ puts format(" Tire: %.1f r/s/s %.2f r/s %.3f r",
119
+ tire_alpha, tire_omega, tire_theta)
120
+ puts format(" Car: %.1f m/s/s %.2f m/s %.3f m (%.1f MPH)",
121
+ acc, speed, dist, Imperial.mph(speed))
122
+ puts format(" Motor: %d RPM %.1f Nm", rpm, crank_torque)
123
+ puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
124
+ axle_torque, car.drive_force(rpm), force)
125
+ puts "Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
126
+ "#{s}: %.1f N"
127
+ }.join(' '), ar, rr, rf, ir)
128
+ puts
129
+ flag = false
130
+ end
131
+
132
+ # tire_omega determines new rpm
133
+ new_rpm = car.powertrain.crank_rpm(tire_omega)
134
+ new_clutch, proportion = car.powertrain.gearbox.match_rpms(rpm, new_rpm)
135
+
136
+ if new_clutch != clutch
137
+ flag = true
138
+ puts format("Clutch: [%s] %d RPM is %.1f%% from %d RPM",
139
+ new_clutch, new_rpm, proportion * 100, rpm)
140
+ clutch = new_clutch
141
+ paused += CLI.pause
142
+ end
143
+
144
+ case new_clutch
145
+ when :ok
146
+ rpm = new_rpm
147
+ when :mismatch
148
+ flag = true
149
+ puts '#'
150
+ puts '# LURCH!'
151
+ puts '#'
152
+ puts
153
+ rpm = new_rpm
154
+ end
155
+ next_gear = car.powertrain.gearbox.next_gear(rpm)
156
+ if next_gear != gearbox.gear
157
+ flag = true
158
+ puts "Gear Change: #{next_gear}"
159
+ car.gear = next_gear
160
+ paused += CLI.pause
161
+ end
162
+
163
+ # maintain idle when revs drop
164
+ if car.throttle == 0 and rpm < motor.idle_rpm
165
+ phase = :idling
166
+ car.gear = 0
167
+ paused += CLI.pause
168
+ end
169
+
170
+
171
+ elsif phase == :idling
172
+ # fake
173
+ rpm = motor.idle_rpm
174
+ break
175
+ end
176
+ }
177
+
178
+ puts Timer.summary(start, num_ticks, paused)
data/demo/disk.rb ADDED
@@ -0,0 +1,83 @@
1
+ require 'driving_physics/disk'
2
+ require 'driving_physics/cli'
3
+
4
+ include DrivingPhysics
5
+
6
+ env = Environment.new
7
+ disk = Disk.new(env)
8
+
9
+ puts env
10
+ puts disk
11
+ puts
12
+
13
+ axle_torque = 50
14
+ alpha = disk.alpha(axle_torque)
15
+ drive_force = disk.force(axle_torque)
16
+
17
+ puts [format("Axle torque: %.1f Nm", axle_torque),
18
+ format(" Alpha: %.1f rad/s/s", alpha),
19
+ format("Drive force: %.1f N", drive_force),
20
+ ].join("\n")
21
+ puts
22
+
23
+ puts "* Spin up the disk with #{axle_torque} Nm of torque"
24
+ puts "* Cut the power at some point"
25
+ puts "* Observe"
26
+ CLI.pause
27
+
28
+ duration = 750 # sec
29
+
30
+ dist = 0.0 # meters
31
+ speed = 0.0 # meters/s
32
+
33
+ theta = 0.0 # radians
34
+ omega = 0.0 # radians/s
35
+
36
+ paused = 0.0 # seconds
37
+ num_ticks = duration * env.hz + 1
38
+ flag = false # to display current stats
39
+ start = Timer.now
40
+
41
+ num_ticks.times { |i|
42
+ # shut off the powah!
43
+ if i == 18_950
44
+ flag = true
45
+ puts '#'
46
+ puts '# CUT POWER'
47
+ puts '#'
48
+ puts
49
+ axle_torque = 0
50
+ end
51
+
52
+ rotating_friction = disk.rotating_friction(omega)
53
+ net_torque = axle_torque + rotating_friction
54
+ net_force = disk.force(net_torque)
55
+
56
+ # rotational kinematics
57
+ alpha = disk.alpha(net_torque)
58
+ omega += alpha * env.tick
59
+ omega = 0.0 if omega.abs < 0.0001
60
+ theta += omega * env.tick
61
+
62
+ if flag or i < 10 or
63
+ (i < 20_000 and i%1000 == 0) or
64
+ (i % 10_000 == 0) or
65
+ i == duration * env.hz - 1
66
+
67
+ puts Timer.display(ms: i)
68
+ puts format(" Torque: %.1f Nm (%d Nm) Friction: %.1f Nm",
69
+ net_torque, axle_torque, rotating_friction)
70
+ puts format("Radians: %.1f r %.2f r/s %.3f r/s^2", theta, omega, alpha)
71
+ puts format(" Revs: %d revs %d revs/s %d rpm",
72
+ DrivingPhysics.revs(theta),
73
+ DrivingPhysics.revs(omega),
74
+ DrivingPhysics.rpm(omega))
75
+ puts
76
+ if flag
77
+ paused += CLI.pause
78
+ flag = false
79
+ end
80
+ end
81
+ }
82
+
83
+ puts Timer.summary(start, num_ticks, paused)
data/demo/gearbox.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'driving_physics/gearbox'
2
+ require 'driving_physics/cli'
3
+
4
+ include DrivingPhysics
5
+
6
+ env = Environment.new
7
+ gearbox = Gearbox.new(env)
8
+
9
+ torque = 30
10
+ duration = 30
11
+
12
+ puts env
13
+ puts gearbox
14
+ puts
15
+ puts "Spin up the gearbox with #{torque} Nm of input torque"
16
+ puts "How fast will it go in #{duration} seconds?"
17
+ CLI.pause
18
+
19
+ # rotational kinematics
20
+ alpha = 0.0
21
+ omega = 0.0
22
+ theta = 0.0
23
+
24
+ num_ticks = duration * env.hz + 1
25
+ start = Timer.now
26
+ paused = 0.0
27
+
28
+ num_ticks.times { |i|
29
+ # just for info, not used in the simulation
30
+ friction = gearbox.spinner.rotating_friction(omega)
31
+
32
+ # update rotational kinematics
33
+ # gearbox.alpha incorporates friction and inertia
34
+ alpha = gearbox.alpha(torque, omega: omega)
35
+ omega += alpha * env.tick
36
+ theta += omega * env.tick
37
+
38
+ net_torque = gearbox.implied_torque(alpha)
39
+
40
+ # periodic output
41
+ if i < 10 or
42
+ (i < 100 and i % 10 == 0) or
43
+ (i < 1000 and i % 100 == 0) or
44
+ i % 1000 == 0
45
+ puts Timer.display(ms: i)
46
+ puts format("RPM %d Torque: %.3f Nm (%d Nm) Friction: %.3f Nm",
47
+ DrivingPhysics.rpm(omega), net_torque, torque, friction)
48
+ puts format("%.1f rad %.1f rad/s %.1f rad/s/s", theta, omega, alpha)
49
+ puts
50
+ end
51
+ }
52
+
53
+ puts Timer.summary(start, num_ticks, paused)
data/demo/motor.rb ADDED
@@ -0,0 +1,140 @@
1
+ require 'driving_physics/motor'
2
+ require 'driving_physics/cli'
3
+ require 'driving_physics/power'
4
+
5
+ # next fun idea: PID controller
6
+
7
+ include DrivingPhysics
8
+
9
+ env = Environment.new
10
+ motor = Motor.new(env)
11
+ puts env
12
+ puts motor
13
+ puts
14
+
15
+ puts "Rev it up!"
16
+ motor.throttle = 1.0
17
+ [:torque, :power].each { |run|
18
+ puts
19
+ puts run.to_s.upcase + ':'
20
+
21
+ 800.upto(7000) { |rpm|
22
+ next unless rpm % 200 == 0
23
+ omega = DrivingPhysics.omega(rpm)
24
+ torque = motor.torque(rpm)
25
+ case run
26
+ when :torque
27
+ count = (torque.to_f / 10).round
28
+ char = 'T'
29
+ val = torque.round.to_s.rjust(5, ' ')
30
+ fmt = "%s Nm"
31
+ when :power
32
+ power = DrivingPhysics.power(torque, omega)
33
+ kw = power.to_f / 1000
34
+ count = (kw / 5).round
35
+ char = 'P'
36
+ val = kw.round(1).to_s.rjust(5, ' ')
37
+ fmt = "%s kW"
38
+ else
39
+ raise "unknown"
40
+ end
41
+ puts format("%s RPM: #{fmt}\t%s",
42
+ rpm.to_s.rjust(4, ' '),
43
+ val,
44
+ char * count)
45
+ }
46
+ }
47
+
48
+ puts
49
+ puts "Now, the simulation begins..."
50
+ puts "---"
51
+ puts "* Spin the motor up to #{motor.idle_rpm} RPM with the starter motor."
52
+ puts "* Rev it up with the throttle."
53
+ puts "* Let it die."
54
+ CLI.pause
55
+
56
+ alpha = 0.0
57
+ omega = 0.0
58
+ theta = 0.0
59
+
60
+ duration = 60
61
+
62
+ status = :ignition
63
+ rpm = 0
64
+
65
+ paused = 0.0
66
+ num_ticks = duration * env.hz + 1
67
+ start = Timer.now
68
+
69
+ num_ticks.times { |i|
70
+ # this is an input torque; alpha is determined after inertia and friction
71
+ torque = case status
72
+ when :ignition
73
+ motor.starter_torque
74
+ when :running, :off_throttle, :idling
75
+ motor.torque(rpm)
76
+ else
77
+ 0
78
+ end
79
+
80
+ # Motor#alpha incorporates inertia and friction
81
+ alpha = motor.alpha(torque, omega: omega)
82
+ omega += alpha * env.tick
83
+ theta += omega * env.tick
84
+
85
+ # prevent silly oscillations due to ticks or tiny floating point errors
86
+ omega = 0 if omega < 0.0001
87
+
88
+ net_torque = motor.implied_torque(alpha)
89
+ rpm = DrivingPhysics.rpm(omega)
90
+ power = DrivingPhysics.power(net_torque, omega)
91
+
92
+ if rpm > motor.idle_rpm and status == :ignition
93
+ status = :running
94
+ flag = true
95
+ end
96
+
97
+ if rpm > 7000 and status == :running
98
+ status = :off_throttle
99
+ motor.throttle = 0.0
100
+ flag = true
101
+ end
102
+
103
+ if rpm < 1000 and status == :off_throttle
104
+ status = :idling
105
+ motor.throttle = 0.06
106
+ flag = true
107
+ end
108
+
109
+ if status == :idling
110
+ if rpm < 999
111
+ motor.throttle += (1.0 - motor.throttle) * 0.005
112
+ elsif rpm > 1001
113
+ motor.throttle -= motor.throttle * 0.005
114
+ end
115
+ end
116
+
117
+ if flag or
118
+ (i < 10) or
119
+ (i < 100 and i % 10 == 0) or
120
+ (i < 1000 and i % 100 == 0) or
121
+ (i < 10_000 and i % 500 == 0) or
122
+ (i % 5000 == 0) or
123
+ (status == :idling and i % 100 == 0)
124
+ puts Timer.display(ms: i)
125
+ puts format("Throttle: %.1f%%", motor.throttle * 100)
126
+ puts format("%d RPM %.1f Nm (%d Nm) %.1f kW Friction: %.1f Nm",
127
+ DrivingPhysics.rpm(omega),
128
+ net_torque,
129
+ torque,
130
+ power / 1000,
131
+ motor.spinner.rotating_friction(omega))
132
+ puts format("%.3f rad/s/s %.2f rad/s %.1f rad", alpha, omega, theta)
133
+ puts
134
+
135
+ paused += CLI.pause if flag
136
+ flag = false
137
+ end
138
+ }
139
+
140
+ puts Timer.summary(start, num_ticks, paused)
@@ -0,0 +1,81 @@
1
+
2
+ include DrivingPhysics
3
+
4
+ env = Environment.new
5
+ disk = Disk.new(env)
6
+
7
+ puts env
8
+ puts disk
9
+ puts
10
+
11
+ axle_torque = 50
12
+ alpha = disk.alpha(axle_torque)
13
+ drive_force = disk.force(axle_torque)
14
+
15
+ puts [format("Axle torque: %.1f Nm", axle_torque),
16
+ format(" Alpha: %.1f rad/s/s", alpha),
17
+ format("Drive force: %.1f N", drive_force),
18
+ ].join("\n")
19
+ puts
20
+
21
+ puts "* Spin up the disk with #{axle_torque} Nm of torque"
22
+ puts "* Cut the power at some point"
23
+ puts "* Observe"
24
+ CLI.pause
25
+
26
+ duration = 750 # sec
27
+
28
+ dist = 0.0 # meters
29
+ speed = 0.0 # meters/s
30
+
31
+ theta = 0.0 # radians
32
+ omega = 0.0 # radians/s
33
+
34
+ paused = 0.0 # seconds
35
+ num_ticks = duration * env.hz + 1
36
+ flag = false # to display current stats
37
+ start = Timer.now
38
+
39
+ num_ticks.times { |i|
40
+ # shut off the powah!
41
+ if i == 18_950
42
+ flag = true
43
+ puts '#'
44
+ puts '# CUT POWER'
45
+ puts '#'
46
+ puts
47
+ axle_torque = 0
48
+ end
49
+
50
+ rotating_friction = disk.rotating_friction(omega)
51
+ net_torque = axle_torque + rotating_friction
52
+ net_force = disk.force(net_torque)
53
+
54
+ # rotational kinematics
55
+ alpha = disk.alpha(net_torque)
56
+ omega += alpha * env.tick
57
+ omega = 0.0 if omega.abs < 0.0001
58
+ theta += omega * env.tick
59
+
60
+ if flag or i < 10 or
61
+ (i < 20_000 and i%1000 == 0) or
62
+ (i % 10_000 == 0) or
63
+ i == duration * env.hz - 1
64
+
65
+ puts Timer.display(ms: i)
66
+ puts format(" Torque: %.1f Nm (%d Nm) Friction: %.1f Nm",
67
+ net_torque, axle_torque, rotating_friction)
68
+ puts format("Radians: %.1f r %.2f r/s %.3f r/s^2", theta, omega, alpha)
69
+ puts format(" Revs: %d revs %d revs/s %d rpm",
70
+ DrivingPhysics.revs(theta),
71
+ DrivingPhysics.revs(omega),
72
+ DrivingPhysics.rpm(omega))
73
+ puts
74
+ if flag
75
+ paused += CLI.pause
76
+ flag = false
77
+ end
78
+ end
79
+ }
80
+
81
+ puts Timer.summary(start, num_ticks, paused)