Module: DrivingPhysics

Defined in:
lib/driving_physics.rb,
lib/driving_physics/car.rb,
lib/driving_physics/cli.rb,
lib/driving_physics/disk.rb,
lib/driving_physics/tire.rb,
lib/driving_physics/motor.rb,
lib/driving_physics/mruby.rb,
lib/driving_physics/power.rb,
lib/driving_physics/timer.rb,
lib/driving_physics/cockpit.rb,
lib/driving_physics/gearbox.rb,
lib/driving_physics/imperial.rb,
lib/driving_physics/powertrain.rb,
lib/driving_physics/environment.rb,
lib/driving_physics/scalar_force.rb,
lib/driving_physics/vector_force.rb,
lib/driving_physics/pid_controller.rb

Overview

Power is Work / time P = W / dt P = T * Th / dt P = T * Omega

Defined Under Namespace

Modules: CLI, Imperial, ScalarForce, Timer, VectorForce, VectorZeroBackport Classes: Car, Cockpit, Disk, Environment, Gearbox, Motor, PIDController, Powertrain, Tire, TorqueCurve

Constant Summary collapse

HZ =

environmental defaults

1000
TICK =
Rational(1) / HZ
G =

m/s^2

9.8
AIR_TEMP =

deg c

25
AIR_DENSITY =

kg / m^3

1.29
PETROL_DENSITY =

kg / L

0.71
FRONTAL_AREA =

defaults for resistance forces

2.2
DRAG_COF =

m^2, based roughly on 2000s-era Chevrolet Corvette

0.3
DRAG =

based roughly on 2000s-era Chevrolet Corvette

0.4257
ROT_COF =

air_resistance at 1 m/s given above numbers

12.771
ROT_CONST =

if rot matches air at 30 m/s

0.05
ROLL_COF =

N opposing drive force / torque

0.01
SECS_PER_MIN =

constants

60
MINS_PER_HOUR =
60
SECS_PER_HOUR =
SECS_PER_MIN * MINS_PER_HOUR

Class Method Summary collapse

Class Method Details

.acc(force, mass) ⇒ Object

acceleration; F=ma force can be a scalar or a Vector



56
57
58
# File 'lib/driving_physics.rb', line 56

def self.acc(force, mass)
  force / mass.to_f
end

.accum(init, rate, dt: TICK) ⇒ Object Also known as: vel, pos, omega, theta

init and rate can be scalar or Vector but must match this provides the general form for determining velocity and position



62
63
64
# File 'lib/driving_physics.rb', line 62

def self.accum(init, rate, dt: TICK)
  init + rate * dt
end

.compass_dir(unit_vector) ⇒ Object

,0 E 0, N .9,.1 ENE .1,.9 NNE



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/driving_physics/vector_force.rb', line 43

def self.compass_dir(unit_vector)
  horz = case
         when unit_vector[0] < -0.001 then 'W'
         when unit_vector[0] > 0.001 then 'E'
         else ''
         end

  vert = case
         when unit_vector[1] < -0.001 then 'S'
         when unit_vector[1] > 0.001 then 'N'
         else ''
         end

  dir = [vert, horz].join
  if dir.length == 2
    # detect and include bias
    if (unit_vector[0].abs - unit_vector[1].abs).abs > 0.2
      bias = unit_vector[0].abs > unit_vector[1].abs ? horz : vert
      dir = [bias, dir].join
    end
  end
  dir
end

.elapsed_display(elapsed_ms) ⇒ Object

HH::MM::SS.mmm



39
40
41
42
43
44
45
46
47
48
# File 'lib/driving_physics.rb', line 39

def self.elapsed_display(elapsed_ms)
  elapsed_s, ms = elapsed_ms.divmod 1000

  h = elapsed_s / SECS_PER_HOUR
  elapsed_s -= h * SECS_PER_HOUR
  m, s = elapsed_s.divmod SECS_PER_MIN

  [[h, m, s].map { |i| i.to_s.rjust(2, '0') }.join(':'),
   ms.to_s.rjust(3, '0')].join('.')
end

.force_vector(torque, radius) ⇒ Object



79
80
81
82
83
84
85
86
87
88
# File 'lib/driving_physics/vector_force.rb', line 79

def self.force_vector(torque, radius)
  if !torque.is_a?(Vector) or torque.size != 3
    raise(ArgumentError, "torque must be a 3D vector")
  end
  if !radius.is_a?(Vector) or radius.size != 2
    raise(ArgumentError, "radius must be a 2D vector")
  end
  radius = Vector[radius[0], radius[1], 0]
  radius.cross(torque) / radius.dot(radius)
