Created
March 3, 2021 01:04
-
-
Save msepcot/9dc92f98180b4e288fa0542688fd2839 to your computer and use it in GitHub Desktop.
Exploring different marathon race time calculations.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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