driving_physics 0.0.0.3 → 0.0.2.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.
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)