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
-
.acc(force, mass) ⇒ Object
acceleration; F=ma force can be a scalar or a Vector.
-
.accum(init, rate, dt: TICK) ⇒ Object
(also: 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.
-
.compass_dir(unit_vector) ⇒ Object
,0 E 0, N .9,.1 ENE .1,.9 NNE.
-
.elapsed_display(elapsed_ms) ⇒ Object
HH::MM::SS.mmm.
- .force_vector(torque, radius) ⇒ Object
-
.has_vector? ⇒ Boolean
runtime check; this returns false by default Vector is not currently/easily available in mruby.
- .interpolate(x, xs:, ys:) ⇒ Object
- .kph(meters_per_sec) ⇒ Object
- .power(force, speed) ⇒ Object
-
.rads(revs) ⇒ Object
convert revs to rads; works for alpha/omega/theta.
-
.random_centered_zero(magnitude) ⇒ Object
e.g.
- .random_unit_vector(dimensions = 2, resolution: 9) ⇒ Object
-
.revs(rads) ⇒ Object
convert radians to revolutions; works for alpha/omega/theta.
- .rot_90(vec, clockwise: true) ⇒ Object
-
.rpm(omega) ⇒ Object
convert omega to RPM (revs per minute).
- .torque_vector(force, radius) ⇒ Object
- .unit_interval!(val) ⇒ Object
- .work(force, displacement) ⇒ Object
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
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
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 |