Allow users to choose alternatives to TSS

You don’t have to convince us, most of the advanced users already know that.
But I assure you that as soon as you introduce a different way of calculating TSS as a default one, the forum will be flooded with ‘Bug Reports’. Simply because it deviates from the big players (TP, WKO, Strava…) who are ‘considered’ the only source of truth. It off course has to do with the fact that elite’s have been using this for decades.

3 Likes

There’s an easier way of training, where TSS isn’t the driving metric, but rather duration, frequency and/or intensity drives your way forward.

  • Workout a little longer than before (duration)
  • Workout more frequently than before (frequency)
  • Workout a little harder than before (intensity)
  • Recover a little less between efforts
  • Recover after 2-3-4 weeks of progressive increases in volume/frequency/intensity
1 Like

The only thing I would change is the order of the first two. Apart from that, I fully agree :ok_hand:

1 Like

Fair enough.

And here I though listing it in alphabetical order, would keep it neutral :grin:

  • Duration
  • Frequency
  • Intensity
  • Recovery

OK, smart people, this is a person whose exposure to program languages began and ended with Fortran. Yes, I’m that old.
So, as I am interested in @PiotrekNL’ s idea and thinking of implementing it: where and how is David’s method done? I’d be grateful for a simple explanation / demonstration…

Oh, and more in line with my knowledge: @MedTechCD and @Gerald : my view of load metrics like TSS is that they are constraints not targets. That is, train according to Gerald’s schema but limit the changes to those that raise TSS this week by no more than about 3-5% compared to the previous week.

Create a custom field and put this the in script field:


{
watts = streams.get("fixed_watts").data
w30 = icu.stats.calcMovingAvg(watts, 30)

tss = 0
for (let i = 0; i < w30.length; i++) {
  w = (w30[i]/icu.sportSettings.ftp)**2;
  tss += w
}

activity.icu_training_load = tss/36
}

It will update the Load after re-analyzing the activity or for new activities.

Or maybe @PiotrekNL will make his script public, so anyone could search for it and add it :slight_smile:

3 Likes

Thank you very much, @R2Tom . You are so helpful. I can certainly do this, Yippee.

So, imposing on your patience again: if I replace the line
activity.icu_training_load = tss/36
with
activity.icu_training_AltTSS = tss/36,
then I will have both the original load [TSS] and this new datum [AltTSS]?

Incidentally, where does the /36 come from?

And to get a new fitness graph based on AltTSS, then I just have to wait until I have enough new days for the graph to make sense and make a custom chart with the appropriate moving average functions?

If you don’t want to replace original load, just replace it with

data = tss/36

The division by 36 is conversion from seconds to hours multiplied by 100.

You have to create a Name and Code-Name in the ‘Type’ Tab and can use this in a custom fitness chart

You can re-analyze activities of the last 8 weeks and you will get appropriate numbers.

Hope this helps :slight_smile:

1 Like

I just need a few days so I can find time for it.
Btw, my idea in this thread is just very minimalistic improvement over TSS fixing its most glaring hole without really adjusting it.

My more promising idea is:

-instead of squaring power use the following function:
(P - 0.5)^2 for P bigger than 0.5 and 0 for P lower than 0.5 where P is power in % of FTP.

-calculate area under it - see @R2Tom 's code above.

This improves on TSS in two ways:

-it fixes the issue mentioned in this OP
-it stops overrating weak efforts weighting stronger efforts more. For example with this new function you need to ride 4 hours at 75% of FTP to get the same score as one hour at 100% of FTP.
Riding below 50% of FTP doesn’t increase the score as it should because there is 0 stimulus from it according to some papers I’ve seen (minimum stimulus is seemingly estimated to occur at 40%-60% of VO2max depending how well trained you are but again I am not physiologist so take that into account).

Of course the choice of 0.5 is completely arbitrary (maybe 0.3 or 0.4 works better if you want to weigh easier effort a bit more) although the choice of the function in the original TSS was also completely arbitrary so there is that :slight_smile:

I am sure it would be promising area of research to find a perfect physiologically correct function and it will likely be a different function for every kind of stimulus you can get, for fatigue etc. but then again I am also sure those simple adjustments make TSS a sensible metric unlike the original which can easily be ballooned by doing one 5 minutes interval every ride and then filling the rest with free-wheeling or riding at 50 Watts.

It’s also mentioned here, in the webinar on PMC.

1 Like

Gerald, the point is that if you have a very rigid schedule, for example when riding a trainer or running close to your house then yeah, it’s easy to eyeball your training load and then just do a bit more (longer and/or stronger) in small increments.

The problems start when you don’t have rigid schedule, for example when you ride a bike outside in a nice area and do a lot of 2-5 hours rides. Then it’s useful to measure load somehow and a TSS-like metric would be very helpful.

The problem with TSS is that it completely fails in the case of this flexible/ever changin schedule and only works in a rigid schedule case where, as you noted, it’s not needed in the first place.

Not really, it’s not rigid at all.

I have 4 hard interval sessions in a 21 day rolling period (that aren’t always on the same days each mesocycle; it’s adjusted as life happens). Everything else is easy and can range from an hour to 6 hours depending on time available and how the social rides affect it. Perhaps the better term is “focussed on the outcome”.

BTW, I agree that 41 (load) for a 10-10-10 (100%) workout appears to be too high, compared to 33 (load) for a 1x20m workout with no recovery.

  • The work (kJ) is the same for both, as work is joules = watts * seconds
  • The 1x20m @ 100% workout should have more fatigue than a 2x10m @ 100% with a 10m recovery @ 0% in between. HR would indicate that the second 10-min would be easier after a recovery.
  • Normalised Power and Intensity is higher on the 20m effort compared to the 10-10-10.

