driving_physics 0.0.1.2 → 0.0.3.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: ef486c39982798a7331f49f601e648d520a65977161180631cc4c5d3340a9011
4
- data.tar.gz: 9e30b74ae34cb77a711d5d9631983b0c070872900123a1dde2d3dd03bdab854b
3
+ metadata.gz: 9b9159b499a0082d5af3ef926fceab925572552978b50d020b71bc51df7506fb
4
+ data.tar.gz: 183eaae3e07e24f38bb1417d96a6310eebfd288a1300d4e8bab96f008abbf3e5
5
5
  SHA512:
6
- metadata.gz: 46eaf512dad64ca39ec20451d7acce1b1819ecd64125ee858f92c26e5edd7b0ec0e7ef01af8f73daa0d37868e0ffd76bd3b29478093219cd906d8d9e38026bcd
7
- data.tar.gz: ae2e01ae8e7986c9d2e659ff5a0934ed5359e20cf2757d7724118bb59e0fb0199180249815f2f28f55f48a2336511a0b43f0d6fab042ed5d20a5ab6a897baef1
6
+ metadata.gz: 9a19ba4646abe21f30ca12c88f43e9ddc7083028e96504260f0277b167af5374364967b1617ab83744f824d6d2aa513ef38627d23ad5e9ccc8ff7e017b8a2ac8
7
+ data.tar.gz: 5357970cd547b12727bfc20c408df27a07d3aaac5d5b9042cbb10fc98c85484eb7bf054e140158f7e8695ef6c30eb18961fdd6979b4d0e3ab6c343f7767fc296
data/README.md CHANGED
@@ -10,14 +10,36 @@ using `Vector` class from `matrix.rb` in stdlib.
10
10
 
11
11
  This is very much a **Work In Progress**. Implemented so far:
12
12
 
13
+ ### Library Features
14
+
13
15
  * Spinning: rotational inertia and friction / hysteresis
14
- * Tires: traction force via friction and the normal force
16
+ * Tires: traction force via friction (static and kinetic) and the normal force
15
17
  * Vectors: 2D Vector forces and 3D Vector torques
16
18
  * Motor: torque curves, rotating mass with friction
17
19
  * Gearbox: gear ratios, final drive, rotating mass with friction
18
20
  * Powertrain: combines motor and gearbox
19
21
  * Car: 4 tires, powertrain, extra mass presents a load to the powertrain
20
22
  * Acceleration: via drive traction from "first principles"
23
+ * Variable clutch and throttle
24
+ * Engine braking
25
+ * Power and Energy
26
+
27
+ ### mruby Support
28
+
29
+ This library is primarily a Ruby library, intended for use with CRuby (MRI),
30
+ JRuby, etc. **mruby** is the redheaded stepchild, with significant
31
+ implications for a codebase that wants to support both sides. Supporting
32
+ mruby here meant:
33
+
34
+ 1. Editing the codebase (minimally) to avoid and remove *mruby-deadends*.
35
+ 2. Creating `rake` tasks to process normal .rb files into *mruby-compatible*
36
+ .rb files
37
+ 3. Assembling all needed library .rb files into a master .rb file
38
+ 4. Compiling the library to bytecode
39
+ 5. Executing a toplevel/main script against the library
40
+ 6. Optionally executing toplevel/main bytecode against the library
41
+
42
+ See the main Rakefile near the top for mruby stuff.
21
43
 
22
44
  ## Rationale
23
45
 
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[cli environment imperial power timer
44
+ disk tire motor gearbox powertrain car pid_controller].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 pid_controller].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.1.2
1
+ 0.0.3.1
data/demo/car.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'driving_physics/car'
2
+ require 'driving_physics/cockpit'
2
3
  require 'driving_physics/imperial'
3
4
  require 'driving_physics/cli'
4
5
 
@@ -10,13 +11,16 @@ puts env
10
11
  tire = Tire.new(env)
11
12
  motor = Motor.new(env)
12
13
  gearbox = Gearbox.new(env)
