Skip to content

Instantly share code, notes, and snippets.

@msepcot
Created March 3, 2021 01:04
Show Gist options
  • Save msepcot/9dc92f98180b4e288fa0542688fd2839 to your computer and use it in GitHub Desktop.
Save msepcot/9dc92f98180b4e288fa0542688fd2839 to your computer and use it in GitHub Desktop.
Exploring different marathon race time calculations.
import Foundation
class Calculator {
class Constants {
static let kilometersPerMile = 1.60934
static let metersPerMile = 1609.34
}
enum Distance: Double { // rawValue in miles
case marathon = 26.21875
case halfMarathon = 13.109375
case tenMile = 10
case tenKilometer = 6.21371192
case fiveMile = 5
case fiveKilometer = 3.10685596
case threeKilometer = 1.864113576
case oneMile = 1
case fifteenHundredMeter = 0.932056788
func miles() -> Double {
rawValue
}
func kilometers() -> Double {
rawValue * Constants.kilometersPerMile
}
func meters() -> Double {
rawValue * Constants.metersPerMile
}
}
struct Race {
let distance: Distance
let time: TimeInterval
init(distance: Distance, time: TimeInterval) {
self.distance = distance
self.time = time
}
init(distance: Distance, hours: Int, minutes: Int, seconds: Int) {
self.distance = distance
self.time = TimeInterval(seconds + minutes * 60 + hours * 3600)
}
func pace() -> Double { // seconds per mile
return Double(time) / distance.rawValue
}
var debugDescription: String {
"<Race distance=\(distance) time=\(time) pace=\(String(format: "%i:%02i/mi", Int(pace()) / 60, Int(pace()) % 60))>"
}
}
// Vickers, A.J., Vertosick, E.A.: An empirical study of race times in recreational endurance runners. BMC Sports Science, Medicine and Rehabilitation 8(26) (2016)
class func vickersVertosick(recentRace: Race, milesPerWeek: Int) -> Double {
let velocityRiegel = Distance.marathon.meters() / Calculator.riegel(recentRace: recentRace, exponent: 1.07)
let velocityModel = 0.16018617 + 0.83076202 * velocityRiegel + 0.06423826 * Double(milesPerWeek) / 10.0
return Distance.marathon.meters() / velocityModel
}
class func vickersVertosick(shorterRace: Race, longerRace: Race, milesPerWeek: Int) -> Double {
let k_r2r1 = log(longerRace.time / shorterRace.time) / log(longerRace.distance.meters() / shorterRace.distance.meters())
let k_marathon = 1.4510756 + -0.23797948 * k_r2r1 + -0.01410023 * Double(milesPerWeek) / 10.0
return Calculator.riegel(recentRace: longerRace, exponent: k_marathon)
}
// In 1977, Pete Riegel devised a formula for predicting race times, based on your existing performances. The basic formula is t2 = t1 * (d2 / d1)^1.06, but the "1.06" can be improved upon.
class func riegel(recentRace: Race, exponent: Double = 1.06) -> Double {
recentRace.time * pow((Distance.marathon.rawValue / recentRace.distance.rawValue), exponent)
}
class func improvedRiegel(recentRace: Race) -> Double {
Calculator.riegel(recentRace: recentRace, exponent: 1.15)
}
class func tanda() -> Double {
// https://rua.ua.es/dspace/bitstream/10045/18930/1/jhse_Vol_VI_N_III_511-520.pdf
// Enter your average weekly distance and pace in the eight weeks prior to your marathon (excluding race week)
// Pm (sec/km) = 17.1 + 140.0 exp[-0.0053 K(km/week)] + 0.55 P (sec/km)
// mean distance run per week K
// mean training pace P
// This study collected training data of twenty-two runners (twenty-one males, one female), age 28-54 years for a five-year period, over which they run 46 marathons, with MPT ranging from 167 min to 216 min (2h 47min to 3h 36min)
// The reference, 8-week, period started 9 weeks before the race and finished seven days prior to the race.
// March 24th - Oakland Marathon - 3:43:40 - 13420 sec
let k = 50.292
// 8 weeks leading: 33, 37, 33, 41, 37, 31, 24, 14 -> average 31.25mi == 50.292km
let p = 321.9487491767
// 8 weeks leading: 8'30", 8'21", 8'51", 8'34", 8'45", 8'54", 8'21", 8'49" -> 510, 501, 531, 514, 525, 534, 501, 529 -> average 518.125 sec/mi == 321.9487491767 sec/km
let predicted_sec_per_km = 17.1 + 140.0 * pow(2.71828, -0.0053 * k) + 0.55 * p
// 301.4145669211176 sec/km == 485.0785191288 sec/mi == 8'05" / mi
return predicted_sec_per_km * Distance.marathon.kilometers() // 12718 sec estimate (3:31:58); 13420 sec actual (3:43:40)
}
}
func hhmmss(_ time: TimeInterval) -> String {
let time = Int(time)
let hours = time / 3600
let seconds = time % 60
let minutes = (time - (hours * 3600)) / 60
return String(format: "%02i:%02i:%02i", hours, minutes, seconds)
}
// let oakland2019 = Calculator.Race(distance: .marathon, hours: 3, minutes: 43, seconds: 40) // March 2019
let surfersPoint2018 = Calculator.Race(distance: .halfMarathon, hours: 1, minutes: 41, seconds: 46) // November 2018
let spiritRun2018 = Calculator.Race(distance: .tenKilometer, hours: 0, minutes: 46, seconds: 19) // September 2018
hhmmss(Calculator.vickersVertosick(recentRace: surfersPoint2018, milesPerWeek: 40))
hhmmss(Calculator.vickersVertosick(shorterRace: spiritRun2018, longerRace: surfersPoint2018, milesPerWeek: 40))
hhmmss(Calculator.improvedRiegel(recentRace: surfersPoint2018))
hhmmss(Calculator.improvedRiegel(recentRace: spiritRun2018))
hhmmss(Calculator.riegel(recentRace: surfersPoint2018))
hhmmss(Calculator.riegel(recentRace: spiritRun2018))
hhmmss(Calculator.tanda())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment