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