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
data/demo/wheel.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
require 'driving_physics/wheel'
|
2
|
-
|
3
|
-
include DrivingPhysics
|
4
|
-
|
5
|
-
env = Environment.new
|
6
|
-
wheel = Wheel.new(env, mass: 25.0)
|
7
|
-
|
8
|
-
puts env
|
9
|
-
puts wheel
|
10
|
-
|
11
|
-
# 1000 kg car
|
12
|
-
# 4 tires
|
13
|
-
# 250 kg per tire plus tire mass
|
14
|
-
|
15
|
-
supported_mass = 1000 # kg
|
16
|
-
total_mass = supported_mass + 4 * wheel.mass
|
17
|
-
corner_mass = Rational(total_mass) / 4
|
18
|
-
normal_force = corner_mass * env.g
|
19
|
-
axle_torque = 1000 # N*m
|
20
|
-
friction_loss = 0.05 # 5% friction / hysteresis loss
|
21
|
-
|
22
|
-
puts [format("Corner mass: %d kg", corner_mass),
|
23
|
-
format("Normal force: %.1f N", normal_force),
|
24
|
-
format("Axle torque: %d Nm", axle_torque),
|
25
|
-
].join("\n")
|
26
|
-
puts
|
27
|
-
|
28
|
-
traction = wheel.traction(normal_force)
|
29
|
-
drive_force = wheel.force(axle_torque)
|
30
|
-
inertial_loss = wheel.inertial_loss(axle_torque, supported_mass)
|
31
|
-
friction_loss *= axle_torque # 5% of the axle torque
|
32
|
-
|
33
|
-
# drive force = (axle torque - inertia - friction) limited by traction
|
34
|
-
|
35
|
-
net_axle_torque = axle_torque - inertial_loss - friction_loss
|
36
|
-
net_drive_force = wheel.force(net_axle_torque)
|
37
|
-
net_drive_force = traction if net_drive_force > traction # traction limited
|
38
|
-
|
39
|
-
acc = DrivingPhysics.acc(net_drive_force, supported_mass) # translational
|
40
|
-
|
41
|
-
puts [format("Traction: %.1f N", traction),
|
42
|
-
format("Drive force: %.1f N", drive_force),
|
43
|
-
format("Inertial loss: %.1f Nm", inertial_loss),
|
44
|
-
format("Friction loss: %.1f Nm", friction_loss),
|
45
|
-
format("Net Axle Torque: %.1f Nm", net_axle_torque),
|
46
|
-
format("Net Drive Force: %.1f N", net_drive_force),
|
47
|
-
format("Acceleration: %.1f m/s/s", acc),
|
48
|
-
format("Alpha: %.2f r/s/s", acc / wheel.radius_m),
|
49
|
-
].join("\n")
|
50
|
-
puts
|
51
|
-
|
52
|
-
duration = 100 # sec
|
53
|
-
|
54
|
-
dist = 0.0 # meters
|
55
|
-
speed = 0.0 # meters/s
|
56
|
-
|
57
|
-
theta = 0.0 # radians
|
58
|
-
omega = 0.0 # radians/s
|
59
|
-
|
60
|
-
(duration * env.hz).times { |i|
|
61
|
-
# accumulate frictional losses with speed (omega)
|
62
|
-
omega_loss_cof = [wheel.omega_friction * omega, 1.0].min
|
63
|
-
slowed_acc = acc - acc * omega_loss_cof
|
64
|
-
|
65
|
-
# translational kinematics
|
66
|
-
speed += slowed_acc * env.tick
|
67
|
-
dist += speed * env.tick
|
68
|
-
|
69
|
-
# rotational kinematics
|
70
|
-
alpha = slowed_acc / wheel.radius_m
|
71
|
-
omega += alpha * env.tick
|
72
|
-
theta += omega * env.tick
|
73
|
-
|
74
|
-
if i < 10 or
|
75
|
-
(i < 10_000 and i%1000 == 0) or
|
76
|
-
(i % 10_000 == 0)
|
77
|
-
puts DrivingPhysics.elapsed_display(i)
|
78
|
-
puts format("Wheel: %.1f r %.2f r/s %.3f r/s^2", theta, omega, alpha)
|
79
|
-
puts format(" Car: %.1f m %.2f m/s %.3f m/s^2", dist, speed, slowed_acc)
|
80
|
-
puts format("Omega Frictional Loss: %.1f%%", omega_loss_cof * 100)
|
81
|
-
puts "Press [enter]"
|
82
|
-
gets
|
83
|
-
end
|
84
|
-
}
|
@@ -1,191 +0,0 @@
|
|
1
|
-
require 'driving_physics/environment'
|
2
|
-
require 'driving_physics/vector_force'
|
3
|
-
|
4
|
-
module DrivingPhysics
|
5
|
-
# Rotational complements to acc/vel/pos
|
6
|
-
# alpha - angular acceleration
|
7
|
-
# omega - angular velocity (radians / s)
|
8
|
-
# theta - radians
|
9
|
-
|
10
|
-
class Wheel
|
11
|
-
# Note, this is not the density of solid rubber. This density
|
12
|
-
# yields a sensible mass for a wheel / tire combo at common radius
|
13
|
-
# and width, assuming a uniform density
|
14
|
-
# e.g. 25kg at 350mm R x 200mm W
|
15
|
-
#
|
16
|
-
DENSITY = 0.325 # kg / L
|
17
|
-
|
18
|
-
# * the traction force opposes the axle torque / drive force
|
19
|
-
# thus, driving the car forward
|
20
|
-
# * if the drive force exceeds the traction force, slippage occurs
|
21
|
-
# * slippage reduces the available traction force further
|
22
|
-
# * if the drive force is not reduced, the slippage increases
|
23
|
-
# until resistance forces equal the drive force
|
24
|
-
def self.traction(normal_force, cof)
|
25
|
-
normal_force * cof
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.force(axle_torque, radius_m)
|
29
|
-
axle_torque / radius_m.to_f
|
30
|
-
end
|
31
|
-
|
32
|
-
# in m^3
|
33
|
-
def self.volume(radius_m, width_m)
|
34
|
-
Math::PI * radius_m ** 2 * width_m.to_f
|
35
|
-
end
|
36
|
-
|
37
|
-
# in L
|
38
|
-
def self.volume_l(radius_m, width_m)
|
39
|
-
volume(radius_m, width_m) * 1000
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.density(mass, volume_l)
|
43
|
-
mass.to_f / volume_l
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.mass(radius_m, width_m, density)
|
47
|
-
density * volume_l(radius_m, width_m)
|
48
|
-
end
|
49
|
-
|
50
|
-
# I = 1/2 (m)(r^2) for a disk
|
51
|
-
def self.rotational_inertia(radius_m, mass)
|
52
|
-
mass * radius_m**2 / 2.0
|
53
|
-
end
|
54
|
-
class << self
|
55
|
-
alias_method(:moment_of_inertia, :rotational_inertia)
|
56
|
-
end
|
57
|
-
|
58
|
-
# angular acceleration
|
59
|
-
def self.alpha(torque, inertia)
|
60
|
-
torque / inertia
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.tangential(rotational, radius_m)
|
64
|
-
rotational * radius_m
|
65
|
-
end
|
66
|
-
class << self
|
67
|
-
alias_method(:tangential_a, :tangential)
|
68
|
-
alias_method(:tangential_v, :tangential)
|
69
|
-
alias_method(:tangential_p, :tangential)
|
70
|
-
end
|
71
|
-
|
72
|
-
# vectors only
|
73
|
-
def self.torque_vector(force, radius)
|
74
|
-
if !force.is_a?(Vector) or force.size != 2
|
75
|
-
raise(ArgumentError, "force must be a 2D vector")
|
76
|
-
end
|
77
|
-
if !radius.is_a?(Vector) or radius.size != 2
|
78
|
-
raise(ArgumentError, "radius must be a 2D vector")
|
79
|
-
end
|
80
|
-
force = Vector[force[0], force[1], 0]
|
81
|
-
radius = Vector[radius[0], radius[1], 0]
|
82
|
-
force.cross(radius)
|
83
|
-
end
|
84
|
-
|
85
|
-
# vectors only
|
86
|
-
def self.force_vector(torque, radius)
|
87
|
-
if !torque.is_a?(Vector) or torque.size != 3
|
88
|
-
raise(ArgumentError, "torque must be a 3D vector")
|
89
|
-
end
|
90
|
-
if !radius.is_a?(Vector) or radius.size != 2
|
91
|
-
raise(ArgumentError, "radius must be a 2D vector")
|
92
|
-
end
|
93
|
-
radius = Vector[radius[0], radius[1], 0]
|
94
|
-
radius.cross(torque) / radius.dot(radius)
|
95
|
-
end
|
96
|
-
|
97
|
-
attr_reader :env, :radius, :radius_m, :width, :width_m, :density, :temp,
|
98
|
-
:mu_s, :mu_k, :omega_friction
|
99
|
-
|
100
|
-
def initialize(env,
|
101
|
-
radius: 350, width: 200, density: DENSITY,
|
102
|
-
temp: nil, mass: nil,
|
103
|
-
mu_s: 1.1, mu_k: 0.7,
|
104
|
-
omega_friction: 0.002)
|
105
|
-
@env = env
|
106
|
-
@radius = radius.to_f # mm
|
107
|
-
@radius_m = @radius / 1000
|
108
|
-
@width = width.to_f # mm
|
109
|
-
@width_m = @width / 1000
|
110
|
-
@mu_s = mu_s.to_f # static friction
|
111
|
-
@mu_k = mu_k.to_f # kinetic friction
|
112
|
-
@omega_friction = omega_friction # scales with speed
|
113
|
-
@density = mass.nil? ? density : self.class.density(mass, volume_l)
|
114
|
-
@temp = temp.to_f || @env.air_temp
|
115
|
-
end
|
116
|
-
|
117
|
-
def to_s
|
118
|
-
[[format("%d mm (R) x %d mm (W)", @radius, @width),
|
119
|
-
format("Mass: %.1f kg %.3f kg/L", self.mass, @density),
|
120
|
-
format("cF: %.1f / %.1f", @mu_s, @mu_k),
|
121
|
-
].join(" | "),
|
122
|
-
[format("Temp: %.1f C", @temp),
|
123
|
-
].join(" | "),
|
124
|
-
].join("\n")
|
125
|
-
end
|
126
|
-
|
127
|
-
def wear!(amount_mm)
|
128
|
-
@radius -= amount_mm
|
129
|
-
@radius_m = @radius / 1000
|
130
|
-
end
|
131
|
-
|
132
|
-
def heat!(amount_deg_c)
|
133
|
-
@temp += amount_deg_c
|
134
|
-
end
|
135
|
-
|
136
|
-
def mass
|
137
|
-
self.class.mass(@radius_m, @width_m, @density)
|
138
|
-
end
|
139
|
-
|
140
|
-
# in m^3
|
141
|
-
def volume
|
142
|
-
self.class.volume(@radius_m, @width_m)
|
143
|
-
end
|
144
|
-
|
145
|
-
# in L
|
146
|
-
def volume_l
|
147
|
-
self.class.volume_l(@radius_m, @width_m)
|
148
|
-
end
|
149
|
-
|
150
|
-
def rotational_inertia
|
151
|
-
self.class.rotational_inertia(@radius_m, self.mass)
|
152
|
-
end
|
153
|
-
alias_method(:moment_of_inertia, :rotational_inertia)
|
154
|
-
|
155
|
-
def traction(nf, static: true)
|
156
|
-
self.class.traction(nf, static ? @mu_s : @mu_k)
|
157
|
-
end
|
158
|
-
|
159
|
-
def force(axle_torque)
|
160
|
-
self.class.force(axle_torque, @radius_m)
|
161
|
-
end
|
162
|
-
|
163
|
-
# how much torque to accelerate rotational inertia at alpha
|
164
|
-
def inertial_torque(alpha)
|
165
|
-
alpha * self.rotational_inertia
|
166
|
-
end
|
167
|
-
|
168
|
-
# this doesn't take inertial losses or internal frictional losses
|
169
|
-
# into account. input torque required to saturate traction will be
|
170
|
-
# higher than what this method returns
|
171
|
-
def tractable_torque(nf, static: true)
|
172
|
-
traction(nf, static: static) * @radius_m
|
173
|
-
end
|
174
|
-
|
175
|
-
# inertial loss in terms of axle torque when used as a drive wheel
|
176
|
-
def inertial_loss(axle_torque, total_driven_mass)
|
177
|
-
drive_force = self.force(axle_torque)
|
178
|
-
force_loss = 0
|
179
|
-
# The force loss depends on the acceleration, but the acceleration
|
180
|
-
# depends on the force loss. Converge the value via 5 round trips.
|
181
|
-
# This is a rough way to compute an integral and should be accurate
|
182
|
-
# to 8+ digits.
|
183
|
-
5.times {
|
184
|
-
acc = DrivingPhysics.acc(drive_force - force_loss, total_driven_mass)
|
185
|
-
alpha = acc / @radius_m
|
186
|
-
force_loss = self.inertial_torque(alpha) / @radius_m
|
187
|
-
}
|
188
|
-
force_loss * @radius_m
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|