Module: ClimbFactor
- Defined in:
- lib/climb_factor/physiology.rb,
lib/climb_factor.rb,
lib/climb_factor/geometry.rb,
lib/climb_factor/filtering.rb,
lib/climb_factor/low_level_math.rb
Overview
For the cr and cw functions, see Minetti, jap.physiology.org/content/93/3/1039.full
Defined Under Namespace
Modules: CfMath, Filtering, Geom, Phys
Class Method Summary collapse
- .estimate(hv, nominal_distance: nil, filtering: 200.0) ⇒ Object
- .integrate_gain_and_energy(hv, rescale, body_mass: 1.0) ⇒ Object
Class Method Details
.estimate(hv, nominal_distance: nil, filtering: 200.0) ⇒ Object
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/climb_factor.rb', line 6 def self.estimate(hv,nominal_distance:nil,filtering:200.0) # inputs: # All inputs are in units of meters. # hv = list of [horizontal,vertical] coordinate pairs # nominal_distance = if supplied, scale horizontal length of course to equal this value # filtering = get rid of bogus fluctuations in vertical data that occur on horizontal scales less than this # output: # cf = a floating-point number representing a climb factor, expressed as a percentage hv = hv.map do |a| unless a.all? { |v| v.is_a?(Numeric) } raise ArgumentError, "error in type of input, got #{a.inspect}, should be pairs of Floats" end a.map(&:to_f) end hv = Filtering.resample_and_filter_hv(hv,filtering) rescale = 1.0 if !nominal_distance.nil? then rescale = nominal_distance/(hv.last[0]-hv[0][0]) end stats,hv = integrate_gain_and_energy(hv,rescale) c,h = stats['c'],stats['h'] # energy cost in joules and (possibly rescaled) horiz distance in meters cf = 100.0*(c-h*Phys.minetti(0.0))/c return cf end |
.integrate_gain_and_energy(hv, rescale, body_mass: 1.0) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/climb_factor.rb', line 31 def self.integrate_gain_and_energy(hv,rescale,body_mass:1.0) # integrate to find total gain, slope distance, and energy burned # returns [stats,hv], where: # stats = {'c'=>c,'d'=>d,'gain'=>gain,'i_rms'=>i_rms,...} # hv = modified copy of input hv, with predicted times added, if we have the necessary data v = 0 # total vertical distance (=0 at end of a loop) d = 0 # total distance along the slope gain = 0 # total gain c = 0 # cost in joules first = true old_h = 0 old_v = 0 i_sum = 0.0 i_sum_sq = 0.0 iota_sum = 0.0 iota_sum_sq = 0.0 baumel_si = 0.0 # compute this directly as a check t = 0.0 # integrated time, in seconds h_reintegrated = 0.0 # if rescale!=1, this should be the same as nominal_h k = 0 hv.each { |a| h,v = a if !first then dh = (h-old_h)*rescale dv = v-old_v dd = Math::sqrt(dh*dh+dv*dv) h_reintegrated = h_reintegrated+dh d = d+dd if dv>0 then gain=gain+dv end i=0 if dh>0 then i=dv/dh end if dh>0 then baumel_si=baumel_si+dv**2/dh end # In the following, weight by dh, although normally this doesn't matter because we make the # h intervals constant before this point. i_sum = i_sum + i*dh i_sum_sq = i_sum_sq + i*i*dh dc = dd*body_mass*Phys.minetti(i) # in theory it matters whether we use dd or dh here; I think from Minetti's math it's dd c = c+dc #if not ($split_energy_at.nil?) and d-dd<$split_energy_at_m and d>$split_energy_at_m then # $stderr.print "at d=#{$split_energy_at}, energy=#{(c*0.0002388459).round} kcals\n" # # fixme -- implement this in a better way #end k=k+1 end old_h = h old_v = v first = false } n = hv.length-1.0 h = h_reintegrated # should equal $nominal_h; may differ from hv.last[0]-hv[0][0] if rescale!=1 i_rms = Math::sqrt(i_sum_sq/h - (i_sum/h)**2) i_mean = (hv.last[1]-hv[0][1])/h i0,c0,c2,b0,b1,b2 = Phys.minetti_quadratic_coeffs() e_q = h*body_mass*(b0+b1*i_mean+b2*i_rms) cf = (c-h*body_mass*Phys.minetti(0.0))/c stats = {'c'=>c,'h'=>h,'d'=>d,'gain'=>gain,'i_rms'=>i_rms,'i_mean'=>i_mean,'e_q'=>e_q, 'cf'=>cf,'baumel_si'=>baumel_si, 't'=>t} return [stats,hv] end |