Pace Effectivity Metric

172 views
Skip to first unread message

H R

unread,
Jun 20, 2024, 11:20:27 PMJun 20
to golden-cheetah-users
I am thinking about developing a pace effectivity metric for mountain/hill time trials.
GC has an efficiency index that is average speed by average power but I am not sure about the exact formula. I don't think that it captures what I have in mind.

What I would like the metric to account for is:
- Physiological cost of effort is measured in TSS (perhaps altitude adjusted)
- Gain is measured by the speed (km/h).
- The relationship between speed and power depends on the slope.
- The effectivity is calculated given a fixed amount of stress spent for the interval.

Does anybody know whether I am just reinventing the wheel? Has this been done before?

If not, here are some further thoughts I have on this.

At any point in time, marginal gain should equate marginal cost:
dSpeed(power)/dpower = dStress(power)/dpower * lambda
Where lambda is the associated Lagrange multiplier of the stress constraint.

The relationship between power and speed has a linear component (slope) and a cubic component (air resistance). The relationship between power and stress is to the fourth power. Turning the calculus crank a bit should allow us to obtain for every point in time the marginal speed divided by the marginal stress, i.e., a value of the implied Lagrange multiplier lambda(t). Since the Lagrange multiplier should be identical at all points in time, this provides a basis to evaluate the effectiveness of the pacing strategy.

There are a few things we could do with this. We could simply look at the variance of lambda(t). The higher the variance, the worse the pacing strategy. Plotting it against the slope tells you about whether you should go harder or easier on climbs next time. Plotting it over time might show a positive/negative split strategy.

There is of course an issue with braking. If on a descent or at a traffic light you need to brake and stop applying power, the implied lambda(t) goes up but does not indicate inefficient pacing.

I am curious to hear your thoughts. Let me know if I am terribly mistaken in some aspect of this or if this has been done before and is just one click away...

Ale Martinez

unread,
Jun 21, 2024, 1:18:05 PMJun 21
to golden-cheetah-users
El viernes, 21 de junio de 2024 a la(s) 12:20:27 a.m. UTC-3, H R escribió:
I am thinking about developing a pace effectivity metric for mountain/hill time trials.
GC has an efficiency index that is average speed by average power but I am not sure about the exact formula. I don't think that it captures what I have in mind.

Definition is in glossary and full details in source code, if you are curious.

What I would like the metric to account for is:
- Physiological cost of effort is measured in TSS (perhaps altitude adjusted)

Which formula for TSS do you have in mind? GoldenCheetah doesn't compute it for runs using power due to the lack of a published one.
 
- Gain is measured by the speed (km/h).
- The relationship between speed and power depends on the slope.
- The effectivity is calculated given a fixed amount of stress spent for the interval.

Does anybody know whether I am just reinventing the wheel? Has this been done before?

If not, here are some further thoughts I have on this.

I am not sure I understand what you are trying to accomplish, but perhaps some kind of speed normalized by grade vs power normalized by intensity and variability may help. 

pepe

unread,
Jun 21, 2024, 7:07:50 PMJun 21
to golden-cheetah-users

Take a look at Run Efficiency Combined Hill and Wind Effect