13
- powertrain = Powertrain.new(motor, gearbox)
14
+ powertrain = Powertrain.new(motor: motor, gearbox: gearbox)
14
15
  car = Car.new(tire: tire, powertrain: powertrain) { |c|
15
16
  c.body_mass = 850.0
16
17
  c.frontal_area = 2.5
17
18
  c.cd = 0.5
18
19
  }
19
20
  puts car
21
+
22
+ cockpit = Cockpit.new(car)
23
+ puts cockpit
20
24
  CLI.pause
21
25
 
22
26
  duration = 120
@@ -31,16 +35,20 @@ tire_theta = 0.0
31
35
 
32
36
  crank_alpha = 0.0
33
37
  crank_omega = 0.0
34
- crank_theta = 0.0
35
38
 
36
39
  start = Timer.now
37
40
  paused = 0.0
38
41
  num_ticks = duration * env.hz + 1
39
42
 
40
- clutch = :ok
43
+ rev_match = :ok
41
44
  phase = :ignition
45
+ gearbox.clutch = 0.0 # clutch in to fire up motor
42
46
  flag = false
43
- rpm = 0
47
+
48
+ rpm, crank_torque = 0, 0
49
+ axle_torque, drive_force, net_force = 0, 0, 0
50
+ ar, rr, rf, ir = 0, 0, 0, 0
51
+
44
52
  puts <<EOF
45
53
 
46
54
  #
@@ -51,24 +59,15 @@ EOF
51
59
 
52
60
  num_ticks.times { |i|
53
61
  if phase == :ignition
54
- # ignition phase
55
62
  crank_alpha = motor.alpha(motor.starter_torque, omega: crank_omega)
56
63
  crank_omega += crank_alpha * env.tick
57
- crank_theta += crank_omega * env.tick
58
64
 
59
65
  rpm = DrivingPhysics.rpm(crank_omega)
60
66
 
61
- if i % 100 == 0 or rpm > motor.idle_rpm
62
- puts Timer.display(i)
63
- puts format("%d rad %d rad/s %d rad/s/s",
64
- crank_theta, crank_omega, crank_alpha)
65
- puts format("%d RPM %d Nm starter torque", rpm, motor.starter_torque)
66
- puts
67
- end
68
-
69
- if rpm > motor.idle_rpm
70
- car.gear = 1
71
- car.throttle = 1.0
67
+ if rpm > motor.idle
68
+ flag = true
69
+ cockpit.gear = 1
70
+ cockpit.throttle_pedal = 1.0
72
71
  phase = :running
73
72
 
74
73
  puts <<EOF
@@ -79,23 +78,19 @@ num_ticks.times { |i|
79
78
 
80
79
  EOF
81
80
  end
82
- elsif phase == :running
83
- # track crank_alpha/omega/theta
84
-
85
- # cut throttle after 60 s
86
- car.throttle = 0 if i > 60 * env.hz and car.throttle == 1.0
87
-
81
+ elsif phase == :running or phase == :off_throttle
88
82
  ar = car.air_force(speed)
89
83
  rr = car.tire_rolling_force(tire_omega)
90
84
  rf = car.tire_rotational_force(tire_omega)
91
85
 
92
86
  # note, this fails if we're in neutral
93
- force = car.drive_force(rpm) + ar + rr + rf
87
+ drive_force = car.drive_force(rpm, axle_omega: tire_omega)
88
+ net_force = drive_force + ar + rr + rf
94
89
 
95
- ir = car.tire_inertial_force(force)
96
- force += ir
90
+ ir = car.tire_inertial_force(net_force)
91
+ net_force += ir
97
92
 
98
- acc = DrivingPhysics.acc(force, car.total_mass)
93
+ acc = DrivingPhysics.acc(net_force, car.total_mass)
99
94
  speed += acc * env.tick
100
95
  dist += speed * env.tick
101
96
 
@@ -105,71 +100,89 @@ EOF
105
100
 
106
101
  crank_alpha = tire_alpha / car.powertrain.gearbox.ratio
107
102
  crank_omega += crank_alpha * env.tick
108
- crank_theta += crank_omega * env.tick
109
-
110
- axle_torque = car.powertrain.axle_torque(rpm)
111
- crank_torque = car.powertrain.motor.torque(rpm)
112
-
113
- if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
114
- puts Timer.display(i)
115
- puts format(" Tire: %.1f r/s/s %.2f r/s %.3f r",
116
- tire_alpha, tire_omega, tire_theta)
117
- puts format(" Car: %.1f m/s/s %.2f m/s %.3f m (%.1f MPH)",
118
- acc, speed, dist, Imperial.mph(speed))
119
- puts format(" Motor: %d RPM %.1f Nm", rpm, crank_torque)
120
- puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
121
- axle_torque, car.drive_force(rpm), force)
122
- puts "Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
123
- "#{s}: %.1f N"
124
- }.join(' '), ar, rr, rf, ir)
125
- puts
126
- flag = false
127
- end
128
103
 
