Module: TrainingZones

Included in:
Calcpace
Defined in:
lib/calcpace/training_zones.rb

Overview

Module for deriving personalized training zones from VO2max

Training paces invert the Daniels & Gilbert (1979) velocity equation:

VO2 = -4.60 + 0.182258 * v + 0.000104 * v²   (v in m/min)

Solving the quadratic for v at a target %VO2max gives the running velocity for each training intensity (Daniels' Running Formula).

Heart rate zones use the Karvonen method (Heart Rate Reserve):

target = hr_rest + pct * (hr_max - hr_rest)

Defined Under Namespace

Classes: HrZone, PaceBand

Constant Summary collapse

TRAINING_INTENSITIES =

Training intensities as fraction of VO2max (Daniels' Running Formula)

{
  easy: { low: 0.59, high: 0.74 },
  marathon: { low: 0.75, high: 0.84 },
  threshold: { low: 0.83, high: 0.88 },
  interval: { low: 0.95, high: 1.00 },
  repetition: { low: 1.05, high: 1.10 }
}.freeze
PACE_UNIT_METERS =

Metres per pace unit — pace bands can be expressed per km or per mile

{ km: 1000.0, mi: Converter::Distance::MI_TO_METERS }.freeze
HR_ZONE_BOUNDARIES =

Heart-rate zone boundaries as fractions of the range being split: Heart Rate Reserve in #hr_zones (Karvonen) and HRmax in #hr_zones_from_max

[0.50, 0.60, 0.70, 0.80, 0.90, 1.00].freeze

Instance Method Summary collapse

Instance Method Details

#hr_zones(hr_max:, hr_rest:) ⇒ Array<HrZone>

Computes the five Karvonen heart-rate training zones

target_bpm = hr_rest + pct * (hr_max - hr_rest)

Examples:

calc.hr_zones(hr_max: 190, hr_rest: 55).last.max_bpm #=> 190

Parameters:

  • hr_max (Numeric)

    maximum heart rate in bpm (must be > 0)

  • hr_rest (Numeric)

    resting heart rate in bpm (must be > 0 and < hr_max)

Returns:

  • (Array<HrZone>)

    five contiguous zones from Z1 (50–60% HRR) to Z5 (90–100% HRR)

Raises:



117
118
119
120
121
122
123
124
# File 'lib/calcpace/training_zones.rb', line 117

def hr_zones(hr_max:, hr_rest:)
  max  = hr_max.to_f
  rest = hr_rest.to_f
  check_heart_rates(max, rest)

  reserve = max - rest
  build_hr_zones(HR_ZONE_BOUNDARIES.map { |pct| (rest + (pct * reserve)).round })
end

#hr_zones_from_max(hr_max:) ⇒ Array<HrZone>

Computes five heart-rate zones from maximum heart rate only (%HRmax method)

Fallback for athletes who don't know their resting heart rate — the classic percent-of-max model used as default by most sports watches:

target_bpm = pct * hr_max

Prefer #hr_zones (Karvonen) when resting heart rate is available.

Examples:

calc.hr_zones_from_max(hr_max: 190).first.min_bpm #=> 95

Parameters:

  • hr_max (Numeric)

    maximum heart rate in bpm (must be > 0)

Returns:

  • (Array<HrZone>)

    five contiguous zones from Z1 (50–60% HRmax) to Z5 (90–100% HRmax)

Raises:



140
141
142
143
144
145
# File 'lib/calcpace/training_zones.rb', line 140

def hr_zones_from_max(hr_max:)
  max = hr_max.to_f
  check_positive(max, 'Maximum heart rate')

  build_hr_zones(HR_ZONE_BOUNDARIES.map { |pct| (pct * max).round })
end

#training_paces(vo2max, unit: :km) ⇒ Hash{Symbol => PaceBand}

Derives training pace bands from a VO2max value

Examples:

calc.training_paces(50.0)[:threshold].fast_clock             #=> "00:04:15"
calc.training_paces(50.0, unit: :mi)[:threshold].fast_clock  #=> "00:06:51"

Parameters:

  • vo2max (Numeric)

    VO2max in ml/kg/min (must be > 0)

  • unit (Symbol) (defaults to: :km)

    pace unit — :km (default) or :mi

Returns:

  • (Hash{Symbol => PaceBand})

    keys: :easy, :marathon, :threshold, :interval, :repetition — paces per chosen unit

Raises:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/calcpace/training_zones.rb', line 48

def training_paces(vo2max, unit: :km)
  check_positive(vo2max.to_f, 'VO2max')
  meters = pace_unit_meters(unit)

  TRAINING_INTENSITIES.transform_values do |band|
    slow = pace_seconds_at_pct(vo2max.to_f, band[:low], meters)
    fast = pace_seconds_at_pct(vo2max.to_f, band[:high], meters)

    PaceBand.new(
      slow_seconds: slow,
      fast_seconds: fast,
      slow_clock: convert_to_clocktime(slow),
      fast_clock: convert_to_clocktime(fast)
    )
  end
end

#training_paces_from_race(race, time, unit: :km, distance_unit: nil) ⇒ Hash{Symbol => PaceBand}

Derives training pace bands from a recent race result

Convenience wrapper: estimates VO2max via Daniels & Gilbert (see Vo2maxEstimator#estimate_vo2max) and derives the bands from it.

Examples:

calc.training_paces_from_race(10.0, '00:40:00')[:easy].slow_clock #=> "00:05:42"
calc.training_paces_from_race('5mile', '00:35:00', unit: :mi)
calc.training_paces_from_race(6.2, '00:40:00', distance_unit: :mi, unit: :mi)

Parameters:

  • race (Numeric, String, Symbol)

    race distance in kilometres (or in miles via distance_unit: :mi), either numeric or as a numeric string ('10', '21.0975'), or a standard race name ('5k', '10k', 'half_marathon', 'marathon', '1mile', '5mile', '10mile', '100k' — see PaceCalculator::RACE_DISTANCES)

  • time (String, Integer)

    finish time as "HH:MM:SS" / "MM:SS" or total seconds

  • unit (Symbol) (defaults to: :km)

    pace unit of the output bands — :km (default) or :mi

  • distance_unit (Symbol) (defaults to: nil)

    unit of a numeric race distance input — :km (default) or :mi. Rejected when race is a race name: standard races already carry their own distance, so the combination is always a caller mistake

Returns:

  • (Hash{Symbol => PaceBand})

    same shape as #training_paces

Raises:



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/calcpace/training_zones.rb', line 91

def training_paces_from_race(race, time, unit: :km, distance_unit: nil)
  # Numeric strings ('10') keep working as distances; only non-numeric input
  # (race names) falls through to the RACE_DISTANCES lookup
  numeric = race.is_a?(Numeric) ? race : Float(race, exception: false)
  distance_km = if numeric
                  normalize_distance_km(numeric, distance_unit || :km)
                else
                  reject_distance_unit_with_race_name!(distance_unit, race)
                  race_distance(race)
                end

  training_paces(estimate_vo2max(distance_km, time), unit: unit)
end