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 +4 -4
- data/README.md +31 -1
- data/Rakefile +84 -0
- data/VERSION +1 -1
- data/demo/car.rb +178 -0
- data/demo/disk.rb +83 -0
- data/demo/gearbox.rb +53 -0
- data/demo/motor.rb +140 -0
- data/demo/mruby/disk.rb +81 -0
- data/demo/mruby/motor.rb +137 -0
- data/demo/powertrain.rb +47 -0
- data/demo/scalar_force.rb +41 -15
- data/demo/tire.rb +87 -0
- data/demo/vector_force.rb +46 -16
- data/lib/driving_physics/car.rb +123 -0
- data/lib/driving_physics/cli.rb +51 -0
- data/lib/driving_physics/disk.rb +185 -0
- data/lib/driving_physics/gearbox.rb +109 -0
- data/lib/driving_physics/imperial.rb +6 -0
- data/lib/driving_physics/motor.rb +150 -0
- data/lib/driving_physics/mruby.rb +45 -0
- data/lib/driving_physics/power.rb +20 -0
- data/lib/driving_physics/powertrain.rb +50 -0
- data/lib/driving_physics/scalar_force.rb +6 -3
- data/lib/driving_physics/tire.rb +120 -0
- data/lib/driving_physics/vector_force.rb +15 -2
- data/lib/driving_physics.rb +2 -0
- data/test/disk.rb +132 -0
- data/test/scalar_force.rb +7 -5
- data/test/{wheel.rb → tire.rb} +65 -55
- data/test/vector_force.rb +7 -1
- metadata +20 -4
- data/demo/wheel.rb +0 -84
- data/lib/driving_physics/wheel.rb +0 -191
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89628c4f71193088741435eb426419edf7c9ac78cad2169c4718f2a365db0881
|
4
|
+
data.tar.gz: a229fa52ed29f6dcb8eab0513e845732e0735dd272e516e4f26603aeabe7f312
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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.
|
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)
|
data/demo/mruby/disk.rb
ADDED
@@ -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)
|