129
104
  # tire_omega determines new rpm
130
- new_rpm = car.powertrain.crank_rpm(tire_omega)
131
- new_clutch, proportion = car.powertrain.gearbox.match_rpms(rpm, new_rpm)
105
+ new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
106
+ new_rev_match, clutch, proportion = Cockpit.rev_match(rpm, new_rpm)
132
107
 
133
- if new_clutch != clutch
108
+ if new_rev_match != rev_match
134
109
  flag = true
135
- puts format("Clutch: [%s] %d RPM is %.1f%% from %d RPM",
136
- new_clutch, new_rpm, proportion * 100, rpm)
137
- clutch = new_clutch
138
- paused += CLI.pause
110
+ puts format("Rev Match: [%s] %d RPM is %.1f%% from %d RPM",
111
+ new_rev_match, new_rpm, proportion * 100, rpm)
112
+ # puts format("Recommend clutch to %.1f%%", clutch * 100)
113
+ rev_match = new_rev_match
139
114
  end
140
115
 
141
- case new_clutch
142
- when :ok
143
- rpm = new_rpm
144
- when :mismatch
116
+ clutch_diff = clutch - gearbox.clutch
117
+ if clutch_diff.abs > 0.1
145
118
  flag = true
146
- puts '#'
147
- puts '# LURCH!'
148
- puts '#'
149
- puts
150
- rpm = new_rpm
119
+ puts format("Clutch: %.1f%% Recommended Clutch: %.1f%%",
120
+ gearbox.clutch * 100, clutch * 100)
151
121
  end
152
- next_gear = car.powertrain.gearbox.next_gear(rpm)
153
- if next_gear != gearbox.gear
122
+ # the clutch pedal will reflect this change
123
+ gearbox.clutch += clutch_diff * 0.5
124
+
125
+ # update the motor RPM based on new clutch
126
+ new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
127
+ rpm = new_rpm if new_rpm > motor.idle
128
+
129
+ new_gear = cockpit.choose_gear(rpm)
130
+ if new_gear != cockpit.gear
131
+ flag = true
132
+ puts "Gear Change: #{new_gear}"
133
+ cockpit.gear = new_gear
134
+ end
135
+
136
+ # cut throttle after 60 s
137
+ if i > 60 * env.hz and car.throttle == 1.0
154
138
  flag = true
155
- puts "Gear Change: #{next_gear}"
156
- car.gear = next_gear
157
- paused += CLI.pause
139
+ phase = :off_throttle
140
+ cockpit.throttle_pedal = 0
158
141
  end
159
142
 
160
143
  # maintain idle when revs drop
161
- if car.throttle == 0 and rpm < motor.idle_rpm
144
+ if cockpit.throttle_pedal == 0 and rpm < motor.idle
162
145
  phase = :idling
163
146
  car.gear = 0
164
- paused += CLI.pause
165
147
  end
166
148
 
149
+ print "===\n\n" if flag
167
150
 
168
151
  elsif phase == :idling
169
- # fake
170
- rpm = motor.idle_rpm
152
+ # fake; exit
153
+ rpm = motor.idle
171
154
  break
172
155
  end
