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