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 +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)
|