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
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'driving_physics/disk'
|
2
|
+
|
3
|
+
module DrivingPhysics
|
4
|
+
|
5
|
+
# a Tire is a Disk with lighter density and meaningful surface friction
|
6
|
+
|
7
|
+
class Tire < Disk
|
8
|
+
# Note, this is not the density of solid rubber. This density
|
9
|
+
# yields a sensible mass for a wheel / tire combo at common radius
|
10
|
+
# and width, assuming a uniform density
|
11
|
+
# e.g. 25kg at 350mm R x 200mm W
|
12
|
+
#
|
13
|
+
DENSITY = 0.325 # kg / L
|
14
|
+
|
15
|
+
# * the traction force opposes the axle torque / drive force
|
16
|
+
# thus, driving the car forward
|
17
|
+
# * if the drive force exceeds the traction force, slippage occurs
|
18
|
+
# * slippage reduces the available traction force further
|
19
|
+
# * if the drive force is not reduced, the slippage increases
|
20
|
+
# until resistance forces equal the drive force
|
21
|
+
def self.traction(normal_force, cof)
|
22
|
+
normal_force * cof
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :mu_s, :mu_k, :omega_friction, :base_friction, :roll_cof
|
26
|
+
|
27
|
+
def initialize(env)
|
28
|
+
@env = env
|
29
|
+
@radius = 0.35
|
30
|
+
@width = 0.2
|
31
|
+
@density = DENSITY
|
32
|
+
@temp = @env.air_temp
|
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
|
+
@roll_cof = DrivingPhysics::ROLL_COF
|
38
|
+
|
39
|
+
yield self if block_given?
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
[[format("%d mm x %d mm (RxW)", @radius * 1000, @width * 1000),
|
44
|
+
format("%.1f kg %.1f C", self.mass, @temp),
|
45
|
+
format("cF: %.1f / %.1f", @mu_s, @mu_k),
|
46
|
+
].join(" | "),
|
47
|
+
].join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def wear!(amount)
|
51
|
+
@radius -= amount
|
52
|
+
end
|
53
|
+
|
54
|
+
def heat!(amount_deg_c)
|
55
|
+
@temp += amount_deg_c
|
56
|
+
end
|
57
|
+
|
58
|
+
def traction(nf, static: true)
|
59
|
+
self.class.traction(nf, static ? @mu_s : @mu_k)
|
60
|
+
end
|
61
|
+
|
62
|
+
# require a normal_force to be be passed in
|
63
|
+
def rotating_friction(omega, normal_force:)
|
64
|
+
super(omega, normal_force: normal_force)
|
65
|
+
end
|
66
|
+
|
67
|
+
# rolling loss in terms of axle torque
|
68
|
+
def rolling_friction(omega, normal_force:)
|
69
|
+
return omega if omega.zero?
|
70
|
+
mag = omega.abs
|
71
|
+
sign = omega / mag
|
72
|
+
-1 * sign * (normal_force * @roll_cof) * @radius
|
73
|
+
end
|
74
|
+
|
75
|
+
# inertial loss in terms of axle torque when used as a drive wheel
|
76
|
+
def inertial_loss(axle_torque, driven_mass:)
|
77
|
+
drive_force = self.force(axle_torque)
|
78
|
+
force_loss = 0
|
79
|
+
# The force loss depends on the acceleration, but the acceleration
|
80
|
+
# depends on the force loss. Converge the value via 5 round trips.
|
81
|
+
# This is a rough way to compute an integral and should be accurate
|
82
|
+
# to 8+ digits.
|
83
|
+
5.times {
|
84
|
+
acc = DrivingPhysics.acc(drive_force - force_loss, driven_mass)
|
85
|
+
alpha = acc / @radius
|
86
|
+
force_loss = self.implied_torque(alpha) / @radius
|
87
|
+
}
|
88
|
+
force_loss * @radius
|
89
|
+
end
|
90
|
+
|
91
|
+
def net_torque(axle_torque, mass:, omega:, normal_force:)
|
92
|
+
# friction forces oppose omega
|
93
|
+
net = axle_torque +
|
94
|
+
self.rolling_friction(omega, normal_force: normal_force) +
|
95
|
+
self.rotating_friction(omega, normal_force: normal_force)
|
96
|
+
|
97
|
+
# inertial loss has interdependencies; calculate last
|
98
|
+
# it opposes net torque, not omega
|
99
|
+
sign = net / net.abs
|
100
|
+
net - sign * self.inertial_loss(net, driven_mass: mass)
|
101
|
+
end
|
102
|
+
|
103
|
+
def net_tractable_torque(axle_torque,
|
104
|
+
mass:, omega:, normal_force:, static: true)
|
105
|
+
net = self.net_torque(axle_torque,
|
106
|
+
mass: mass,
|
107
|
+
omega: omega,
|
108
|
+
normal_force: normal_force)
|
109
|
+
tt = self.tractable_torque(normal_force, static: static)
|
110
|
+
net > tt ? tt : net
|
111
|
+
end
|
112
|
+
|
113
|
+
# this doesn't take inertial losses or internal frictional losses
|
114
|
+
# into account. input torque required to saturate traction will be
|
115
|
+
# higher than what this method returns
|
116
|
+
def tractable_torque(nf, static: true)
|
117
|
+
traction(nf, static: static) * @radius
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -84,12 +84,25 @@ module DrivingPhysics
|
|
84
84
|
frontal_area: FRONTAL_AREA,
|
85
85
|
drag_cof: DRAG_COF,
|
86
86
|
air_density: AIR_DENSITY)
|
87
|
+
return velocity if velocity.zero?
|
87
88
|
-1 * 0.5 * frontal_area * drag_cof * air_density *
|
88
89
|
velocity * velocity.magnitude
|
89
90
|
end
|
90
91
|
|
91
|
-
|
92
|
-
|
92
|
+
# return a force opposing velocity, representing friction / hysteresis
|
93
|
+
def self.rotational_resistance(velocity,
|
94
|
+
rot_const: ROT_CONST,
|
95
|
+
rot_cof: ROT_COF)
|
96
|
+
return velocity if velocity.zero?
|
97
|
+
-1 * velocity * rot_cof + -1 * velocity.normalize * rot_const
|
98
|
+
end
|
99
|
+
|
100
|
+
# return a torque opposing omega, representing friction / hysteresis
|
101
|
+
def self.omega_resistance(omega,
|
102
|
+
rot_const: ROT_TQ_CONST,
|
103
|
+
rot_cof: ROT_TQ_COF)
|
104
|
+
return 0 if omega == 0.0
|
105
|
+
omega * ROT_TQ_COF + ROT_TQ_CONST
|
93
106
|
end
|
94
107
|
|
95
108
|
# dir is drive_force vector or velocity vector; will be normalized
|
data/lib/driving_physics.rb
CHANGED
@@ -26,6 +26,7 @@ module DrivingPhysics
|
|
26
26
|
DRAG_COF = 0.3 # based roughly on 2000s-era Chevrolet Corvette
|
27
27
|
DRAG = 0.4257 # air_resistance at 1 m/s given above numbers
|
28
28
|
ROT_COF = 12.771 # if rotating resistance matches air resistance at 30 m/s
|
29
|
+
ROT_CONST = 0.05 # N opposing drive force / torque
|
29
30
|
ROLL_COF = 0.01 # roughly: street tires on concrete
|
30
31
|
|
31
32
|
#
|
@@ -35,6 +36,7 @@ module DrivingPhysics
|
|
35
36
|
MINS_PER_HOUR = 60
|
36
37
|
SECS_PER_HOUR = SECS_PER_MIN * MINS_PER_HOUR
|
37
38
|
|
39
|
+
# HH::MM::SS.mmm
|
38
40
|
def self.elapsed_display(elapsed_ms)
|
39
41
|
elapsed_s, ms = elapsed_ms.divmod 1000
|
40
42
|
|
data/test/disk.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/disk'
|
3
|
+
|
4
|
+
D = DrivingPhysics::Disk
|
5
|
+
|
6
|
+
describe D do
|
7
|
+
describe "Disk.volume" do
|
8
|
+
it "calculates the volume (m^3) of disk given radius and width" do
|
9
|
+
cubic_m = D.volume(1.0, 1.0)
|
10
|
+
expect(cubic_m).must_equal Math::PI
|
11
|
+
|
12
|
+
cubic_m = D.volume(0.35, 0.2)
|
13
|
+
expect(cubic_m).must_be_within_epsilon 0.076969
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "Disk.volume_l" do
|
18
|
+
it "calculates the volume (L) of a disk given radius and width" do
|
19
|
+
liters = D.volume_l(1.0, 1.0)
|
20
|
+
expect(liters).must_equal Math::PI * 1000
|
21
|
+
|
22
|
+
liters = D.volume_l(0.35, 0.2)
|
23
|
+
expect(liters).must_be_within_epsilon 76.96902
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "Disk.density" do
|
28
|
+
it "calculates the density (kg/L) given mass and volume" do
|
29
|
+
expect(D.density(25.0, 25.0)).must_equal 1.0
|
30
|
+
expect(D.density(50.0, 25.0)).must_equal 2.0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "Disk.mass" do
|
35
|
+
it "calculates the mass (kg) of a disk given radius, width, and density" do
|
36
|
+
skip
|
37
|
+
expect(D.mass(0.35, 0.2, D::DENSITY)).must_be_within_epsilon 25.015
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "Disk.rotational_inertia" do
|
42
|
+
it "calculates rotational inertia for a disk given radius and mass" do
|
43
|
+
expect(D.rotational_inertia(0.35, 25.0)).must_be_within_epsilon 1.53125
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "Disk.alpha" do
|
48
|
+
it "calculates angular acceleration from torque and inertia" do
|
49
|
+
scalar_torque = 1000
|
50
|
+
inertia = D.rotational_inertia(0.35, 25.0)
|
51
|
+
expect(D.alpha scalar_torque, inertia).must_be_within_epsilon 653.061
|
52
|
+
|
53
|
+
skip # Vector
|
54
|
+
vector_torque = Vector[0, 0, 1000]
|
55
|
+
vector_alpha = D.alpha vector_torque, inertia
|
56
|
+
expect(vector_alpha).must_be_instance_of Vector
|
57
|
+
expect(vector_alpha.size).must_equal 3
|
58
|
+
expect(vector_alpha[2]).must_be_within_epsilon 653.06
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "Disk.torque_vector" do
|
63
|
+
it "calculates a torque in the 3rd dimension given 2D force and radius" do
|
64
|
+
skip # Vector
|
65
|
+
force = Vector[1000, 0]
|
66
|
+
radius = Vector[0, 5]
|
67
|
+
torque = D.torque_vector(force, radius)
|
68
|
+
expect(torque).must_be_instance_of Vector
|
69
|
+
expect(torque.size).must_equal 3
|
70
|
+
expect(torque[2]).must_be_within_epsilon 5000.0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "Disk.force_vector" do
|
75
|
+
it "calculates a (3D) force given 3D torque and 2D radius" do
|
76
|
+
skip # Vector
|
77
|
+
# let's invert the Disk.torque_vector case from above:
|
78
|
+
torque = Vector[0, 0, 5000]
|
79
|
+
radius = Vector[0, 5]
|
80
|
+
force = D.force_vector(torque, radius)
|
81
|
+
expect(force).must_be_instance_of Vector
|
82
|
+
expect(force.size).must_equal 3
|
83
|
+
expect(force[0]).must_be_within_epsilon 1000.0
|
84
|
+
|
85
|
+
# now let's rotate the radius into the x-dimension
|
86
|
+
# right hand rule, positive torque means thumb into screen, clockwise
|
87
|
+
# negative-x radius means positive-y force
|
88
|
+
torque = Vector[0, 0, 500]
|
89
|
+
radius = Vector[-5, 0]
|
90
|
+
force = D.force_vector(torque, radius)
|
91
|
+
expect(force).must_be_instance_of Vector
|
92
|
+
expect(force.size).must_equal 3
|
93
|
+
expect(force[1]).must_be_within_epsilon 100.0
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "instance methods" do
|
98
|
+
before do
|
99
|
+
@env = DrivingPhysics::Environment.new
|
100
|
+
@disk = D.new(@env)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "initializes" do
|
104
|
+
skip
|
105
|
+
expect(@disk).must_be_instance_of D
|
106
|
+
expect(@disk.density).must_equal D::DENSITY # sanity check
|
107
|
+
expect(@disk.mass).must_be_within_epsilon 25.01
|
108
|
+
|
109
|
+
with_mass = D.new(@env) { |w|
|
110
|
+
w.mass = 99.01
|
111
|
+
}
|
112
|
+
expect(with_mass.mass).must_equal 99.01
|
113
|
+
expect(with_mass.density).wont_equal D::DENSITY
|
114
|
+
end
|
115
|
+
|
116
|
+
it "has a string representation" do
|
117
|
+
str = @disk.to_s
|
118
|
+
expect(str).must_be_instance_of String
|
119
|
+
expect(str.length).must_be(:>, 5)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "has volume" do
|
123
|
+
expect(@disk.volume).must_be_within_epsilon 0.07697
|
124
|
+
expect(@disk.volume_l).must_be_within_epsilon 76.96902
|
125
|
+
end
|
126
|
+
|
127
|
+
it "has inertia" do
|
128
|
+
skip
|
129
|
+
expect(@disk.rotational_inertia).must_be_within_epsilon 1.5321
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/test/scalar_force.rb
CHANGED
@@ -6,25 +6,27 @@ include DrivingPhysics
|
|
6
6
|
describe ScalarForce do
|
7
7
|
# i.e. multiply this number times speed^2 to approximate drag force
|
8
8
|
it "calculates a reasonable drag constant" do
|
9
|
-
expect(ScalarForce.air_resistance 1).must_be_within_epsilon DRAG
|
9
|
+
expect(ScalarForce.air_resistance 1).must_be_within_epsilon(-1 * DRAG)
|
10
10
|
end
|
11
11
|
|
12
12
|
# ROT_COF's value is from observing that rotational resistance
|
13
13
|
# matches air resistance at roughly 30 m/s in street cars
|
14
14
|
it "approximates a reasonable rotational resistance constant" do
|
15
|
-
expect(30 * ScalarForce.air_resistance(1)).
|
15
|
+
expect(30 * ScalarForce.air_resistance(1)).
|
16
|
+
must_be_within_epsilon(-1 * ROT_COF)
|
16
17
|
end
|
17
18
|
|
18
19
|
it "approximates a positive drag force" do
|
19
|
-
expect(ScalarForce.air_resistance 30).must_be_within_epsilon
|
20
|
+
expect(ScalarForce.air_resistance 30).must_be_within_epsilon(-383.13)
|
20
21
|
end
|
21
22
|
|
22
23
|
it "approximates a positive rotational resistance force" do
|
23
|
-
expect(ScalarForce.rotational_resistance 30).
|
24
|
+
expect(ScalarForce.rotational_resistance 30).
|
25
|
+
must_be_within_epsilon(-383.13)
|
24
26
|
end
|
25
27
|
|
26
28
|
it "approximates a positive rolling resistance force" do
|
27
29
|
nf = 1000 * G
|
28
|
-
expect(ScalarForce.rolling_resistance nf).must_be_within_epsilon
|
30
|
+
expect(ScalarForce.rolling_resistance nf).must_be_within_epsilon(-98.0)
|
29
31
|
end
|
30
32
|
end
|
data/test/{wheel.rb → tire.rb}
RENAMED
@@ -1,92 +1,96 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
|
-
require 'driving_physics/
|
2
|
+
require 'driving_physics/tire'
|
3
3
|
|
4
|
-
|
4
|
+
T = DrivingPhysics::Tire
|
5
5
|
|
6
|
-
describe
|
7
|
-
describe "
|
6
|
+
describe T do
|
7
|
+
describe "Tire.traction" do
|
8
8
|
it "calculates traction force from normal force and coeff of friction" do
|
9
9
|
scalar_nf = 9800 # N
|
10
10
|
cof = 1.1
|
11
|
-
scalar_t =
|
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
|
-
vector_t =
|
16
|
+
vector_t = T.traction(vector_nf, cof)
|
16
17
|
expect(vector_t).must_equal Vector[10780.0, 0.0]
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
|
-
describe "
|
21
|
+
describe "Tire.volume" do
|
21
22
|
it "calculates the volume (m^3) of disk given radius and width" do
|
22
|
-
cubic_m =
|
23
|
+
cubic_m = T.volume(1.0, 1.0)
|
23
24
|
expect(cubic_m).must_equal Math::PI
|
24
25
|
|
25
|
-
cubic_m =
|
26
|
+
cubic_m = T.volume(0.35, 0.2)
|
26
27
|
expect(cubic_m).must_be_within_epsilon 0.076969
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
describe "
|
31
|
+
describe "Tire.volume_l" do
|
31
32
|
it "calculates the volume (L) of a disk given radius and width" do
|
32
|
-
liters =
|
33
|
+
liters = T.volume_l(1.0, 1.0)
|
33
34
|
expect(liters).must_equal Math::PI * 1000
|
34
35
|
|
35
|
-
liters =
|
36
|
+
liters = T.volume_l(0.35, 0.2)
|
36
37
|
expect(liters).must_be_within_epsilon 76.96902
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
|
-
describe "
|
41
|
+
describe "Tire.density" do
|
41
42
|
it "calculates the density (kg/L) given mass and volume" do
|
42
|
-
expect(
|
43
|
-
expect(
|
43
|
+
expect(T.density(25.0, 25.0)).must_equal 1.0
|
44
|
+
expect(T.density(50.0, 25.0)).must_equal 2.0
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
|
-
describe "
|
48
|
+
describe "Tire.mass" do
|
48
49
|
it "calculates the mass (kg) of a disk given radius, width, and density" do
|
49
|
-
expect(
|
50
|
+
expect(T.mass(0.35, 0.2, T::DENSITY)).must_be_within_epsilon 25.015
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
describe "
|
54
|
+
describe "Tire.rotational_inertia" do
|
54
55
|
it "calculates rotational inertia for a disk given radius and mass" do
|
55
|
-
expect(
|
56
|
+
expect(T.rotational_inertia(0.35, 25.0)).must_be_within_epsilon 1.53125
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
59
|
-
describe "
|
60
|
+
describe "Tire.alpha" do
|
60
61
|
it "calculates angular acceleration from torque and inertia" do
|
61
62
|
scalar_torque = 1000
|
62
|
-
inertia =
|
63
|
-
expect(
|
63
|
+
inertia = T.rotational_inertia(0.35, 25.0)
|
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
|
-
vector_alpha =
|
68
|
+
vector_alpha = T.alpha vector_torque, inertia
|
67
69
|
expect(vector_alpha).must_be_instance_of Vector
|
68
70
|
expect(vector_alpha.size).must_equal 3
|
69
71
|
expect(vector_alpha[2]).must_be_within_epsilon 653.06
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
73
|
-
describe "
|
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
|
-
torque =
|
80
|
+
torque = T.torque_vector(force, radius)
|
78
81
|
expect(torque).must_be_instance_of Vector
|
79
82
|
expect(torque.size).must_equal 3
|
80
83
|
expect(torque[2]).must_be_within_epsilon 5000.0
|
81
84
|
end
|
82
85
|
end
|
83
86
|
|
84
|
-
describe "
|
87
|
+
describe "Tire.force_vector" do
|
85
88
|
it "calculates a (3D) force given 3D torque and 2D radius" do
|
86
|
-
# let's invert the
|
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
|
-
force =
|
93
|
+
force = T.force_vector(torque, radius)
|
90
94
|
expect(force).must_be_instance_of Vector
|
91
95
|
expect(force.size).must_equal 3
|
92
96
|
expect(force[0]).must_be_within_epsilon 1000.0
|
@@ -96,7 +100,7 @@ describe W do
|
|
96
100
|
# negative-x radius means positive-y force
|
97
101
|
torque = Vector[0, 0, 500]
|
98
102
|
radius = Vector[-5, 0]
|
99
|
-
force =
|
103
|
+
force = T.force_vector(torque, radius)
|
100
104
|
expect(force).must_be_instance_of Vector
|
101
105
|
expect(force.size).must_equal 3
|
102
106
|
expect(force[1]).must_be_within_epsilon 100.0
|
@@ -106,72 +110,78 @@ describe W do
|
|
106
110
|
describe "instance methods" do
|
107
111
|
before do
|
108
112
|
@env = DrivingPhysics::Environment.new
|
109
|
-
@
|
113
|
+
@tire = T.new(@env)
|
110
114
|
end
|
111
115
|
|
112
116
|
it "initializes" do
|
113
|
-
expect(@
|
114
|
-
expect(@
|
115
|
-
expect(@
|
117
|
+
expect(@tire).must_be_instance_of T
|
118
|
+
expect(@tire.density).must_equal T::DENSITY # sanity check
|
119
|
+
expect(@tire.mass).must_be_within_epsilon 25.01
|
116
120
|
|
117
|
-
with_mass =
|
121
|
+
with_mass = T.new(@env) { |w|
|
122
|
+
w.mass = 99.01
|
123
|
+
}
|
118
124
|
expect(with_mass.mass).must_equal 99.01
|
119
|
-
expect(with_mass.density).wont_equal
|
125
|
+
expect(with_mass.density).wont_equal T::DENSITY
|
120
126
|
end
|
121
127
|
|
122
128
|
it "has a string representation" do
|
123
|
-
str = @
|
129
|
+
str = @tire.to_s
|
124
130
|
expect(str).must_be_instance_of String
|
125
131
|
expect(str.length).must_be(:>, 5)
|
126
132
|
end
|
127
133
|
|
128
134
|
it "loses radius as it wears" do
|
129
|
-
|
130
|
-
|
131
|
-
|
135
|
+
old_r = @tire.radius
|
136
|
+
wear_amt = 50/1000r
|
137
|
+
@tire.wear! wear_amt
|
138
|
+
expect(@tire.radius).must_equal old_r - wear_amt
|
132
139
|
end
|
133
140
|
|
134
141
|
it "calculates mass from current radius" do
|
135
|
-
expect(@
|
136
|
-
@
|
137
|
-
expect(@
|
142
|
+
expect(@tire.mass).must_be_within_epsilon 25.01
|
143
|
+
@tire.wear!(50/1000r)
|
144
|
+
expect(@tire.mass).must_be_within_epsilon 18.378
|
138
145
|
end
|
139
146
|
|
140
147
|
it "has volume" do
|
141
|
-
expect(@
|
142
|
-
expect(@
|
148
|
+
expect(@tire.volume).must_be_within_epsilon 0.07697
|
149
|
+
expect(@tire.volume_l).must_be_within_epsilon 76.96902
|
143
150
|
end
|
144
151
|
|
145
152
|
it "has inertia" do
|
146
|
-
expect(@
|
153
|
+
expect(@tire.rotational_inertia).must_be_within_epsilon 1.5321
|
147
154
|
end
|
148
155
|
|
149
156
|
it "has traction force based on normal force" do
|
150
157
|
scalar_nf = 9800
|
151
|
-
expect(@
|
152
|
-
expect(@
|
158
|
+
expect(@tire.traction scalar_nf).must_equal 10780.0
|
159
|
+
expect(@tire.traction scalar_nf, static: false).must_equal 6860.0
|
153
160
|
|
161
|
+
skip # Vector
|
154
162
|
vector_nf = Vector[9800, 0]
|
155
|
-
expect(@
|
156
|
-
expect(@
|
163
|
+
expect(@tire.traction vector_nf).must_equal Vector[10780.0, 0.0]
|
164
|
+
expect(@tire.traction vector_nf, static: false).
|
157
165
|
must_equal Vector[6860.0, 0.0]
|
158
166
|
end
|
159
167
|
|
160
168
|
it "determines (e.g. thrust) force based on axle torque" do
|
161
|
-
expect(@
|
162
|
-
@
|
163
|
-
expect(@
|
169
|
+
expect(@tire.force 1000).must_be_within_epsilon 2857.143
|
170
|
+
@tire.wear! 50/1000r
|
171
|
+
expect(@tire.force 1000).must_be_within_epsilon 3333.333
|
164
172
|
end
|
165
173
|
|
166
174
|
it "determines tractable torque" do
|
167
175
|
scalar_nf = 9800
|
168
|
-
expect(@
|
169
|
-
kin_tq = @
|
176
|
+
expect(@tire.tractable_torque scalar_nf).must_be_within_epsilon 3773.0
|
177
|
+
kin_tq = @tire.tractable_torque scalar_nf, static: false
|
170
178
|
expect(kin_tq).must_be_within_epsilon 2401.0
|
171
179
|
|
172
180
|
# not sure about how torque vectors work, but the "math" "works":
|
181
|
+
skip # Vector
|
173
182
|
vector_nf = Vector[9800, 0]
|
174
|
-
expect(@
|
183
|
+
expect(@tire.tractable_torque(vector_nf)[0]).
|
184
|
+
must_be_within_epsilon 3773.0
|
175
185
|
end
|
176
186
|
end
|
177
187
|
end
|
data/test/vector_force.rb
CHANGED
@@ -74,9 +74,15 @@ describe VectorForce do
|
|
74
74
|
end
|
75
75
|
|
76
76
|
it "calculates the rotational resistance as a function of velocity" do
|
77
|
+
rr = VectorForce.rotational_resistance(@v, rot_const: 0)
|
78
|
+
rr2 = VectorForce.rotational_resistance(@v * 2, rot_const: 0)
|
79
|
+
expect(rr2).must_equal rr * 2
|
80
|
+
|
81
|
+
# now with rot_const != 0, the relationship is skewed
|
77
82
|
rr = VectorForce.rotational_resistance(@v)
|
78
83
|
rr2 = VectorForce.rotational_resistance(@v * 2)
|
79
|
-
expect(rr2).
|
84
|
+
expect(rr2).wont_equal rr * 2 # because of rot_const
|
85
|
+
expect(rr2.magnitude).must_be(:<, (rr * 2).magnitude)
|
80
86
|
end
|
81
87
|
|
82
88
|
it "sums resistance forces" do
|
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.
|
4
|
+
version: 0.0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rick Hull
|
@@ -19,20 +19,36 @@ files:
|
|
19
19
|
- README.md
|
20
20
|
- Rakefile
|
21
21
|
- VERSION
|
22
|
+
- demo/car.rb
|
23
|
+
- demo/disk.rb
|
24
|
+
- demo/gearbox.rb
|
25
|
+
- demo/motor.rb
|
26
|
+
- demo/mruby/disk.rb
|
27
|
+
- demo/mruby/motor.rb
|
28
|
+
- demo/powertrain.rb
|
22
29
|
- demo/scalar_force.rb
|
30
|
+
- demo/tire.rb
|
23
31
|
- demo/vector_force.rb
|
24
|
-
- demo/wheel.rb
|
25
32
|
- driving_physics.gemspec
|
26
33
|
- lib/driving_physics.rb
|
34
|
+
- lib/driving_physics/car.rb
|
35
|
+
- lib/driving_physics/cli.rb
|
36
|
+
- lib/driving_physics/disk.rb
|
27
37
|
- lib/driving_physics/environment.rb
|
38
|
+
- lib/driving_physics/gearbox.rb
|
28
39
|
- lib/driving_physics/imperial.rb
|
40
|
+
- lib/driving_physics/motor.rb
|
41
|
+
- lib/driving_physics/mruby.rb
|
42
|
+
- lib/driving_physics/power.rb
|
43
|
+
- lib/driving_physics/powertrain.rb
|
29
44
|
- lib/driving_physics/scalar_force.rb
|
45
|
+
- lib/driving_physics/tire.rb
|
30
46
|
- lib/driving_physics/vector_force.rb
|
31
|
-
-
|
47
|
+
- test/disk.rb
|
32
48
|
- test/driving_physics.rb
|
33
49
|
- test/scalar_force.rb
|
50
|
+
- test/tire.rb
|
34
51
|
- test/vector_force.rb
|
35
|
-
- test/wheel.rb
|
36
52
|
homepage: https://github.com/rickhull/driving_physics
|
37
53
|
licenses:
|
38
54
|
- LGPL-3.0
|