running-tools

A collection of tools for runners and their coaches
git clone https://git.ashermorgan.net/running-tools/
Log | Files | Refs | README

calculators.ts (9250B)


      1 /*
      2  * Contains types and functions for core calculator functionality
      3  */
      4 
      5 import * as racePrediction from '@/core/racePrediction';
      6 import type { RacePredictionOptions } from '@/core/racePrediction';
      7 import { TargetTypes, workoutTargetToString } from '@/core/targets';
      8 import type { StandardTarget, WorkoutTarget } from '@/core/targets';
      9 import { DistanceUnits, UnitSystems, convertDistance, detectDefaultUnitSystem, formatDistance,
     10   formatDuration, formatPace, getDefaultDistanceUnit, getDefaultPaceUnit } from '@/core/units';
     11 import type { DistanceTime } from '@/core/units';
     12 
     13 /*
     14  * The four main calculators (batch and unit calculators not included)
     15  *
     16  * Used to determine available options and target set format
     17  */
     18 export enum Calculators {
     19   Pace = 'pace',
     20   Race = 'race',
     21   Split = 'split',
     22   Workout = 'workout',
     23 }
     24 
     25 /*
     26  * The type for the available race statistics
     27  */
     28 export interface RaceStats {
     29   purdyPoints: number,
     30   vo2Max: number,
     31   vo2: number,
     32   vo2MaxPercentage: number,
     33 };
     34 
     35 /*
     36  * The type for the options specific to each calculator
     37  */
     38 export interface GlobalOptions {
     39   defaultUnitSystem: UnitSystems,
     40   racePredictionOptions: RacePredictionOptions,
     41 };
     42 export interface SplitOptions {
     43   selectedTargetSet: string,
     44 };
     45 export interface PaceOptions extends SplitOptions {
     46   input: DistanceTime,
     47 };
     48 export type RaceOptions = PaceOptions;
     49 export interface WorkoutOptions extends PaceOptions {
     50   customTargetNames: boolean,
     51 };
     52 export interface BatchOptions {
     53   calculator: Calculators.Pace | Calculators.Race | Calculators.Workout,
     54   increment: number,
     55   input: DistanceTime,
     56   label: string,
     57   rows: number,
     58 };
     59 
     60 /*
     61  * The two possible result fields of a target result: "key" and "value"
     62  */
     63 export enum ResultType {
     64   Key = 'key',
     65   Value = 'value',
     66 };
     67 
     68 /*
     69  * The type for target results
     70  */
     71 export interface TargetResult {
     72   key: string,
     73   value: string,
     74   pace: string,
     75   result: ResultType,
     76   sort: number,
     77 };
     78 
     79 /*
     80  * The default input and options for each calculator
     81  */
     82 export const defaultGlobalOptions: GlobalOptions = {
     83   defaultUnitSystem: detectDefaultUnitSystem(),
     84   racePredictionOptions: racePrediction.defaultRacePredictionOptions,
     85 };
     86 export const defaultInput: DistanceTime = {
     87   distanceValue: 5,
     88   distanceUnit: DistanceUnits.Kilometers,
     89   time: 1200,
     90 };
     91 export const defaultBatchOptions: BatchOptions = {
     92   calculator: Calculators.Workout,
     93   increment: 15,
     94   input: defaultInput,
     95   label: '',
     96   rows: 20,
     97 };
     98 export const defaultPaceOptions: PaceOptions = {
     99   input: defaultInput,
    100   selectedTargetSet: '_pace_targets',
    101 };
    102 export const defaultRaceOptions: RaceOptions = {
    103   input: defaultInput,
    104   selectedTargetSet: '_race_targets',
    105 };
    106 export const defaultSplitOptions: SplitOptions = {
    107   selectedTargetSet: '_split_targets',
    108 };
    109 export const defaultWorkoutOptions: WorkoutOptions = {
    110   customTargetNames: false,
    111   input: defaultInput,
    112   selectedTargetSet: '_workout_targets',
    113 };
    114 
    115 /**
    116  * Calculate results for a standard target
    117  * @param {DistanceTime} input The input pace
    118  * @param {StandardTarget} target The standard target
    119  * @param {Function} calculateTime The function for calculating time results
    120  * @param {Function} calculateDistance The function for calculating distance results
    121  * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric)
    122  * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations
    123  * @returns {TargetResult} The result
    124  */
    125 function calculateStandardResult(input: DistanceTime, target: StandardTarget,
    126   calculateTime: (d1: number, t1: number, d2: number) => number,
    127   calculateDistance: (t1: number, d1: number, t2: number) => number, defaultUnitSystem: UnitSystems,
    128   preciseDurations: boolean = true): TargetResult {
    129 
    130   let distanceValue, distanceUnit, time;
    131   const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters);
    132   if (target.type === TargetTypes.Distance) {
    133     // Add target distance to result
    134     distanceValue = target.distanceValue;
    135     distanceUnit = target.distanceUnit;
    136 
    137     // Calculate time result
    138     const d2 = convertDistance(target.distanceValue, target.distanceUnit, DistanceUnits.Meters);
    139     time = calculateTime(d1, input.time, d2);
    140   } else {
    141     // Add target time to result
    142     time = target.time;
    143 
    144     // Calculate distance result
    145     const d2 = calculateDistance(input.time, d1, target.time);
    146     const units = getDefaultDistanceUnit(defaultUnitSystem);
    147     distanceValue = convertDistance(d2, DistanceUnits.Meters, units);
    148     distanceUnit = units;
    149   }
    150 
    151   return {
    152     // Convert distance to key string
    153     key: formatDistance({ distanceValue, distanceUnit }, target.type === TargetTypes.Time),
    154 
    155     // Convert time to time string
    156     value: formatDuration(time, 3, preciseDurations ? 2 : 0, target.type === TargetTypes.Distance),
    157 
    158     // Convert pace to pace string
    159     pace: formatPace({ time, distanceValue, distanceUnit }, getDefaultPaceUnit(defaultUnitSystem)),
    160 
    161     // Convert dist/time result to key/value
    162     result: target.type === TargetTypes.Distance ? ResultType.Value : ResultType.Key,
    163 
    164     // Use time (in seconds) as sort key
    165     sort: time,
    166   };
    167 }
    168 
    169 /**
    170  * Calculate paces from a target
    171  * @param {DistanceTime} input The input pace
    172  * @param {StandardTarget} target The pace target
    173  * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric)
    174  * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations
    175  * @returns {TargetResult} The result
    176  */
    177 export function calculatePaceResults(input: DistanceTime, target: StandardTarget,
    178                                      defaultUnitSystem: UnitSystems,
    179                                      preciseDurations: boolean): TargetResult {
    180 
    181   return calculateStandardResult(input, target, (d1, t1, d2) => ((t1 / d1) * d2),
    182     (t1, d1, t2) => ((d1 / t1) * t2), defaultUnitSystem, preciseDurations);
    183 }
    184 
    185 /**
    186  * Predict race results from a target
    187  * @param {DistanceTime} input The input race
    188  * @param {StandardTarget} target The race target
    189  * @param {RacePredictionOptions} racePredictionOptions The race prediction options
    190  * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric)
    191  * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations
    192  * @returns {TargetResult} The result
    193  */
    194 export function calculateRaceResults(input: DistanceTime, target: StandardTarget,
    195                                      racePredictionOptions: RacePredictionOptions,
    196                                      defaultUnitSystem: UnitSystems, preciseDurations: boolean
    197                                     ): TargetResult {
    198 
    199   return calculateStandardResult(input, target,
    200     (d1, t1, d2) => racePrediction.predictTime(d1, t1, d2, racePredictionOptions),
    201     (t1, d1, t2) => racePrediction.predictDistance(t1, d1, t2, racePredictionOptions),
    202     defaultUnitSystem, preciseDurations);
    203 }
    204 
    205 /**
    206  * Calculate race statistics from an input race
    207  * @param {DistanceTime} input The input race
    208  * @returns {RaceStats} The race statistics
    209  */
    210 export function calculateRaceStats(input: DistanceTime): RaceStats {
    211   const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters);
    212 
    213   return {
    214     purdyPoints: racePrediction.getPurdyPoints(d1, input.time),
    215     vo2Max: racePrediction.getVO2Max(d1, input.time),
    216     vo2: racePrediction.getVO2(d1, input.time),
    217     vo2MaxPercentage: racePrediction.getVO2Percentage(input.time) * 100,
    218   }
    219 }
    220 
    221 /**
    222  * Predict workout results from a target
    223  * @param {DistanceTime} input The input race
    224  * @param {WorkoutTarget} target The workout target
    225  * @param {RacePredictionOptions} racePredictionOptions The race prediction options
    226  * @param {Boolean} customTargetNames Whether to use custom target names
    227  * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations
    228  * @returns {TargetResult} The result
    229  */
    230 export function calculateWorkoutResults(input: DistanceTime, target: WorkoutTarget,
    231                                         racePredictionOptions: RacePredictionOptions,
    232                                         customTargetNames: boolean, preciseDurations: boolean
    233                                        ): TargetResult {
    234   // Initialize distance and time variables
    235   const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters);
    236   const t1 = input.time;
    237   const d3 = convertDistance(target.splitValue, target.splitUnit, DistanceUnits.Meters);
    238   let d2, t2;
    239 
    240   // Calculate result
    241   if (target.type === 'distance') {
    242     // Convert target distance into meters
    243     d2 = convertDistance(target.distanceValue, target.distanceUnit, DistanceUnits.Meters);
    244 
    245     // Get workout split prediction
    246     t2 = racePrediction.predictTime(d1, input.time, d2, racePredictionOptions);
    247   } else {
    248     t2 = target.time;
    249 
    250     // Get workout split prediction
    251     d2 = racePrediction.predictDistance(t1, d1, t2, racePredictionOptions);
    252   }
    253   const t3 = (t2 / d2) * d3;
    254 
    255   // Return result
    256   return {
    257     key: (customTargetNames && target.customName) || workoutTargetToString(target),
    258     value: formatDuration(t3, 3, preciseDurations ? 2 : 0, true),
    259     pace: '', // Pace not used in workout calculator
    260     result: ResultType.Value,
    261     sort: t3,
    262   }
    263 }