driving_physics 0.0.1.2 → 0.0.3.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 +23 -1
- data/Rakefile +84 -0
- data/VERSION +1 -1
- data/demo/car.rb +85 -72
- data/demo/gearbox.rb +4 -3
- data/demo/motor.rb +12 -10
- data/demo/mruby/car.rb +184 -0
- data/demo/mruby/motor.rb +140 -0
- 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 +14 -5
- data/lib/driving_physics/cli.rb +7 -35
- data/lib/driving_physics/cockpit.rb +167 -0
- data/lib/driving_physics/disk.rb +10 -30
- data/lib/driving_physics/environment.rb +2 -0
- data/lib/driving_physics/gearbox.rb +62 -32
- data/lib/driving_physics/motor.rb +218 -63
- data/lib/driving_physics/mruby.rb +48 -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/tire.rb +6 -6
- data/lib/driving_physics/vector_force.rb +23 -0
- data/lib/driving_physics.rb +28 -22
- data/test/disk.rb +23 -59
- 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 -53
- data/test/vector_force.rb +76 -37
- metadata +28 -4
@@ -0,0 +1,34 @@
|
|
1
|
+
module DrivingPhysics
|
2
|
+
module Timer
|
3
|
+
# don't use `defined?` with mruby
|
4
|
+
if (Process::CLOCK_MONOTONIC rescue false)
|
5
|
+
def self.now
|
6
|
+
Process.clock_gettime Process::CLOCK_MONOTONIC
|
7
|
+
end
|
8
|
+
else
|
9
|
+
def self.now
|
10
|
+
Time.now
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.since(t)
|
15
|
+
self.now - t
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.elapsed(&work)
|
19
|
+
t = self.now
|
20
|
+
return yield, self.since(t)
|
21
|
+
end
|
22
|
+
|
23
|
+
# HH:MM:SS.mmm
|
24
|
+
def self.display(seconds: 0, ms: 0)
|
25
|
+
ms += (seconds * 1000).round if seconds > 0
|
26
|
+
DrivingPhysics.elapsed_display(ms)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.summary(start, num_ticks, paused = 0)
|
30
|
+
elapsed = self.since(start) - paused
|
31
|
+
format("%.3f s (%d ticks/s)", elapsed, num_ticks.to_f / elapsed)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/driving_physics/tire.rb
CHANGED
@@ -26,14 +26,14 @@ module DrivingPhysics
|
|
26
26
|
|
27
27
|
def initialize(env)
|
28
28
|
@env = env
|
29
|
-
@radius =
|
30
|
-
@width =
|
29
|
+
@radius = 0.35
|
30
|
+
@width = 0.2
|
31
31
|
@density = DENSITY
|
32
32
|
@temp = @env.air_temp
|
33
|
-
@mu_s =
|
34
|
-
@mu_k =
|
35
|
-
@base_friction = 5/
|
36
|
-
@omega_friction = 5/
|
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
37
|
@roll_cof = DrivingPhysics::ROLL_COF
|
38
38
|
|
39
39
|
yield self if block_given?
|
@@ -64,6 +64,29 @@ module DrivingPhysics
|
|
64
64
|
dir
|
65
65
|
end
|
66
66
|
|
67
|
+
def self.torque_vector(force, radius)
|
68
|
+
if !force.is_a?(Vector) or force.size != 2
|
69
|
+
raise(ArgumentError, "force must be a 2D vector")
|
70
|
+
end
|
71
|
+
if !radius.is_a?(Vector) or radius.size != 2
|
72
|
+
raise(ArgumentError, "radius must be a 2D vector")
|
73
|
+
end
|
74
|
+
force = Vector[force[0], force[1], 0]
|
75
|
+
radius = Vector[radius[0], radius[1], 0]
|
76
|
+
force.cross(radius)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.force_vector(torque, radius)
|
80
|
+
if !torque.is_a?(Vector) or torque.size != 3
|
81
|
+
raise(ArgumentError, "torque must be a 3D vector")
|
82
|
+
end
|
83
|
+
if !radius.is_a?(Vector) or radius.size != 2
|
84
|
+
raise(ArgumentError, "radius must be a 2D vector")
|
85
|
+
end
|
86
|
+
radius = Vector[radius[0], radius[1], 0]
|
87
|
+
radius.cross(torque) / radius.dot(radius)
|
88
|
+
end
|
89
|
+
|
67
90
|
module VectorForce
|
68
91
|
#
|
69
92
|
# Resistance Forces
|
data/lib/driving_physics.rb
CHANGED
@@ -1,37 +1,36 @@
|
|
1
|
+
#
|
2
|
+
# Units: metric
|
3
|
+
#
|
4
|
+
# distance: meter
|
5
|
+
# velocity: meter / sec
|
6
|
+
# acceleration: meter / sec^2
|
7
|
+
# volume: Liter
|
8
|
+
# temperature: Celsius
|
9
|
+
#
|
1
10
|
module DrivingPhysics
|
2
|
-
#
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# acceleration: meter / sec^2
|
8
|
-
# volume: Liter
|
9
|
-
# temperature: Celsius
|
10
|
-
#
|
11
|
+
# runtime check; this returns false by default
|
12
|
+
# Vector is not currently/easily available in mruby
|
13
|
+
def self.has_vector?
|
14
|
+
Vector rescue false
|
15
|
+
end
|
11
16
|
|
12
|
-
#
|
13
17
|
# environmental defaults
|
14
|
-
#
|
15
18
|
HZ = 1000
|
16
19
|
TICK = Rational(1) / HZ
|
17
20
|
G = 9.8 # m/s^2
|
18
21
|
AIR_TEMP = 25 # deg c
|
19
22
|
AIR_DENSITY = 1.29 # kg / m^3
|
20
|
-
PETROL_DENSITY = 0.71 # kg / L
|
23
|
+
PETROL_DENSITY = 0.71 # kg / L
|
21
24
|
|
22
|
-
#
|
23
25
|
# defaults for resistance forces
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
ROLL_COF = 0.01 # roughly: street tires on concrete
|
26
|
+
FRONTAL_AREA = 2.2 # m^2, based roughly on 2000s-era Chevrolet Corvette
|
27
|
+
DRAG_COF = 0.3 # based roughly on 2000s-era Chevrolet Corvette
|
28
|
+
DRAG = 0.4257 # air_resistance at 1 m/s given above numbers
|
29
|
+
ROT_COF = 12.771 # if rot matches air at 30 m/s
|
30
|
+
ROT_CONST = 0.05 # N opposing drive force / torque
|
31
|
+
ROLL_COF = 0.01 # roughly: street tires on concrete
|
31
32
|
|
32
|
-
#
|
33
33
|
# constants
|
34
|
-
#
|
35
34
|
SECS_PER_MIN = 60
|
36
35
|
MINS_PER_HOUR = 60
|
37
36
|
SECS_PER_HOUR = SECS_PER_MIN * MINS_PER_HOUR
|
@@ -70,4 +69,11 @@ module DrivingPhysics
|
|
70
69
|
alias_method(:omega, :accum)
|
71
70
|
alias_method(:theta, :accum)
|
72
71
|
end
|
72
|
+
|
73
|
+
def self.unit_interval!(val)
|
74
|
+
if val < 0.0 or val > 1.0
|
75
|
+
raise(ArgumentError, "val #{val.inspect} should be between 0 and 1")
|
76
|
+
end
|
77
|
+
val
|
78
|
+
end
|
73
79
|
end
|
data/test/disk.rb
CHANGED
@@ -1,113 +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 unless DrivingPhysics.has_vector?
|
53
54
|
vector_torque = Vector[0, 0, 1000]
|
54
|
-
vector_alpha =
|
55
|
+
vector_alpha = Disk.alpha vector_torque, inertia
|
55
56
|
expect(vector_alpha).must_be_instance_of Vector
|
56
57
|
expect(vector_alpha.size).must_equal 3
|
57
58
|
expect(vector_alpha[2]).must_be_within_epsilon 653.06
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
61
|
-
describe "Disk.torque_vector" do
|
62
|
-
it "calculates a torque in the 3rd dimension given 2D force and radius" do
|
63
|
-
force = Vector[1000, 0]
|
64
|
-
radius = Vector[0, 5]
|
65
|
-
torque = D.torque_vector(force, radius)
|
66
|
-
expect(torque).must_be_instance_of Vector
|
67
|
-
expect(torque.size).must_equal 3
|
68
|
-
expect(torque[2]).must_be_within_epsilon 5000.0
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
describe "Disk.force_vector" do
|
73
|
-
it "calculates a (3D) force given 3D torque and 2D radius" do
|
74
|
-
# let's invert the Disk.torque_vector case from above:
|
75
|
-
torque = Vector[0, 0, 5000]
|
76
|
-
radius = Vector[0, 5]
|
77
|
-
force = D.force_vector(torque, radius)
|
78
|
-
expect(force).must_be_instance_of Vector
|
79
|
-
expect(force.size).must_equal 3
|
80
|
-
expect(force[0]).must_be_within_epsilon 1000.0
|
81
|
-
|
82
|
-
# now let's rotate the radius into the x-dimension
|
83
|
-
# right hand rule, positive torque means thumb into screen, clockwise
|
84
|
-
# negative-x radius means positive-y force
|
85
|
-
torque = Vector[0, 0, 500]
|
86
|
-
radius = Vector[-5, 0]
|
87
|
-
force = D.force_vector(torque, radius)
|
88
|
-
expect(force).must_be_instance_of Vector
|
89
|
-
expect(force.size).must_equal 3
|
90
|
-
expect(force[1]).must_be_within_epsilon 100.0
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
62
|
describe "instance methods" do
|
95
63
|
before do
|
96
64
|
@env = DrivingPhysics::Environment.new
|
97
|
-
@disk =
|
65
|
+
@disk = Disk.new(@env)
|
98
66
|
end
|
99
67
|
|
100
68
|
it "initializes" do
|
101
|
-
|
102
|
-
expect(@disk).
|
103
|
-
expect(@disk.
|
104
|
-
|
105
|
-
|
106
|
-
with_mass = D.new(@env) { |w|
|
107
|
-
w.mass = 99.01
|
108
|
-
}
|
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 }
|
109
74
|
expect(with_mass.mass).must_equal 99.01
|
110
|
-
expect(with_mass.density).wont_equal
|
75
|
+
expect(with_mass.density).wont_equal Disk::DENSITY
|
111
76
|
end
|
112
77
|
|
113
78
|
it "has a string representation" do
|
@@ -122,8 +87,7 @@ describe D do
|
|
122
87
|
end
|
123
88
|
|
124
89
|
it "has inertia" do
|
125
|
-
|
126
|
-
expect(@disk.rotational_inertia).must_be_within_epsilon 1.5321
|
90
|
+
expect(@disk.rotational_inertia).must_be_within_epsilon 4.714
|
127
91
|
end
|
128
92
|
end
|
129
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
|