156
+
157
+ if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
158
+ puts Timer.display(ms: i)
159
+ puts format(" Phase: %s", phase)
160
+ puts format(" Tire: %.3f r/s/s %.2f r/s %.1f r",
161
+ tire_alpha, tire_omega, tire_theta)
162
+ puts format(" Car: %.3f m/s/s %.2f m/s %.1f m (%.1f MPH)",
163
+ acc, speed, dist, Imperial.mph(speed))
164
+ if phase == :ignition
165
+ puts format(" Motor: %d RPM Starter: %d Nm Friction: %.1f Nm",
166
+ rpm, motor.starter_torque, motor.friction(crank_omega))
167
+ else
168
+ crank_torque = car.powertrain.motor.torque(rpm)
169
+ puts format(" Motor: %d RPM %.1f Nm Friction: %.1f Nm",
170
+ rpm, crank_torque, motor.friction(crank_omega))
171
+ end
172
+ puts format("Gearbox: %s", gearbox.inputs)
173
+ if phase != :ignition
174
+ axle_torque = car.powertrain.axle_torque(rpm, axle_omega: tire_omega)
175
+ puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
176
+ axle_torque, drive_force, net_force)
177
+ end
178
+ puts " Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
179
+ "#{s}: %.1f N"
180
+ }.join(' '), ar, rr, rf, ir)
181
+ puts cockpit
182
+ puts
183
+ paused += CLI.pause if flag
184
+ flag = false
185
+ end
173
186
  }
174
187
 
175
188
  puts Timer.summary(start, num_ticks, paused)
data/demo/gearbox.rb CHANGED
@@ -21,10 +21,11 @@ alpha = 0.0
21
21
  omega = 0.0
22
22
  theta = 0.0
23
23
 
24
+ num_ticks = duration * env.hz + 1
24
25
  start = Timer.now
25
- paused = 0
26
+ paused = 0.0
26
27
 