{

relevant { isRun && Data contains "P" && Data contains "L" && Data contains "S" && XDATA("DEVELOPER", "AIR*POWER", sparse); }


# value { (((((Average_Speed/3.6)/(Average_Power/Athlete_Weight))/((16.667/xPace)/(Average_Power/Athlete_Weight)))-1) + (((Average_Speed /3.6)/(Average_Power /Athlete_Weight)) / ((Average_Speed/3.6)/((Average_Power - mean(xdata("DEVELOPER", "Air Power")))/Athlete_Weight))-1))*100;}



value { (((((Average_Speed/3.6)/( Average_Power/Athlete_Weight ))/(mean(xdata("EXTRA", "GAP"))/(Average_Power/Athlete_Weight )))-1) + ((((Average_Speed /3.6)/(Average_Power/Athlete_Weight ) )

/ ((Average_Speed/3.6)/( (Average_Power - mean(xdata("DEVELOPER", "Air Power")))/Athlete_Weight)))-1))*100;}

pepe

unread,
Jun 21, 2024, 7:47:52 PMJun 21
to golden-cheetah-users
Correction:  Run Effectiveness Combined Hill and Wind Effect

Ale Martinez

unread,
Jun 21, 2024, 9:04:47 PMJun 21
to golden-cheetah-users
If you want to use the 4-Norm for power (aka "normalized power") instead of Average Power, it can be computed as:

mean(smooth(samples(POWER), sma, backward, 30)^4)^(1/4)

but, since run "power" tends to be heavily filtered by the "measuring" devices, the smoothing part is likely unnecessary.

H R

unread,
Jun 21, 2024, 9:08:07 PMJun 21
to golden-cheetah-users
Thank you for your ideas. To answer Ale's question, I think I am looking for pretty much the metric that Pepe suggested but for cycling.

I now crunched the calculus a bit and came up with the following metric:

1/mu = (c1(t) + c2 V(t)^2) 2 P(t)^3/NP
where
- V(t) is the speed at time t (perhaps smoothed)
- P(t) is the power at time t (perhaps smoothed)
- NP is the normalized power over the entire interval/activity and can be replaced by xPower or even omitted
- c1(t) = m g (sin(theta(t)) + Cr rho)
- c2 = 3/2 rho CdA
- m is the mass of rider + bicycle
- g is the gravitational constant
- theta(t) is the slope at time t
- rho is the air density
- CdA is the drag coefficient times frontal area

Explanation: To obtain the formula, I maximized the average steady state speed of a cyclist given a constraint on TSS (calculated as (integral P(t)^4dt )^2/FTP).
I accounted for rolling resistance, air resistance, and slope. The solution to the optimization problem yields a relation between current speed and power that should be constant over time.
mu is the Lagrange multiplier (shadow price of TSS) of the optimization problem. Since the implied mu becomes infinite at zero power, it is better to use 1/mu as the metric.
1/mu has units stress/speed (TSS/(km/h)) since the Lagrange multiplier converts the stress constraint into speed.

Comparative statics: If you want to hold 1/mu constant, as your speed increases you want to reduce power. If the slope increases, you should decrease speed. I haven't checked whether you should also increase power (which would make sense).

Ale Martinez

unread,
Jun 22, 2024, 8:21:58 AMJun 22
to golden-cheetah-users
El viernes, 21 de junio de 2024 a la(s) 10:08:07 p.m. UTC-3, H R escribió:
Thank you for your ideas. To answer Ale's question, I think I am looking for pretty much the metric that Pepe suggested but for cycling.

I now crunched the calculus a bit and came up with the following metric:

1/mu = (c1(t) + c2 V(t)^2) 2 P(t)^3/NP
where
- V(t) is the speed at time t (perhaps smoothed)
- P(t) is the power at time t (perhaps smoothed)
- NP is the normalized power over the entire interval/activity and can be replaced by xPower or even omitted
- c1(t) = m g (sin(theta(t)) + Cr rho)
- c2 = 3/2 rho CdA
- m is the mass of rider + bicycle
- g is the gravitational constant
- theta(t) is the slope at time t
- rho is the air density
- CdA is the drag coefficient times frontal area

Explanation: To obtain the formula, I maximized the average steady state speed of a cyclist given a constraint on TSS (calculated as (integral P(t)^4dt )^2/FTP).
I accounted for rolling resistance, air resistance, and slope. The solution to the optimization problem yields a relation between current speed and power that should be constant over time.
mu is the Lagrange multiplier (shadow price of TSS) of the optimization problem. Since the implied mu becomes infinite at zero power, it is better to use 1/mu as the metric.
1/mu has units stress/speed (TSS/(km/h)) since the Lagrange multiplier converts the stress constraint into speed.

Comparative statics: If you want to hold 1/mu constant, as your speed increases you want to reduce power. If the slope increases, you should decrease speed. I haven't checked whether you should also increase power (which would make sense).

We thought you were talking about running since efficiency index is a running only metric in GoldenCheetah, so let’s start again…
Long time ago I played with the optimization problem of minimizing time for a course with an NP or TSS constrain breaking the course in segments of constant slope or wind, and the result was not constant power but higher power on slower segments, which is intuitively correct, IMHO.
If you search wattage list you can find discussions about this around 2006-2008, IIRC.

Ale Martinez

unread,
Jun 22, 2024, 1:48:03 PMJun 22
to golden-cheetah-users
PS: your formula above can be implemented in GoldenCheetah formula language as a time series or a metric once you define how to aggregate the values, NP is available as IsoPower for the interval/activity depending on the context it is used. 

H R

unread,
Jun 22, 2024, 11:46:04 PMJun 22
to golden-cheetah-users
Ale and Pepe, thank you for all the hints! Sorry for not being clear about the cycling vs running metric.

I revised the formula a bit (minimization of time given fixed course and optimizing over the segment speeds with a fixed gradient).

Here the new formula:
1/mu = Power(t)^3 * (4(c1 Speed(t) + 3 c2 Speed(t)^3)-Power(t)) / (72*NP^2*FTP)
where c1 = Total Weight * Grav. Acc. * (Cr + sin(atan(slope))) and c2 = 1/2 rho CdA

The pacing strategy can be obtained by calculating 1/mu for the average gradient (let's say 10%) and target Power=NP (let's say 280W). This gives the target 1/mu to hold for all gradients.
It works fairly well at first sight, for example at a system weight of 78kg in an all-out time trial at 280W for one hour with an average gradient of 10% it roughly suggests the following pacing strategy: 16%: 296W, 10%: 280W, 5%: 240W, 0%: 162W

There are still some theoretical issues. I am not sure whether TSS is really the right constraint of effort to use. Clearly over a short course W'bal is the right constraint. I am also unsure about how meaningful TSS are over long efforts as a constraint.

I will now go ahead and implement it in GC as a time series and share the code then!

H R

unread,
Jun 23, 2024, 8:47:23 AMJun 23
to golden-cheetah-users

I have more or less managed to create a script that creates the xdata time series. It works for some activities but crashes GC for other activities. Probably I need to include some further checks on data length and availability, etc..

Any comments are very welcome! I am also thinking about adding some smoothing after fixing the bugs. Just what smoothing would work well here? The steeper the hill, the lower the effect of past power/speed on current speed, suggesting slope-dependent smoothing of speed -- that would be a mess.

""" Script to estimate a hilly time trial pacing metric. TO DO: - Extract crr, FTP, bike weight from data - Calculate rho from elevation + weather """ from scipy.constants import g from math import sin, atan import numpy as np activity = GC.activity() athlete = GC.athlete() zones = GC.athleteZones() metrics = GC.activityMetrics() p = 'power' in activity.keys() s = 'speed' in activity.keys() l = 'slope' in activity.keys() if p and s and l: # Aerodynamic parameters rho = 1.22601 cda = 0.321 # Rolling resistance parameters crr = 0.005 # Gravitation parameters # Activity metrics xpower = metrics['xPower'] cp = metrics['CP_setting'] weight = metrics['Athlete_Weight'] # Activity data power = np.array(activity['power']) speed = np.array(activity['speed'])/3.6 #Speed in SI units slope = np.array(activity['slope']) c1 = (np.sin(np.arctan(slope)) + crr) * weight * g c2 = rho * cda / 2 muraw = (power ** 3) * (4 * (c1 * speed + 3 * c2 * (speed ** 3)) - power) mu = muraw / (xpower ** 2) / 72 / cp # Create xdata series GC.createXDataSeries("PACING", "MURAW", "Watt^4/s") GC.createXDataSeries("PACING", "MU", "Stress/h") GC.createXDataSeries("PACING", "secs", "seconds") for t in range(0,len(power)): GC.xdataSeries("PACING", "secs").append(t) GC.xdataSeries("PACING", "MURAW")[t] = muraw[t] GC.xdataSeries("PACING", "MU")[t] = mu[t]

Ale Martinez

unread,
Jun 23, 2024, 10:17:57 AMJun 23
to golden-cheetah-users
El domingo, 23 de junio de 2024 a la(s) 12:46:04 a.m. UTC-3, H R escribió:
Ale and Pepe, thank you for all the hints! Sorry for not being clear about the cycling vs running metric.

I revised the formula a bit (minimization of time given fixed course and optimizing over the segment speeds with a fixed gradient).

Here the new formula:
1/mu = Power(t)^3 * (4(c1 Speed(t) + 3 c2 Speed(t)^3)-Power(t)) / (72*NP^2*FTP)
where c1 = Total Weight * Grav. Acc. * (Cr + sin(atan(slope))) and c2 = 1/2 rho CdA

The pacing strategy can be obtained by calculating 1/mu for the average gradient (let's say 10%) and target Power=NP (let's say 280W). This gives the target 1/mu to hold for all gradients.
It works fairly well at first sight, for example at a system weight of 78kg in an all-out time trial at 280W for one hour with an average gradient of 10% it roughly suggests the following pacing strategy: 16%: 296W, 10%: 280W, 5%: 240W, 0%: 162W

There are still some theoretical issues. I am not sure whether TSS is really the right constraint of effort to use. Clearly over a short course W'bal is the right constraint. I am also unsure about how meaningful TSS are over long efforts as a constraint.

An alternative constrain I found useful is NP<=RiegelPower(T), where RiegelPower(T) is the power for duration T using the power law model, s.t. FTP*(T/TTE)^(E-1), with modeled or hand-waving-estimated parameters. 

Ale Martinez

unread,
Jun 23, 2024, 6:58:23 PMJun 23
to golden-cheetah-users
El domingo, 23 de junio de 2024 a la(s) 9:47:23 a.m. UTC-3, H R escribió:

Any comments are very welcome!

If your objective is to create a metric, but you want to start with a time series, my suggestion would be to create a User Chart, it would likely be easier and more efficient.

H R

unread,
Jun 23, 2024, 10:43:11 PMJun 23
to golden-cheetah-users
I figured out that using RiegelPower (or any other constraint on NP) yields the same time series as mu above up to a factor of NP/FTP. That's reassuring if RiegelPower is something people have used in the past.

The next goal is indeed to plot it as a user chart to get a feel for what the model says about some paced activities. Then I will build a Connect IQ data field to try using it in real time.

I still cannot figure out why the code does not work sometimes (even on activities it did work on before). I am using the python that is embedded in GC and have all the checks on data availability added. Still, sometimes GC crashes when running the code. I read the "working with python" special topics manual and don't see its recommendations conflicting with the code.

Bests,
Hendrik

Ale Martinez

unread,
Jun 24, 2024, 7:31:33 AMJun 24
to golden-cheetah-users
El domingo, 23 de junio de 2024 a la(s) 11:43:11 p.m. UTC-3, H R escribió:
I figured out that using RiegelPower (or any other constraint on NP) yields the same time series as mu above up to a factor of NP/FTP. That's reassuring if RiegelPower is something people have used in the past.

That makes sense to me, provided the constrain is global it will likely not change the relative pacing but the absolute watts and final time. 

The next goal is indeed to plot it as a user chart to get a feel for what the model says about some paced activities. Then I will build a Connect IQ data field to try using it in real time.

It seems a good plan 
 
I still cannot figure out why the code does not work sometimes (even on activities it did work on before). I am using the python that is embedded in GC and have all the checks on data availability added. Still, sometimes GC crashes when running the code. I read the "working with python" special topics manual and don't see its recommendations conflicting with the code.

There is a wiki entry about crash diagnosis in the troubleshooting section which may help.
Reply all
Reply to author
Forward
0 new messages