end

.has_vector?Boolean

runtime check; this returns false by default Vector is not currently/easily available in mruby

Returns:

  • (Boolean)


13
14
15
# File 'lib/driving_physics.rb', line 13

def self.has_vector?
  Vector rescue false
end

.interpolate(x, xs:, ys:) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/driving_physics/motor.rb', line 4

def self.interpolate(x, xs:, ys:)
  raise("Xs size #{xs.size}; Ys size #{ys.size}") unless xs.size == ys.size
  raise("#{x} out of range") if x < xs.first or x > xs.last
  xs.each.with_index { |xi, i|
    return ys[i] if x == xi
    if i > 0
      last_x, last_y = xs[i-1], ys[i-1]
      raise("xs out of order (#{xi} <= #{last_x})") unless xi > last_x
      if x <= xi
        proportion = Rational(x - last_x) / (xi - last_x)
        return last_y + (ys[i] - last_y) * proportion
      end
    end
  }
  raise("couldn't find #{x} in #{xs.inspect}") # sanity check
end

.kph(meters_per_sec) ⇒ Object



50
51
52
# File 'lib/driving_physics.rb', line 50

def self.kph(meters_per_sec)
  meters_per_sec.to_f * SECS_PER_HOUR / 1000
end

.power(force, speed) ⇒ Object



17
18
19
# File 'lib/driving_physics/power.rb', line 17

def self.power(force, speed)
  force * speed
end

.rads(revs) ⇒ Object

convert revs to rads; works for alpha/omega/theta



20
21
22
# File 'lib/driving_physics/disk.rb', line 20

def self.rads(revs)
  revs * 2 * Math::PI
end

.random_centered_zero(magnitude) ⇒ Object

e.g. given 5, yields a uniformly random number from -5 to +5



18
19
20
21
# File 'lib/driving_physics/vector_force.rb', line 18

def self.random_centered_zero(magnitude)
  m = [magnitude.abs, 1].max
  Random.rand(m * 2 + 1) - m
end

.random_unit_vector(dimensions = 2, resolution: 9) ⇒ Object



23
24
25
26
27
28
29
30
# File 'lib/driving_physics/vector_force.rb', line 23

def self.random_unit_vector(dimensions = 2, resolution: 9)
  begin
    v = Vector.elements(Array.new(dimensions) {
                          random_centered_zero(resolution)
                        })
  end while v.zero?
  v.normalize
end

.revs(rads) ⇒ Object

convert radians to revolutions; works for alpha/omega/theta



15
16
17
# File 'lib/driving_physics/disk.rb', line 15

def self.revs(rads)
  rads / (2 * Math::PI)
end

.rot_90(vec, clockwise: true) ⇒ Object

Raises:

  • (Vector::ZeroVectorError)


32
33
34
35
36
# File 'lib/driving_physics/vector_force.rb', line 32

def self.rot_90(vec, clockwise: true)
  raise(Vector::ZeroVectorError) if vec.zero?
  raise(ArgumentError, "vec should be size 2") unless vec.size == 2
  clockwise ? Vector[vec[1], -1 * vec[0]] : Vector[-1 * vec[1], vec[0]]
end

.rpm(omega) ⇒ Object

convert omega to RPM (revs per minute)



30
31
32
# File 'lib/driving_physics/disk.rb', line 30

def self.rpm(omega)
  self.revs(omega) * 60
end

.torque_vector(force, radius) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/driving_physics/vector_force.rb', line 67

def self.torque_vector(force, radius)
  if !force.is_a?(Vector) or force.size != 2
    raise(ArgumentError, "force must be a 2D vector")
  end
  if !radius.is_a?(Vector) or radius.size != 2
    raise(ArgumentError, "radius must be a 2D vector")
  end
  force = Vector[force[0], force[1], 0]
  radius = Vector[radius[0], radius[1], 0]
  force.cross(radius)
end

.unit_interval!(val) ⇒ Object



73
74
75
76
77
78
# File 'lib/driving_physics.rb', line 73

def self.unit_interval!(val)
  if val < 0.0 or val > 1.0
    raise(ArgumentError, "val #{val.inspect} should be between 0 and 1")
  end
  val
end

.work(force, displacement) ⇒ Object



13
14
15
# File 'lib/driving_physics/power.rb', line 13

def self.work(force, displacement)
  force * displacement
end