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