driving_physics 0.0.1.2 → 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 +20 -1
- data/Rakefile +84 -0
- data/VERSION +1 -1
- data/demo/car.rb +5 -2
- data/demo/gearbox.rb +4 -3
- data/demo/motor.rb +5 -6
- data/demo/mruby/disk.rb +81 -0
- data/demo/mruby/motor.rb +137 -0
- data/lib/driving_physics/car.rb +1 -1
- data/lib/driving_physics/disk.rb +5 -5
- data/lib/driving_physics/gearbox.rb +2 -2
- data/lib/driving_physics/motor.rb +55 -8
- data/lib/driving_physics/mruby.rb +45 -0
- data/lib/driving_physics/tire.rb +6 -6
- data/test/disk.rb +3 -0
- data/test/tire.rb +6 -0
- metadata +4 -1
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
@@ -10,8 +10,27 @@ 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
|
+
### 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
|
+
|
13
32
|
* Spinning: rotational inertia and friction / hysteresis
|
14
|
-
* Tires: traction force via friction and the normal force
|
33
|
+
* Tires: traction force via friction (static and kinetic) and the normal force
|
15
34
|
* Vectors: 2D Vector forces and 3D Vector torques
|
16
35
|
* Motor: torque curves, rotating mass with friction
|
17
36
|
* Gearbox: gear ratios, final drive, rotating mass with friction
|
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
|
1
|
+
0.0.2.1
|
data/demo/car.rb
CHANGED
@@ -41,6 +41,9 @@ clutch = :ok
|
|
41
41
|
phase = :ignition
|
42
42
|
flag = false
|
43
43
|
rpm = 0
|
44
|
+
|
45
|
+
|
46
|
+
|
44
47
|
puts <<EOF
|
45
48
|
|
46
49
|
#
|
@@ -59,7 +62,7 @@ num_ticks.times { |i|
|
|
59
62
|
rpm = DrivingPhysics.rpm(crank_omega)
|
60
63
|
|
61
64
|
if i % 100 == 0 or rpm > motor.idle_rpm
|
62
|
-
puts Timer.display(i)
|
65
|
+
puts Timer.display(ms: i)
|
63
66
|
puts format("%d rad %d rad/s %d rad/s/s",
|
64
67
|
crank_theta, crank_omega, crank_alpha)
|
65
68
|
puts format("%d RPM %d Nm starter torque", rpm, motor.starter_torque)
|
@@ -111,7 +114,7 @@ EOF
|
|
111
114
|
crank_torque = car.powertrain.motor.torque(rpm)
|
112
115
|
|
113
116
|
if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
|
114
|
-
puts Timer.display(i)
|
117
|
+
puts Timer.display(ms: i)
|
115
118
|
puts format(" Tire: %.1f r/s/s %.2f r/s %.3f r",
|
116
119
|
tire_alpha, tire_omega, tire_theta)
|
117
120
|
puts format(" Car: %.1f m/s/s %.2f m/s %.3f m (%.1f MPH)",
|
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
|
-
|
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
@@ -82,12 +82,11 @@ num_ticks.times { |i|
|
|
82
82
|
omega += alpha * env.tick
|
83
83
|
theta += omega * env.tick
|
84
84
|
|
85
|
-
|
85
|
+
# prevent silly oscillations due to ticks or tiny floating point errors
|
86
|
+
omega = 0 if omega < 0.0001
|
86
87
|
|
87
|
-
|
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
92
|
if rpm > motor.idle_rpm and status == :ignition
|
@@ -122,7 +121,7 @@ num_ticks.times { |i|
|
|
122
121
|
(i < 10_000 and i % 500 == 0) or
|
123
122
|
(i % 5000 == 0) or
|
124
123
|
(status == :idling and i % 100 == 0)
|
125
|
-
puts Timer.display(i)
|
124
|
+
puts Timer.display(ms: i)
|
126
125
|
puts format("Throttle: %.1f%%", motor.throttle * 100)
|
127
126
|
puts format("%d RPM %.1f Nm (%d Nm) %.1f kW Friction: %.1f Nm",
|
128
127
|
DrivingPhysics.rpm(omega),
|
@@ -130,7 +129,7 @@ num_ticks.times { |i|
|
|
130
129
|
torque,
|
131
130
|
power / 1000,
|
132
131
|
motor.spinner.rotating_friction(omega))
|
133
|
-
puts format("
|
132
|
+
puts format("%.3f rad/s/s %.2f rad/s %.1f rad", alpha, omega, theta)
|
134
133
|
puts
|
135
134
|
|
136
135
|
paused += CLI.pause if flag
|
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)
|
data/demo/mruby/motor.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
|
2
|
+
# next fun idea: PID controller
|
3
|
+
|
4
|
+
include DrivingPhysics
|
5
|
+
|
6
|
+
env = Environment.new
|
7
|
+
motor = Motor.new(env)
|
8
|
+
puts env
|
9
|
+
puts motor
|
10
|
+
puts
|
11
|
+
|
12
|
+
puts "Rev it up!"
|
13
|
+
motor.throttle = 1.0
|
14
|
+
[:torque, :power].each { |run|
|
15
|
+
puts
|
16
|
+
puts run.to_s.upcase + ':'
|
17
|
+
|
18
|
+
800.upto(7000) { |rpm|
|
19
|
+
next unless rpm % 200 == 0
|
20
|
+
omega = DrivingPhysics.omega(rpm)
|
21
|
+
torque = motor.torque(rpm)
|
22
|
+
case run
|
23
|
+
when :torque
|
24
|
+
count = (torque.to_f / 10).round
|
25
|
+
char = 'T'
|
26
|
+
val = torque.round.to_s.rjust(5, ' ')
|
27
|
+
fmt = "%s Nm"
|
28
|
+
when :power
|
29
|
+
power = DrivingPhysics.power(torque, omega)
|
30
|
+
kw = power.to_f / 1000
|
31
|
+
count = (kw / 5).round
|
32
|
+
char = 'P'
|
33
|
+
val = kw.round(1).to_s.rjust(5, ' ')
|
34
|
+
fmt = "%s kW"
|
35
|
+
else
|
36
|
+
raise "unknown"
|
37
|
+
end
|
38
|
+
puts format("%s RPM: #{fmt}\t%s",
|
39
|
+
rpm.to_s.rjust(4, ' '),
|
40
|
+
val,
|
41
|
+
char * count)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
puts
|
46
|
+
puts "Now, the simulation begins..."
|
47
|
+
puts "---"
|
48
|
+
puts "* Spin the motor up to #{motor.idle_rpm} RPM with the starter motor."
|
49
|
+
puts "* Rev it up with the throttle."
|
50
|
+
puts "* Let it die."
|
51
|
+
CLI.pause
|
52
|
+
|
53
|
+
alpha = 0.0
|
54
|
+
omega = 0.0
|
55
|
+
theta = 0.0
|
56
|
+
|
57
|
+
duration = 60
|
58
|
+
|
59
|
+
status = :ignition
|
60
|
+
rpm = 0
|
61
|
+
|
62
|
+
paused = 0.0
|
63
|
+
num_ticks = duration * env.hz + 1
|
64
|
+
start = Timer.now
|
65
|
+
|
66
|
+
num_ticks.times { |i|
|
67
|
+
# this is an input torque; alpha is determined after inertia and friction
|
68
|
+
torque = case status
|
69
|
+
when :ignition
|
70
|
+
motor.starter_torque
|
71
|
+
when :running, :off_throttle, :idling
|
72
|
+
motor.torque(rpm)
|
73
|
+
else
|
74
|
+
0
|
75
|
+
end
|
76
|
+
|
77
|
+
# Motor#alpha incorporates inertia and friction
|
78
|
+
alpha = motor.alpha(torque, omega: omega)
|
79
|
+
omega += alpha * env.tick
|
80
|
+
theta += omega * env.tick
|
81
|
+
|
82
|
+
# prevent silly oscillations due to ticks or tiny floating point errors
|
83
|
+
omega = 0 if omega < 0.0001
|
84
|
+
|
85
|
+
net_torque = motor.implied_torque(alpha)
|
86
|
+
rpm = DrivingPhysics.rpm(omega)
|
87
|
+
power = DrivingPhysics.power(net_torque, omega)
|
88
|
+
|
89
|
+
if rpm > motor.idle_rpm and status == :ignition
|
90
|
+
status = :running
|
91
|
+
flag = true
|
92
|
+
end
|
93
|
+
|
94
|
+
if rpm > 7000 and status == :running
|
95
|
+
status = :off_throttle
|
96
|
+
motor.throttle = 0.0
|
97
|
+
flag = true
|
98
|
+
end
|
99
|
+
|
100
|
+
if rpm < 1000 and status == :off_throttle
|
101
|
+
status = :idling
|
102
|
+
motor.throttle = 0.06
|
103
|
+
flag = true
|
104
|
+
end
|
105
|
+
|
106
|
+
if status == :idling
|
107
|
+
if rpm < 999
|
108
|
+
motor.throttle += (1.0 - motor.throttle) * 0.005
|
109
|
+
elsif rpm > 1001
|
110
|
+
motor.throttle -= motor.throttle * 0.005
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if flag or
|
115
|
+
(i < 10) or
|
116
|
+
(i < 100 and i % 10 == 0) or
|
117
|
+
(i < 1000 and i % 100 == 0) or
|
118
|
+
(i < 10_000 and i % 500 == 0) or
|
119
|
+
(i % 5000 == 0) or
|
120
|
+
(status == :idling and i % 100 == 0)
|
121
|
+
puts Timer.display(ms: i)
|
122
|
+
puts format("Throttle: %.1f%%", motor.throttle * 100)
|
123
|
+
puts format("%d RPM %.1f Nm (%d Nm) %.1f kW Friction: %.1f Nm",
|
124
|
+
DrivingPhysics.rpm(omega),
|
125
|
+
net_torque,
|
126
|
+
torque,
|
127
|
+
power / 1000,
|
128
|
+
motor.spinner.rotating_friction(omega))
|
129
|
+
puts format("%.3f rad/s/s %.2f rad/s %.1f rad", alpha, omega, theta)
|
130
|
+
puts
|
131
|
+
|
132
|
+
paused += CLI.pause if flag
|
133
|
+
flag = false
|
134
|
+
end
|
135
|
+
}
|
136
|
+
|
137
|
+
puts Timer.summary(start, num_ticks, paused)
|
data/lib/driving_physics/car.rb
CHANGED
data/lib/driving_physics/disk.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'driving_physics/environment'
|
2
|
-
require 'driving_physics/vector_force'
|
2
|
+
#require 'driving_physics/vector_force'
|
3
3
|
|
4
4
|
module DrivingPhysics
|
5
5
|
# radius is always in meters
|
@@ -110,11 +110,11 @@ module DrivingPhysics
|
|
110
110
|
|
111
111
|
def initialize(env)
|
112
112
|
@env = env
|
113
|
-
@radius =
|
114
|
-
@width =
|
113
|
+
@radius = 0.35
|
114
|
+
@width = 0.2
|
115
115
|
@density = DENSITY
|
116
|
-
@base_friction = 5/
|
117
|
-
@omega_friction = 5/
|
116
|
+
@base_friction = 5.0/100_000 # constant resistance to rotation
|
117
|
+
@omega_friction = 5.0/100_000 # scales with omega
|
118
118
|
yield self if block_given?
|
119
119
|
end
|
120
120
|
|
@@ -27,8 +27,8 @@ module DrivingPhysics
|
|
27
27
|
# represent all rotating mass
|
28
28
|
@spinner = Disk.new(env) { |m|
|
29
29
|
m.radius = 0.15
|
30
|
-
m.base_friction = 5/
|
31
|
-
m.omega_friction = 15/
|
30
|
+
m.base_friction = 5.0/1000
|
31
|
+
m.omega_friction = 15.0/100_000
|
32
32
|
m.mass = 15
|
33
33
|
}
|
34
34
|
@fixed_mass = 30 # kg
|
@@ -25,8 +25,8 @@ module DrivingPhysics
|
|
25
25
|
@spinner = Disk.new(@env) { |fly|
|
26
26
|
fly.radius = 0.25 # m
|
27
27
|
fly.mass = 75 # kg
|
28
|
-
fly.base_friction = 5/
|
29
|
-
fly.omega_friction = 2/
|
28
|
+
fly.base_friction = 5.0/1000
|
29
|
+
fly.omega_friction = 2.0/10_000
|
30
30
|
}
|
31
31
|
@starter_torque = 500 # Nm
|
32
32
|
@idle_rpm = 1000 # RPM
|
@@ -75,11 +75,8 @@ module DrivingPhysics
|
|
75
75
|
raise(Stall, "RPM #{rpm}") if rpm < @rpms.first
|
76
76
|
raise(OverRev, "RPM #{rpm}") if rpm > @rpms.last
|
77
77
|
|
78
|
-
last_rpm = 99999
|
79
|
-
last_tq = -1
|
80
|
-
torque = nil
|
78
|
+
last_rpm, last_tq, torque = 99999, -1, nil
|
81
79
|
|
82
|
-
# ew; there must be a better way
|
83
80
|
@rpms.each_with_index { |r, i|
|
84
81
|
tq = @torques[i]
|
85
82
|
if last_rpm <= rpm and rpm <= r
|
@@ -87,8 +84,7 @@ module DrivingPhysics
|
|
87
84
|
torque = last_tq + (tq - last_tq) * proportion
|
88
85
|
break
|
89
86
|
end
|
90
|
-
last_rpm = r
|
91
|
-
last_tq = tq
|
87
|
+
last_rpm, last_tq = r, tq
|
92
88
|
}
|
93
89
|
raise(SanityCheck, "RPM #{rpm}") if torque.nil?
|
94
90
|
|
@@ -101,3 +97,54 @@ module DrivingPhysics
|
|
101
97
|
end
|
102
98
|
end
|
103
99
|
end
|
100
|
+
|
101
|
+
|
102
|
+
# TODO: starter motor
|
103
|
+
# Starter motor is power limited, not torque limited
|
104
|
+
# Consider:
|
105
|
+
# * 2.2 kW (3.75:1 gear reduction)
|
106
|
+
# * 1.8 kW (4.4:1 gear reduction)
|
107
|
+
# On a workbench, a starter will draw 80 to 90 amps. However, during actual
|
108
|
+
# start-up of an engine, a starter will draw 250 to 350 amps.
|
109
|
+
# from https://www.motortrend.com/how-to/because-theres-more-to-a-starter-than-you-realize/
|
110
|
+
|
111
|
+
# V - Potential, voltage
|
112
|
+
# I - Current, amperage
|
113
|
+
# R - Resistance, ohms
|
114
|
+
# P - Power, wattage
|
115
|
+
|
116
|
+
# Ohm's law: I = V / R (where R is held constant)
|
117
|
+
# P = I * V
|
118
|
+
# For resistors (where R is helod constant)
|
119
|
+
# = I^2 * R
|
120
|
+
# = V^2 / R
|
121
|
+
|
122
|
+
|
123
|
+
# torque proportional to A
|
124
|
+
# speed proportional to V
|
125
|
+
|
126
|
+
|
127
|
+
# batteries are rated in terms of CCA - cold cranking amps
|
128
|
+
|
129
|
+
# P = I * V
|
130
|
+
# V = 12 (up to 14ish)
|
131
|
+
# I = 300
|
132
|
+
# P = 3600 or 3.6 kW
|
133
|
+
|
134
|
+
|
135
|
+
# A starter rated at e.g. 2.2 kW will use more power on initial cranking
|
136
|
+
# Sometimes up to 500 amperes are required, and some batteries will provide
|
137
|
+
# over 600 cold cranking amps
|
138
|
+
|
139
|
+
|
140
|
+
# Consider:
|
141
|
+
|
142
|
+
# V = 12
|
143
|
+
# R = resistance of battery, wiring, starter motor
|
144
|
+
# L = inductance (approx 0)
|
145
|
+
# I = current through motor
|
146
|
+
# Vc = proportional to omega
|
147
|
+
|
148
|
+
# rated power - Vc * I
|
149
|
+
# input power - V * I
|
150
|
+
# input power that is unable to be converted to output power is wasted as heat
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module DrivingPhysics
|
2
|
+
module CLI
|
3
|
+
# returns user input as a string
|
4
|
+
def self.prompt(msg = '')
|
5
|
+
print msg + ' ' unless msg.empty?
|
6
|
+
print '> '
|
7
|
+
$stdin.gets.chomp
|
8
|
+
end
|
9
|
+
|
10
|
+
# press Enter to continue, ignore input, return elapsed time
|
11
|
+
def self.pause(msg = '')
|
12
|
+
t = Timer.now
|
13
|
+
puts msg unless msg.empty?
|
14
|
+
puts ' [ Press Enter ]'
|
15
|
+
$stdin.gets
|
16
|
+
Timer.since(t)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Timer
|
21
|
+
def self.now
|
22
|
+
Time.now
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.since(t)
|
26
|
+
self.now - t
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.elapsed(&work)
|
30
|
+
t = self.now
|
31
|
+
return yield, self.since(t)
|
32
|
+
end
|
33
|
+
|
34
|
+
# HH:MM:SS.mmm
|
35
|
+
def self.display(seconds: 0, ms: 0)
|
36
|
+
ms += (seconds * 1000).round if seconds > 0
|
37
|
+
DrivingPhysics.elapsed_display(ms)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.summary(start, num_ticks, paused = 0)
|
41
|
+
elapsed = self.since(start) - paused
|
42
|
+
format("%.3f s (%d ticks/s)", elapsed, num_ticks.to_f / elapsed)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/driving_physics/tire.rb
CHANGED
@@ -26,14 +26,14 @@ module DrivingPhysics
|
|
26
26
|
|
27
27
|
def initialize(env)
|
28
28
|
@env = env
|
29
|
-
@radius =
|
30
|
-
@width =
|
29
|
+
@radius = 0.35
|
30
|
+
@width = 0.2
|
31
31
|
@density = DENSITY
|
32
32
|
@temp = @env.air_temp
|
33
|
-
@mu_s =
|
34
|
-
@mu_k =
|
35
|
-
@base_friction = 5/
|
36
|
-
@omega_friction = 5/
|
33
|
+
@mu_s = 1.1 # static friction
|
34
|
+
@mu_k = 0.7 # kinetic friction
|
35
|
+
@base_friction = 5.0/10_000
|
36
|
+
@omega_friction = 5.0/100_000
|
37
37
|
@roll_cof = DrivingPhysics::ROLL_COF
|
38
38
|
|
39
39
|
yield self if block_given?
|
data/test/disk.rb
CHANGED
@@ -50,6 +50,7 @@ describe D do
|
|
50
50
|
inertia = D.rotational_inertia(0.35, 25.0)
|
51
51
|
expect(D.alpha scalar_torque, inertia).must_be_within_epsilon 653.061
|
52
52
|
|
53
|
+
skip # Vector
|
53
54
|
vector_torque = Vector[0, 0, 1000]
|
54
55
|
vector_alpha = D.alpha vector_torque, inertia
|
55
56
|
expect(vector_alpha).must_be_instance_of Vector
|
@@ -60,6 +61,7 @@ describe D do
|
|
60
61
|
|
61
62
|
describe "Disk.torque_vector" do
|
62
63
|
it "calculates a torque in the 3rd dimension given 2D force and radius" do
|
64
|
+
skip # Vector
|
63
65
|
force = Vector[1000, 0]
|
64
66
|
radius = Vector[0, 5]
|
65
67
|
torque = D.torque_vector(force, radius)
|
@@ -71,6 +73,7 @@ describe D do
|
|
71
73
|
|
72
74
|
describe "Disk.force_vector" do
|
73
75
|
it "calculates a (3D) force given 3D torque and 2D radius" do
|
76
|
+
skip # Vector
|
74
77
|
# let's invert the Disk.torque_vector case from above:
|
75
78
|
torque = Vector[0, 0, 5000]
|
76
79
|
radius = Vector[0, 5]
|
data/test/tire.rb
CHANGED
@@ -11,6 +11,7 @@ describe T do
|
|
11
11
|
scalar_t = T.traction(scalar_nf, cof)
|
12
12
|
expect(scalar_t).must_equal 10780.0
|
13
13
|
|
14
|
+
skip # Vector
|
14
15
|
vector_nf = Vector[9800, 0]
|
15
16
|
vector_t = T.traction(vector_nf, cof)
|
16
17
|
expect(vector_t).must_equal Vector[10780.0, 0.0]
|
@@ -62,6 +63,7 @@ describe T do
|
|
62
63
|
inertia = T.rotational_inertia(0.35, 25.0)
|
63
64
|
expect(T.alpha scalar_torque, inertia).must_be_within_epsilon 653.061
|
64
65
|
|
66
|
+
skip # Vector
|
65
67
|
vector_torque = Vector[0, 0, 1000]
|
66
68
|
vector_alpha = T.alpha vector_torque, inertia
|
67
69
|
expect(vector_alpha).must_be_instance_of Vector
|
@@ -72,6 +74,7 @@ describe T do
|
|
72
74
|
|
73
75
|
describe "Tire.torque_vector" do
|
74
76
|
it "calculates a torque in the 3rd dimension given 2D force and radius" do
|
77
|
+
skip # Vector
|
75
78
|
force = Vector[1000, 0]
|
76
79
|
radius = Vector[0, 5]
|
77
80
|
torque = T.torque_vector(force, radius)
|
@@ -84,6 +87,7 @@ describe T do
|
|
84
87
|
describe "Tire.force_vector" do
|
85
88
|
it "calculates a (3D) force given 3D torque and 2D radius" do
|
86
89
|
# let's invert the Tire.torque_vector case from above:
|
90
|
+
skip # Vector
|
87
91
|
torque = Vector[0, 0, 5000]
|
88
92
|
radius = Vector[0, 5]
|
89
93
|
force = T.force_vector(torque, radius)
|
@@ -154,6 +158,7 @@ describe T do
|
|
154
158
|
expect(@tire.traction scalar_nf).must_equal 10780.0
|
155
159
|
expect(@tire.traction scalar_nf, static: false).must_equal 6860.0
|
156
160
|
|
161
|
+
skip # Vector
|
157
162
|
vector_nf = Vector[9800, 0]
|
158
163
|
expect(@tire.traction vector_nf).must_equal Vector[10780.0, 0.0]
|
159
164
|
expect(@tire.traction vector_nf, static: false).
|
@@ -173,6 +178,7 @@ describe T do
|
|
173
178
|
expect(kin_tq).must_be_within_epsilon 2401.0
|
174
179
|
|
175
180
|
# not sure about how torque vectors work, but the "math" "works":
|
181
|
+
skip # Vector
|
176
182
|
vector_nf = Vector[9800, 0]
|
177
183
|
expect(@tire.tractable_torque(vector_nf)[0]).
|
178
184
|
must_be_within_epsilon 3773.0
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: driving_physics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1
|
4
|
+
version: 0.0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rick Hull
|
@@ -23,6 +23,8 @@ files:
|
|
23
23
|
- demo/disk.rb
|
24
24
|
- demo/gearbox.rb
|
25
25
|
- demo/motor.rb
|
26
|
+
- demo/mruby/disk.rb
|
27
|
+
- demo/mruby/motor.rb
|
26
28
|
- demo/powertrain.rb
|
27
29
|
- demo/scalar_force.rb
|
28
30
|
- demo/tire.rb
|
@@ -36,6 +38,7 @@ files:
|
|
36
38
|
- lib/driving_physics/gearbox.rb
|
37
39
|
- lib/driving_physics/imperial.rb
|
38
40
|
- lib/driving_physics/motor.rb
|
41
|
+
- lib/driving_physics/mruby.rb
|
39
42
|
- lib/driving_physics/power.rb
|
40
43
|
- lib/driving_physics/powertrain.rb
|
41
44
|
- lib/driving_physics/scalar_force.rb
|