driving_physics 0.0.1.2 → 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 +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
|