Normalised Power is:

  • the average power for rolling 30s
  • the 30s average value is raised by the 4th power
  • then averaged for the full range, i.e. not just a rolling 30s block
  • the averaged value is reduced by the 4th root

If the average of the 30s is applied and not the full range, then it’s much closer to the value from 1x20.

Is it also possible to overwrite load in workout creator tool? That would make experimenting with various functions way easier.

BTW, I agree that 41 (load) for a 10-10-10 (100%) workout appears to be too high, compared to 33 (load) for a 1x20m workout with no recovery.

It should have never been 4th power to begin with. The reason it’s done this way is that it’s IF x NP in the original TSS formula which reduces to NP^2 * constant. That’s why there was a need to use 4th power in NP instead of normal squares.
It’s clumsy math all around. Better to scratch it all off and start form basic principles:

1)Do we want 30 second smoothing? I am not a physiologists but I understand the idea behind is is that very short bursts and then rest is the same physiologically as constant effort at average power. There were studies to prove it so yeah, smoothing.

2)Do we want to count more intense efforts more than simple average power? Probably yes. Then we need a function that does that to our original function. Maybe it should be a square, maybe a square with adjustment (p - 0.5)^2 as I proposed for example. Maybe a hyperbolic function or maybe something else. Sounds like a good area for physiologists to research. Meanwhile we just need something sensible

Once you decide on questions in 1) and 2) then you just do what a normal math aware person does: calculate area under the curve or average over the whole thing multiplied by duration (it’s the same thing). This way no matter how many 0s are inserted anywhere the result will not change. No matter how many hours at 50W you add on either side or in the middle, the result will only change by how much 50W is worth (and that will not be much if you choose a function in 2) wisely).

We are navigating in the dark here as there is not enough research to decide on the best way to do 1) and 2). We can at least correct the glaring nonsense which somehow keeps being repeated for almost 20 years now and thanks to the awesomeness of Intervals.icu we can experiment with it ourselves!

Btw, the best solution would need to be some kind of summing/progressive function that takes the past into account so 20m at 100% FTP produces higher score than 10m hard - 10m easy - 10m hard. This requires a bit more thought. Original TSS doesn’t solve it either though so it shouldn’t stop us from trying to improve upon it.

I will post some functions here in a few days, experimenting for now :slight_smile:

1 Like

That would be nice but there isn’t any support for “custom workout fields” at the moment.

1 Like

It would be enough to have “overwrite TSS globally in every single place” but I understand it’s a niche use case. It’s awesome as it is anyway, just a bit more difficult to debug.

I was dragged down this wormhole in the past, by a certain trainer software. It runs fully on TSS, with zero consideration of intensity factor [ at the time ] , and completely burned me out on rager VO2s & all-out sprint intervals that my body simply wasn’t capable of. Was fatigued and sick-ish for weeks. I don’t use that software anymore !!

Difficulty, stress, and fatigue on the bod increase exponentially as you approach, and then exceed FTP. It’s not linear, especially above FTP.

I think a valuable metric would be “intensity watt-seconds” , where they are calculated on an exponential curve, weighted relative to FTP.

So if FTP is 200, 5 min @ 100 is near zero intensity watt-seconds. But 1 min @ 220 is… a lot of them. Would need to dial in the exact curve, but I think that would be pretty easy.

Looking for a snapshot & relative measure here, not an exact & objective one, so that you could build how much intensity you’re loading on week over week gradually… and identify “bad idea” WOs, based on your recent training.

I know that we have ‘Intensity’, but that is normalized for the whole ride.

If I sit at 120 w for an hour, but during that hour I blow my brains out on 4 x 60 s @ 240, I will not be walking !!! But the Intensity will still read quite low, as it’s averaged.

I think this would give a great tool. But I’m not sure. Thoughts ?

One quick note: I know most of us now just “know what our bodies can do” , and don’t really need a measure. But new riders, who don’t know that and don’t yet have an innate feel for what is good, and what is too intense / too much stress loading in a week, may find it super useful.

It would have saved my rear end when I started training more intensely, and prevented burn out.

This curve could even be adjusted / scalable for each rider, to customize the data output, to balance out for “steady / threshold” and “strong sprinter” riders…

1 Like

Chris, I’ve created a sample function called PioTSS you can try. It works by adjusting power as (p - adj_factor)^2

Default adj_factor is 0.45. This way you get:

1hour at 100% FTP = 100PioTSS
1hour at 70% FTP = 20.6 PioTSS
1hour at 110% FTP = 139.6 PioTSS
1hour at 120% FTP = 185.9 PioTSS

It’s a public metric. The code is below (using @R2Tom 's template):

{
watts = streams.get("fixed_watts").data
w30 = icu.stats.calcMovingAvg(watts, 30)

tss = 0
adj_factor = 0.45

for (let i = 0; i < w30.length; i++) {
 p = w30[i] / icu.sportSettings.ftp
 if (p > adj_factor) {
  w = (p - adj_factor)**2
 } else {
  w = 0
 }
 tss += w
}

tss = tss  / 36
pio_tss_at_100FTP = (1 - adj_factor) ** 2

activity.icu_training_load = tss * (1 / pio_tss_at_100FTP)
}

This changes default TSS to a new metric. If you want to just experiment and look at the values change the last line to:

tss = tss * (1 / pio_tss_at_100FTP)

While it may still underestimate very intense intervals it’s definitely better than the original in this respect. I’ve played around with it a bit and it seems to be “scoring” my rides quite reasonably. Far from perfect, much better than TSS.

1 Like

@PiotrekNL dude !! This is awesome !! Thanks a ton.

I hope you find it helpful as well. How long have you been running it ?