driving_physics 0.0.2.1 → 0.0.3.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 +17 -14
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/demo/car.rb +83 -73
- data/demo/motor.rb +7 -4
- data/demo/mruby/car.rb +184 -0
- data/demo/mruby/motor.rb +7 -4
- data/demo/pid_controller.rb +143 -0
- data/demo/powertrain.rb +1 -1
- data/driving_physics.gemspec +2 -1
- data/lib/driving_physics/car.rb +13 -4
- data/lib/driving_physics/cli.rb +7 -35
- data/lib/driving_physics/cockpit.rb +167 -0
- data/lib/driving_physics/disk.rb +5 -25
- data/lib/driving_physics/environment.rb +2 -0
- data/lib/driving_physics/gearbox.rb +60 -30
- data/lib/driving_physics/motor.rb +166 -58
- data/lib/driving_physics/mruby.rb +3 -0
- data/lib/driving_physics/pid_controller.rb +111 -0
- data/lib/driving_physics/powertrain.rb +8 -13
- data/lib/driving_physics/scalar_force.rb +2 -1
- data/lib/driving_physics/timer.rb +34 -0
- data/lib/driving_physics/vector_force.rb +23 -0
- data/lib/driving_physics.rb +28 -22
- data/test/disk.rb +23 -62
- data/test/gearbox.rb +36 -0
- data/test/motor.rb +236 -0
- data/test/powertrain.rb +21 -0
- data/test/scalar_force.rb +3 -6
- data/test/tire.rb +25 -59
- data/test/vector_force.rb +76 -37
- metadata +26 -5
- data/demo/mruby/disk.rb +0 -81
data/test/disk.rb
CHANGED
@@ -1,116 +1,78 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
2
|
require 'driving_physics/disk'
|
3
|
+
require 'matrix'
|
3
4
|
|
4
|
-
|
5
|
+
include DrivingPhysics
|
5
6
|
|
6
|
-
describe
|
7
|
+
describe Disk do
|
7
8
|
describe "Disk.volume" do
|
8
9
|
it "calculates the volume (m^3) of disk given radius and width" do
|
9
|
-
cubic_m =
|
10
|
+
cubic_m = Disk.volume(1.0, 1.0)
|
10
11
|
expect(cubic_m).must_equal Math::PI
|
11
12
|
|
12
|
-
cubic_m =
|
13
|
+
cubic_m = Disk.volume(0.35, 0.2)
|
13
14
|
expect(cubic_m).must_be_within_epsilon 0.076969
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
18
|
describe "Disk.volume_l" do
|
18
19
|
it "calculates the volume (L) of a disk given radius and width" do
|
19
|
-
liters =
|
20
|
+
liters = Disk.volume_l(1.0, 1.0)
|
20
21
|
expect(liters).must_equal Math::PI * 1000
|
21
22
|
|
22
|
-
liters =
|
23
|
+
liters = Disk.volume_l(0.35, 0.2)
|
23
24
|
expect(liters).must_be_within_epsilon 76.96902
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
27
28
|
describe "Disk.density" do
|
28
29
|
it "calculates the density (kg/L) given mass and volume" do
|
29
|
-
expect(
|
30
|
-
expect(
|
30
|
+
expect(Disk.density(25.0, 25.0)).must_equal 1.0
|
31
|
+
expect(Disk.density(50.0, 25.0)).must_equal 2.0
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
35
|
describe "Disk.mass" do
|
35
36
|
it "calculates the mass (kg) of a disk given radius, width, and density" do
|
36
|
-
|
37
|
-
expect(D.mass(0.35, 0.2, D::DENSITY)).must_be_within_epsilon 25.015
|
37
|
+
expect(Disk.mass(0.35, 0.2, Disk::DENSITY)).must_be_within_epsilon 76.969
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
describe "Disk.rotational_inertia" do
|
42
42
|
it "calculates rotational inertia for a disk given radius and mass" do
|
43
|
-
expect(
|
43
|
+
expect(Disk.rotational_inertia(0.35, 25.0)).must_be_within_epsilon 1.53125
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
describe "Disk.alpha" do
|
48
48
|
it "calculates angular acceleration from torque and inertia" do
|
49
49
|
scalar_torque = 1000
|
50
|
-
inertia =
|
51
|
-
expect(
|
50
|
+
inertia = Disk.rotational_inertia(0.35, 25.0)
|
51
|
+
expect(Disk.alpha scalar_torque, inertia).must_be_within_epsilon 653.061
|
52
52
|
|
53
|
-
skip
|
53
|
+
skip unless DrivingPhysics.has_vector?
|
54
54
|
vector_torque = Vector[0, 0, 1000]
|
55
|
-
vector_alpha =
|
55
|
+
vector_alpha = Disk.alpha vector_torque, inertia
|
56
56
|
expect(vector_alpha).must_be_instance_of Vector
|
57
57
|
expect(vector_alpha.size).must_equal 3
|
58
58
|
expect(vector_alpha[2]).must_be_within_epsilon 653.06
|
59
59
|
end
|
60
60
|
end
|
61
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
62
|
describe "instance methods" do
|
98
63
|
before do
|
99
64
|
@env = DrivingPhysics::Environment.new
|
100
|
-
@disk =
|
65
|
+
@disk = Disk.new(@env)
|
101
66
|
end
|
102
67
|
|
103
68
|
it "initializes" do
|
104
|
-
|
105
|
-
expect(@disk).
|
106
|
-
expect(@disk.
|
107
|
-
|
108
|
-
|
109
|
-
with_mass = D.new(@env) { |w|
|
110
|
-
w.mass = 99.01
|
111
|
-
}
|
69
|
+
expect(@disk).must_be_instance_of Disk
|
70
|
+
expect(@disk.density).must_equal Disk::DENSITY # sanity check
|
71
|
+
expect(@disk.mass).must_be_within_epsilon 76.969
|
72
|
+
|
73
|
+
with_mass = Disk.new(@env) { |w| w.mass = 99.01 }
|
112
74
|
expect(with_mass.mass).must_equal 99.01
|
113
|
-
expect(with_mass.density).wont_equal
|
75
|
+
expect(with_mass.density).wont_equal Disk::DENSITY
|
114
76
|
end
|
115
77
|
|
116
78
|
it "has a string representation" do
|
@@ -125,8 +87,7 @@ describe D do
|
|
125
87
|
end
|
126
88
|
|
127
89
|
it "has inertia" do
|
128
|
-
|
129
|
-
expect(@disk.rotational_inertia).must_be_within_epsilon 1.5321
|
90
|
+
expect(@disk.rotational_inertia).must_be_within_epsilon 4.714
|
130
91
|
end
|
131
92
|
end
|
132
93
|
end
|
data/test/gearbox.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/gearbox'
|
3
|
+
|
4
|
+
include DrivingPhysics
|
5
|
+
|
6
|
+
describe Gearbox do
|
7
|
+
it "has gear ratios" do
|
8
|
+
end
|
9
|
+
|
10
|
+
it "has a final drive" do
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has a neutral gear" do
|
14
|
+
end
|
15
|
+
|
16
|
+
it "has a clutch with state" do
|
17
|
+
end
|
18
|
+
|
19
|
+
it "has mass" do
|
20
|
+
end
|
21
|
+
|
22
|
+
it "has rotating mass" do
|
23
|
+
end
|
24
|
+
|
25
|
+
it "converts crank torque to axle torque" do
|
26
|
+
end
|
27
|
+
|
28
|
+
it "has losses due to inertia and friction" do
|
29
|
+
end
|
30
|
+
|
31
|
+
it "converts crank omega to axle omega (and vice versa)" do
|
32
|
+
end
|
33
|
+
|
34
|
+
it "varies torque delivery based on gear ratio and clutch" do
|
35
|
+
end
|
36
|
+
end
|
data/test/motor.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/motor'
|
3
|
+
|
4
|
+
include DrivingPhysics
|
5
|
+
|
6
|
+
describe "interpolate" do
|
7
|
+
it "validates xs and ys match in size" do
|
8
|
+
xs = [0, 5, 10, 15, 20]
|
9
|
+
ys = [10, 10, 10, 10, 5]
|
10
|
+
expect(DrivingPhysics.interpolate(1, xs: xs, ys: ys)).must_equal 10
|
11
|
+
expect { DrivingPhysics.interpolate(1, xs: xs + [10], ys: ys) }.must_raise
|
12
|
+
expect { DrivingPhysics.interpolate(1, xs: xs, ys: ys + [10]) }.must_raise
|
13
|
+
end
|
14
|
+
|
15
|
+
it "validates x is within the range of xs" do
|
16
|
+
xs = [0, 5, 10, 15, 20]
|
17
|
+
ys = [10, 10, 10, 10, 5]
|
18
|
+
expect(DrivingPhysics.interpolate(1, xs: xs, ys: ys)).must_equal 10
|
19
|
+
expect { DrivingPhysics.interpolate(-1, xs: xs, ys: ys) }.must_raise
|
20
|
+
expect { DrivingPhysics.interpolate(25, xs: xs, ys: ys) }.must_raise
|
21
|
+
end
|
22
|
+
|
23
|
+
it "validates the xs are in ascending order" do
|
24
|
+
xs = [0, 5, 10, 15, 20]
|
25
|
+
ys = [10, 10, 10, 10, 5]
|
26
|
+
expect(DrivingPhysics.interpolate(1, xs: xs, ys: ys)).must_equal 10
|
27
|
+
xs[1] = 15
|
28
|
+
expect {
|
29
|
+
DrivingPhysics.interpolate(16, xs: xs, ys: ys)
|
30
|
+
}.must_raise
|
31
|
+
end
|
32
|
+
|
33
|
+
it "performs linear interpolation to yield a y value for a valid x" do
|
34
|
+
xs = [0, 5, 10, 15, 20]
|
35
|
+
ys = [5, 5, 10, 10, 15]
|
36
|
+
|
37
|
+
expect(DrivingPhysics.interpolate(0, xs: xs, ys: ys)).must_equal 5
|
38
|
+
expect(DrivingPhysics.interpolate(1, xs: xs, ys: ys)).must_equal 5
|
39
|
+
expect(DrivingPhysics.interpolate(4, xs: xs, ys: ys)).must_equal 5
|
40
|
+
expect(DrivingPhysics.interpolate(5, xs: xs, ys: ys)).must_equal 5
|
41
|
+
expect(DrivingPhysics.interpolate(6, xs: xs, ys: ys)).must_equal 6
|
42
|
+
expect(DrivingPhysics.interpolate(7, xs: xs, ys: ys)).must_equal 7
|
43
|
+
expect(DrivingPhysics.interpolate(8, xs: xs, ys: ys)).must_equal 8
|
44
|
+
expect(DrivingPhysics.interpolate(9, xs: xs, ys: ys)).must_equal 9
|
45
|
+
expect(DrivingPhysics.interpolate(10, xs: xs, ys: ys)).must_equal 10
|
46
|
+
expect(DrivingPhysics.interpolate(11, xs: xs, ys: ys)).must_equal 10
|
47
|
+
expect(DrivingPhysics.interpolate(12, xs: xs, ys: ys)).must_equal 10
|
48
|
+
expect(DrivingPhysics.interpolate(14, xs: xs, ys: ys)).must_equal 10
|
49
|
+
expect(DrivingPhysics.interpolate(15, xs: xs, ys: ys)).must_equal 10
|
50
|
+
expect(DrivingPhysics.interpolate(16, xs: xs, ys: ys)).must_equal 11
|
51
|
+
expect(DrivingPhysics.interpolate(17, xs: xs, ys: ys)).must_equal 12
|
52
|
+
expect(DrivingPhysics.interpolate(18, xs: xs, ys: ys)).must_equal 13
|
53
|
+
expect(DrivingPhysics.interpolate(19, xs: xs, ys: ys)).must_equal 14
|
54
|
+
expect(DrivingPhysics.interpolate(20, xs: xs, ys: ys)).must_equal 15
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# shorthand
|
59
|
+
def tc(rpms, torques)
|
60
|
+
TorqueCurve.new(rpms: rpms, torques: torques)
|
61
|
+
end
|
62
|
+
|
63
|
+
describe TorqueCurve do
|
64
|
+
before do
|
65
|
+
@default = TorqueCurve.new
|
66
|
+
@rpms = [500, 1000, 3500, 6000, 7000, 7100]
|
67
|
+
@torques = [ 0, 100, 300, 300, 250, 0]
|
68
|
+
@minr = [0, 1]
|
69
|
+
@mint = [0, 0]
|
70
|
+
end
|
71
|
+
|
72
|
+
it "maps rpm values to torque values" do
|
73
|
+
expect(@default.torque(3500)).must_be(:>, 100)
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "initialize" do
|
77
|
+
it "accepts two arrays: rpms, and the corresponding torques" do
|
78
|
+
expect(tc(@rpms, @torques)).must_be_kind_of(TorqueCurve)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "has defaults for rpms and torques" do
|
82
|
+
expect(@default).must_be_kind_of(TorqueCurve)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "validates size match for rpms and torques" do
|
86
|
+
expect( tc( @minr, @mint) ).must_be_kind_of TorqueCurve
|
87
|
+
expect { tc( [0], @mint) }.must_raise
|
88
|
+
expect { tc([0,1,2], @mint) }.must_raise
|
89
|
+
expect { tc(@minr, [0]) }.must_raise
|
90
|
+
expect { tc(@minr, [0,1,0]) }.must_raise
|
91
|
+
|
92
|
+
rpms = [100, 2000, 30_000]
|
93
|
+
torques = [0, 500, 0]
|
94
|
+
|
95
|
+
expect( tc( rpms, torques) ).must_be_kind_of TorqueCurve
|
96
|
+
expect { tc( rpms, [0, 0]) }.must_raise
|
97
|
+
expect { tc([1,2], torques) }.must_raise
|
98
|
+
expect { tc([1,2,3,4], torques) }.must_raise
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
describe "rpms" do
|
103
|
+
it "validates rpms are positive" do
|
104
|
+
expect( tc( @minr, @mint) ).must_be_kind_of TorqueCurve
|
105
|
+
expect { tc([-10, 10], @mint) }.must_raise
|
106
|
+
end
|
107
|
+
|
108
|
+
it "validates rpms are in ascending order" do
|
109
|
+
expect( tc( @minr, @mint) ).must_be_kind_of TorqueCurve
|
110
|
+
expect { tc([0, 0], @mint) }.must_raise
|
111
|
+
expect( tc([99, 100], @mint) ).must_be_kind_of TorqueCurve
|
112
|
+
expect { tc([100, 99], @mint) }.must_raise
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "torques" do
|
117
|
+
it "validates that torques start and end at zero" do
|
118
|
+
expect( tc(@minr, @mint) ).must_be_kind_of TorqueCurve
|
119
|
+
expect { tc(@minr, [1,0]) }.must_raise
|
120
|
+
expect { tc(@minr, [0,1]) }.must_raise
|
121
|
+
expect { tc(@minr, [1,1]) }.must_raise
|
122
|
+
|
123
|
+
expect( tc([1,2,3], [0,500,0]) ).must_be_kind_of TorqueCurve
|
124
|
+
expect { tc([1,2,3], [1,500,0]) }.must_raise
|
125
|
+
expect { tc([1,2,3], [0,500,1]) }.must_raise
|
126
|
+
end
|
127
|
+
|
128
|
+
it "validates that torques are positive" do
|
129
|
+
expect( tc([1,2,3], [0, 5,0]) ).must_be_kind_of TorqueCurve
|
130
|
+
expect { tc([1,2,3], [0,-5,0]) }.must_raise
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it "finds the peak torque and corresponding RPM" do
|
136
|
+
rpm, torque = *@default.peak
|
137
|
+
expect(rpm).must_be(:>, 1000)
|
138
|
+
expect(rpm).must_be(:<, 10_000)
|
139
|
+
expect(torque).must_be(:>, 0)
|
140
|
+
expect(torque).must_be(:<, 10_000)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "interpolates to find the torque for a valid RPM" do
|
144
|
+
tc = tc(@rpms, @torques)
|
145
|
+
r1 = @rpms[1]
|
146
|
+
r2 = @rpms[2]
|
147
|
+
|
148
|
+
expect(tc.torque(r1)).must_equal @torques[1]
|
149
|
+
expect(tc.torque(r2)).must_equal @torques[2]
|
150
|
+
|
151
|
+
r3 = (r1 + r2) / 2
|
152
|
+
t3 = tc.torque(r3)
|
153
|
+
t3_expect = (@torques[1] + @torques[2]) / 2.0
|
154
|
+
|
155
|
+
expect(t3).must_be :>, @torques[1]
|
156
|
+
expect(t3).must_be :<, @torques[2]
|
157
|
+
expect(t3).must_be :>=, t3_expect.floor
|
158
|
+
expect(t3).must_be :<=, t3_expect.ceil
|
159
|
+
end
|
160
|
+
|
161
|
+
it "tracks min, idle, redline, and max rpms" do
|
162
|
+
expect(@default.min).must_be :>, 0
|
163
|
+
expect(@default.min).must_be :<, 1000
|
164
|
+
expect(@default.idle).must_be :>, @default.min
|
165
|
+
expect(@default.idle).must_be :<, 3000
|
166
|
+
expect(@default.redline).must_be :>, @default.idle
|
167
|
+
expect(@default.redline).must_be :<, 20_000
|
168
|
+
expect(@default.max).must_be :>, @default.redline
|
169
|
+
expect(@default.max).must_be :<, 20_000
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe Motor do
|
174
|
+
before do
|
175
|
+
@default = Motor.new(Environment.new)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "has a throttle with state" do
|
179
|
+
expect(@default.throttle).must_equal 0.0
|
180
|
+
@default.throttle = 0.5
|
181
|
+
expect(@default.throttle).must_equal 0.5
|
182
|
+
expect(@default.throttle_pct).must_equal "50.0%"
|
183
|
+
|
184
|
+
expect { @default.throttle = 1.5 }.must_raise
|
185
|
+
end
|
186
|
+
|
187
|
+
it "has mass" do
|
188
|
+
expect(@default.mass).must_be :>, 0
|
189
|
+
expect(@default.fixed_mass).must_be :>, 0
|
190
|
+
expect(@default.rotating_mass).must_be :>, 0
|
191
|
+
end
|
192
|
+
|
193
|
+
it "has a torque curve" do
|
194
|
+
expect(@default.torque_curve).must_be_kind_of TorqueCurve
|
195
|
+
end
|
196
|
+
|
197
|
+
it "has idle RPM and redline RPM" do
|
198
|
+
expect(@default.idle).must_be :>, 500
|
199
|
+
expect(@default.idle).must_be :<, 1500
|
200
|
+
|
201
|
+
expect(@default.redline).must_be :>, @default.idle
|
202
|
+
expect(@default.redline).must_be :<, 10_000
|
203
|
+
end
|
204
|
+
|
205
|
+
it "has inertia and energy" do
|
206
|
+
expect(@default.inertia).must_be :>, 0
|
207
|
+
expect(@default.energy(99)).must_be :>, 0
|
208
|
+
end
|
209
|
+
|
210
|
+
it "determines crank alpha from torque" do
|
211
|
+
expect(@default.alpha(50)).must_be :>, 0
|
212
|
+
end
|
213
|
+
|
214
|
+
it "determines torque from crank alpha" do
|
215
|
+
a = @default.alpha(50, omega: 20)
|
216
|
+
t = @default.implied_torque(a)
|
217
|
+
# frictional losses
|
218
|
+
expect(t).must_be :>, 40
|
219
|
+
expect(t).must_be :<, 50
|
220
|
+
end
|
221
|
+
|
222
|
+
it "determines torque based on torque curve, RPM and throttle" do
|
223
|
+
@default.throttle = 1.0
|
224
|
+
expect(@default.torque(1000)).must_be :>, 0
|
225
|
+
expect(@default.torque(3500)).must_be :>, @default.torque(1000)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "has an engine braking effect when the throttle is closed" do
|
229
|
+
@default.throttle = 0
|
230
|
+
expect(@default.torque(3500)).must_be :<, 0
|
231
|
+
end
|
232
|
+
|
233
|
+
it "has a starter motor to get running" do
|
234
|
+
expect(@default.starter_torque).must_be :>, 0
|
235
|
+
end
|
236
|
+
end
|
data/test/powertrain.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/powertrain'
|
3
|
+
|
4
|
+
include DrivingPhysics
|
5
|
+
|
6
|
+
describe Powertrain do
|
7
|
+
it "has a motor and a gearbox" do
|
8
|
+
end
|
9
|
+
|
10
|
+
it "has mass" do
|
11
|
+
end
|
12
|
+
|
13
|
+
it "coverts rpm to axle torque" do
|
14
|
+
end
|
15
|
+
|
16
|
+
it "converts rpm to axle omega" do
|
17
|
+
end
|
18
|
+
|
19
|
+
it "determines crank rpm from axle_omega" do
|
20
|
+
end
|
21
|
+
end
|
data/test/scalar_force.rb
CHANGED
@@ -12,8 +12,7 @@ describe ScalarForce do
|
|
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
|
-
|
16
|
-
must_be_within_epsilon(-1 * ROT_COF)
|
15
|
+
_(30 * ScalarForce.air_resistance(1)).must_be_within_epsilon(-1 * ROT_COF)
|
17
16
|
end
|
18
17
|
|
19
18
|
it "approximates a positive drag force" do
|
@@ -21,12 +20,10 @@ describe ScalarForce do
|
|
21
20
|
end
|
22
21
|
|
23
22
|
it "approximates a positive rotational resistance force" do
|
24
|
-
|
25
|
-
must_be_within_epsilon(-383.13)
|
23
|
+
_(ScalarForce.rotational_resistance 30).must_be_within_epsilon(-383.13)
|
26
24
|
end
|
27
25
|
|
28
26
|
it "approximates a positive rolling resistance force" do
|
29
|
-
|
30
|
-
expect(ScalarForce.rolling_resistance nf).must_be_within_epsilon(-98.0)
|
27
|
+
_(ScalarForce.rolling_resistance(1000 * G)).must_be_within_epsilon(-98.0)
|
31
28
|
end
|
32
29
|
end
|
data/test/tire.rb
CHANGED
@@ -1,128 +1,94 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
2
|
require 'driving_physics/tire'
|
3
|
+
require 'driving_physics/vector_force'
|
3
4
|
|
4
|
-
|
5
|
+
include DrivingPhysics
|
5
6
|
|
6
|
-
describe
|
7
|
+
describe Tire do
|
7
8
|
describe "Tire.traction" do
|
8
9
|
it "calculates traction force from normal force and coeff of friction" do
|
9
10
|
scalar_nf = 9800 # N
|
10
11
|
cof = 1.1
|
11
|
-
scalar_t =
|
12
|
+
scalar_t = Tire.traction(scalar_nf, cof)
|
12
13
|
expect(scalar_t).must_equal 10780.0
|
13
14
|
|
14
|
-
skip
|
15
|
+
skip unless DrivingPhysics.has_vector?
|
15
16
|
vector_nf = Vector[9800, 0]
|
16
|
-
vector_t =
|
17
|
+
vector_t = Tire.traction(vector_nf, cof)
|
17
18
|
expect(vector_t).must_equal Vector[10780.0, 0.0]
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
22
|
describe "Tire.volume" do
|
22
23
|
it "calculates the volume (m^3) of disk given radius and width" do
|
23
|
-
cubic_m =
|
24
|
+
cubic_m = Tire.volume(1.0, 1.0)
|
24
25
|
expect(cubic_m).must_equal Math::PI
|
25
26
|
|
26
|
-
cubic_m =
|
27
|
+
cubic_m = Tire.volume(0.35, 0.2)
|
27
28
|
expect(cubic_m).must_be_within_epsilon 0.076969
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
31
32
|
describe "Tire.volume_l" do
|
32
33
|
it "calculates the volume (L) of a disk given radius and width" do
|
33
|
-
liters =
|
34
|
+
liters = Tire.volume_l(1.0, 1.0)
|
34
35
|
expect(liters).must_equal Math::PI * 1000
|
35
36
|
|
36
|
-
liters =
|
37
|
+
liters = Tire.volume_l(0.35, 0.2)
|
37
38
|
expect(liters).must_be_within_epsilon 76.96902
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
42
|
describe "Tire.density" do
|
42
43
|
it "calculates the density (kg/L) given mass and volume" do
|
43
|
-
expect(
|
44
|
-
expect(
|
44
|
+
expect(Tire.density(25.0, 25.0)).must_equal 1.0
|
45
|
+
expect(Tire.density(50.0, 25.0)).must_equal 2.0
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
49
|
describe "Tire.mass" do
|
49
50
|
it "calculates the mass (kg) of a disk given radius, width, and density" do
|
50
|
-
expect(
|
51
|
+
expect(Tire.mass(0.35, 0.2, Tire::DENSITY)).must_be_within_epsilon 25.015
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
54
55
|
describe "Tire.rotational_inertia" do
|
55
56
|
it "calculates rotational inertia for a disk given radius and mass" do
|
56
|
-
expect(
|
57
|
+
expect(Tire.rotational_inertia(0.35, 25.0)).must_be_within_epsilon 1.53125
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
60
61
|
describe "Tire.alpha" do
|
61
62
|
it "calculates angular acceleration from torque and inertia" do
|
62
63
|
scalar_torque = 1000
|
63
|
-
inertia =
|
64
|
-
expect(
|
64
|
+
inertia = Tire.rotational_inertia(0.35, 25.0)
|
65
|
+
expect(Tire.alpha scalar_torque, inertia).must_be_within_epsilon 653.061
|
65
66
|
|
66
|
-
skip
|
67
|
+
skip unless DrivingPhysics.has_vector?
|
67
68
|
vector_torque = Vector[0, 0, 1000]
|
68
|
-
vector_alpha =
|
69
|
+
vector_alpha = Tire.alpha vector_torque, inertia
|
69
70
|
expect(vector_alpha).must_be_instance_of Vector
|
70
71
|
expect(vector_alpha.size).must_equal 3
|
71
72
|
expect(vector_alpha[2]).must_be_within_epsilon 653.06
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
75
|
-
describe "Tire.torque_vector" do
|
76
|
-
it "calculates a torque in the 3rd dimension given 2D force and radius" do
|
77
|
-
skip # Vector
|
78
|
-
force = Vector[1000, 0]
|
79
|
-
radius = Vector[0, 5]
|
80
|
-
torque = T.torque_vector(force, radius)
|
81
|
-
expect(torque).must_be_instance_of Vector
|
82
|
-
expect(torque.size).must_equal 3
|
83
|
-
expect(torque[2]).must_be_within_epsilon 5000.0
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "Tire.force_vector" do
|
88
|
-
it "calculates a (3D) force given 3D torque and 2D radius" do
|
89
|
-
# let's invert the Tire.torque_vector case from above:
|
90
|
-
skip # Vector
|
91
|
-
torque = Vector[0, 0, 5000]
|
92
|
-
radius = Vector[0, 5]
|
93
|
-
force = T.force_vector(torque, radius)
|
94
|
-
expect(force).must_be_instance_of Vector
|
95
|
-
expect(force.size).must_equal 3
|
96
|
-
expect(force[0]).must_be_within_epsilon 1000.0
|
97
|
-
|
98
|
-
# now let's rotate the radius into the x-dimension
|
99
|
-
# right hand rule, positive torque means thumb into screen, clockwise
|
100
|
-
# negative-x radius means positive-y force
|
101
|
-
torque = Vector[0, 0, 500]
|
102
|
-
radius = Vector[-5, 0]
|
103
|
-
force = T.force_vector(torque, radius)
|
104
|
-
expect(force).must_be_instance_of Vector
|
105
|
-
expect(force.size).must_equal 3
|
106
|
-
expect(force[1]).must_be_within_epsilon 100.0
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
76
|
describe "instance methods" do
|
111
77
|
before do
|
112
78
|
@env = DrivingPhysics::Environment.new
|
113
|
-
@tire =
|
79
|
+
@tire = Tire.new(@env)
|
114
80
|
end
|
115
81
|
|
116
82
|
it "initializes" do
|
117
|
-
expect(@tire).must_be_instance_of
|
118
|
-
expect(@tire.density).must_equal
|
83
|
+
expect(@tire).must_be_instance_of Tire
|
84
|
+
expect(@tire.density).must_equal Tire::DENSITY # sanity check
|
119
85
|
expect(@tire.mass).must_be_within_epsilon 25.01
|
120
86
|
|
121
|
-
with_mass =
|
87
|
+
with_mass = Tire.new(@env) { |w|
|
122
88
|
w.mass = 99.01
|
123
89
|
}
|
124
90
|
expect(with_mass.mass).must_equal 99.01
|
125
|
-
expect(with_mass.density).wont_equal
|
91
|
+
expect(with_mass.density).wont_equal Tire::DENSITY
|
126
92
|
end
|
127
93
|
|
128
94
|
it "has a string representation" do
|
@@ -158,7 +124,7 @@ describe T do
|
|
158
124
|
expect(@tire.traction scalar_nf).must_equal 10780.0
|
159
125
|
expect(@tire.traction scalar_nf, static: false).must_equal 6860.0
|
160
126
|
|
161
|
-
skip
|
127
|
+
skip unless DrivingPhysics.has_vector?
|
162
128
|
vector_nf = Vector[9800, 0]
|
163
129
|
expect(@tire.traction vector_nf).must_equal Vector[10780.0, 0.0]
|
164
130
|
expect(@tire.traction vector_nf, static: false).
|
@@ -178,7 +144,7 @@ describe T do
|
|
178
144
|
expect(kin_tq).must_be_within_epsilon 2401.0
|
179
145
|
|
180
146
|
# not sure about how torque vectors work, but the "math" "works":
|
181
|
-
skip
|
147
|
+
skip unless DrivingPhysics.has_vector?
|
182
148
|
vector_nf = Vector[9800, 0]
|
183
149
|
expect(@tire.tractable_torque(vector_nf)[0]).
|
184
150
|
must_be_within_epsilon 3773.0
|