27
- (duration * env.hz + 1).times { |i|
28
+ num_ticks.times { |i|
28
29
  # just for info, not used in the simulation
29
30
  friction = gearbox.spinner.rotating_friction(omega)
30
31
 
@@ -41,7 +42,7 @@ paused = 0
41
42
  (i < 100 and i % 10 == 0) or
42
43
  (i < 1000 and i % 100 == 0) or
43
44
  i % 1000 == 0
44
- puts Timer.display(i)
45
+ puts Timer.display(ms: i)
45
46
  puts format("RPM %d Torque: %.3f Nm (%d Nm) Friction: %.3f Nm",
46
47
  DrivingPhysics.rpm(omega), net_torque, torque, friction)
47
48
  puts format("%.1f rad %.1f rad/s %.1f rad/s/s", theta, omega, alpha)
data/demo/motor.rb CHANGED
@@ -48,7 +48,7 @@ motor.throttle = 1.0
48
48
  puts
49
49
  puts "Now, the simulation begins..."
50
50
  puts "---"
51
- puts "* Spin the motor up to #{motor.idle_rpm} RPM with the starter motor."
51
+ puts "* Spin the motor up to #{motor.idle} RPM with the starter motor."
52
52
  puts "* Rev it up with the throttle."
53
53
  puts "* Let it die."
54
54
  CLI.pause
@@ -82,15 +82,14 @@ num_ticks.times { |i|
82
82
  omega += alpha * env.tick
83
83
  theta += omega * env.tick
84
84
 
85
- net_torque = motor.implied_torque(alpha)
85
+ # prevent silly oscillations due to ticks or tiny floating point errors
86
+ omega = 0 if omega < 0.0001
86
87
 
87
- # prevent silly oscillations due to tiny floating point errors
88
- omega = 0 if omega < 0.00001
88
+ net_torque = motor.implied_torque(alpha)
89
89
  rpm = DrivingPhysics.rpm(omega)
90
-
91
90
  power = DrivingPhysics.power(net_torque, omega)
92
91
 
93
- if rpm > motor.idle_rpm and status == :ignition
92
+ if rpm > motor.idle and status == :ignition
94
93
  status = :running
95
94
  flag = true
96
95
  end
@@ -107,10 +106,13 @@ num_ticks.times { |i|
107
106
  flag = true
108
107
  end
109
108
 
109
+ # we don't need no PID control
110
110
  if status == :idling
111
- if rpm < 999
111
+ if rpm.round <= 999
112
112
  motor.throttle += (1.0 - motor.throttle) * 0.005
113
- elsif rpm > 1001
113
+ elsif rpm.round == 1000
114
+ motor.throttle += (rand - 0.5) * 0.0005
115
+ else
114
116
  motor.throttle -= motor.throttle * 0.005
115
117
  end
116
118
  end
@@ -122,7 +124,7 @@ num_ticks.times { |i|
122
124
  (i < 10_000 and i % 500 == 0) or
123
125
  (i % 5000 == 0) or
124
126
  (status == :idling and i % 100 == 0)
125
- puts Timer.display(i)
127
+ puts Timer.display(ms: i)
126
128
  puts format("Throttle: %.1f%%", motor.throttle * 100)
127
129
  puts format("%d RPM %.1f Nm (%d Nm) %.1f kW Friction: %.1f Nm",
128
130
  DrivingPhysics.rpm(omega),
@@ -130,7 +132,7 @@ num_ticks.times { |i|
130
132
  torque,
131
133
  power / 1000,
132
134
  motor.spinner.rotating_friction(omega))
133
- puts format("%d rad %.1f rad/s %.1f rad/s/s", theta, omega, alpha)
135
+ puts format("%.3f rad/s/s %.2f rad/s %.1f rad", alpha, omega, theta)
134
136
  puts
135
137
 
136
138
  paused += CLI.pause if flag
data/demo/mruby/car.rb ADDED
@@ -0,0 +1,184 @@
1
+
2
+ include DrivingPhysics
3
+
4
+ env = Environment.new
5
+ puts env
6
+
7
+ tire = Tire.new(env)
8
+ motor = Motor.new(env)
9
+ gearbox = Gearbox.new(env)
10
+ powertrain = Powertrain.new(motor: motor, gearbox: gearbox)
11
+ car = Car.new(tire: tire, powertrain: powertrain) { |c|
12
+ c.body_mass = 850.0
13
+ c.frontal_area = 2.5
14
+ c.cd = 0.5
15
+ }
16
+ puts car
17
+
18
+ cockpit = Cockpit.new(car)
19
+ puts cockpit
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
+
35
+ start = Timer.now
36
+ paused = 0.0
37
+ num_ticks = duration * env.hz + 1
38
+
39
+ rev_match = :ok
40
+ phase = :ignition
41
+ gearbox.clutch = 0.0 # clutch in to fire up motor
42
+ flag = false
43
+
44
+ rpm, crank_torque = 0, 0
45
+ axle_torque, drive_force, net_force = 0, 0, 0
46
+ ar, rr, rf, ir = 0, 0, 0, 0
47
+
48
+ puts <<EOF
49
+
50
+ #
51
+ # IGNITION
52
+ #
53
+
54
+ EOF
55
+
56
+ num_ticks.times { |i|
57
+ if phase == :ignition
58
+ crank_alpha = motor.alpha(motor.starter_torque, omega: crank_omega)
59
+ crank_omega += crank_alpha * env.tick
60
+
61
+ rpm = DrivingPhysics.rpm(crank_omega)
62
+
63
+ if rpm > motor.idle
64
+ flag = true
65
+ cockpit.gear = 1
66
+ cockpit.throttle_pedal = 1.0
67
+ phase = :running
68
+
69
+ puts <<EOF
70
+
71
+ #
72
+ # RUNNING
73
+ #
74
+
75
+ EOF
76
+ end
77
+ elsif phase == :running or phase == :off_throttle
78
+ ar = car.air_force(speed)
79
+ rr = car.tire_rolling_force(tire_omega)
80
+ rf = car.tire_rotational_force(tire_omega)
81
+
82
+ # note, this fails if we're in neutral
83
+ drive_force = car.drive_force(rpm, axle_omega: tire_omega)
84
+ net_force = drive_force + ar + rr + rf
85
+
86
+ ir = car.tire_inertial_force(net_force)
87
+ net_force += ir
88
+
89
+ acc = DrivingPhysics.acc(net_force, car.total_mass)
90
+ speed += acc * env.tick
91
+ dist += speed * env.tick
92
+
93
+ tire_alpha = acc / car.tire.radius
94
+ tire_omega += tire_alpha * env.tick
95
+ tire_theta += tire_omega * env.tick
96
+
97
+ crank_alpha = tire_alpha / car.powertrain.gearbox.ratio
98
+ crank_omega += crank_alpha * env.tick
99
+
100
+ # tire_omega determines new rpm
101
+ new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
102
+ new_rev_match, clutch, proportion = Cockpit.rev_match(rpm, new_rpm)
103
+
104
+ if new_rev_match != rev_match
105
+ flag = true
106
+ puts format("Rev Match: [%s] %d RPM is %.1f%% from %d RPM",
107
+ new_rev_match, new_rpm, proportion * 100, rpm)
108
+ # puts format("Recommend clutch to %.1f%%", clutch * 100)
109
+ rev_match = new_rev_match
110
+ end
111
+
112
+ clutch_diff = clutch - gearbox.clutch
113
+ if clutch_diff.abs > 0.1
114
+ flag = true
115
+ puts format("Clutch: %.1f%% Recommended Clutch: %.1f%%",
116
+ gearbox.clutch * 100, clutch * 100)
117
+ end
118
+ # the clutch pedal will reflect this change
119
+ gearbox.clutch += clutch_diff * 0.5
120
+
121
+ # update the motor RPM based on new clutch
122
+ new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
123
+ rpm = new_rpm if new_rpm > motor.idle
124
+
125
+ new_gear = cockpit.choose_gear(rpm)
126
+ if new_gear != cockpit.gear
127
+ flag = true
128
+ puts "Gear Change: #{new_gear}"
129
+ cockpit.gear = new_gear
130
+ end
131
+
132
+ # cut throttle after 60 s
133
+ if i > 60 * env.hz and car.throttle == 1.0
134
+ flag = true
135
+ phase = :off_throttle
136
+ cockpit.throttle_pedal = 0
137
+ end
138
+
139
+ # maintain idle when revs drop
140
+ if cockpit.throttle_pedal == 0 and rpm < motor.idle
141
+ phase = :idling
142
+ car.gear = 0
143
+ end
144
+
145
+ print "===\n\n" if flag
146
+
147
+ elsif phase == :idling
148
+ # fake; exit
149
+ rpm = motor.idle
150
+ break
151
+ end
152
+
153
+ if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
154
+ puts Timer.display(ms: i)
155
+ puts format(" Phase: %s", phase)
156
+ puts format(" Tire: %.3f r/s/s %.2f r/s %.1f r",
157
+ tire_alpha, tire_omega, tire_theta)
158
+ puts format(" Car: %.3f m/s/s %.2f m/s %.1f m (%.1f MPH)",
159
+ acc, speed, dist, Imperial.mph(speed))
160
+ if phase == :ignition
161
+ puts format(" Motor: %d RPM Starter: %d Nm Friction: %.1f Nm",
162
+ rpm, motor.starter_torque, motor.friction(crank_omega))
163
+ else
164
+ crank_torque = car.powertrain.motor.torque(rpm)
165
+ puts format(" Motor: %d RPM %.1f Nm Friction: %.1f Nm",
166
+ rpm, crank_torque, motor.friction(crank_omega))
167
+ end
168
+ puts format("Gearbox: %s", gearbox.inputs)
169
+ if phase != :ignition
170
+ axle_torque = car.powertrain.axle_torque(rpm, axle_omega: tire_omega)
171
+ puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
172
+ axle_torque, drive_force, net_force)
173
+ end
174
+ puts " Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
175
+ "#{s}: %.1f N"
176
+ }.join(' '), ar, rr, rf, ir)
177
+ puts cockpit
178
+ puts
179
+ paused += CLI.pause if flag
180
+ flag = false
181
+ end
182
+ }
183
+
184
+ puts Timer.summary(start, num_ticks, paused)