thomaschampagne / elevate

A sport app to "Elevate" your training experience and goals! Track your fitness and progressions over time. Analyse deeper your activities. And more...
https://thomaschampagne.github.io/elevate-docs/
Mozilla Public License 2.0
1.28k stars 175 forks source link

Fitness trend "Form" has to be based on yesterday's "Fitness" and "Fatigue" #579

Closed aprokop closed 5 years ago

aprokop commented 6 years ago

Describe your environment

Describe the problem:

Right now, the Form seems to be calculated as the difference of the same day's Fitness and Fatigue. However, it may be better to base it on yesterday's Fitness and Fatigue. This is debatable, of course. I can only suggest these two points: a) it feels more correct to me subjectively, as the training fatigues is felt more the next day b) TrainingPeaks is doing that (see here, "Form" section)

jayti74 commented 6 years ago

Form by definition is (TrainingPeaks): Training Stress Balance (TSB) or Form represents the balance of training stress.

Form (TSB) = Yesterday's Fitness (CTL) - Yesterday's Fatigue (ATL)

https://help.trainingpeaks.com/hc/en-us/articles/204071764-Form-TSB-

thomaschampagne commented 6 years ago

@aprokop Yes it should with the math model used. Formulas are in the helper

aprokop commented 6 years ago

@thomaschampagne Not sure I understand your comment. Are you saying that's already in?

thomaschampagne commented 6 years ago

@aprokop Yes. Elevate fitness model = Training peaks model

image

aprokop commented 6 years ago

@thomaschampagne I don't think so. The TrainingPeaks model is

Form(day+1) = Fitness(day) - Fatigue(day)

It also seems that the other two formulas are not exactly right, and should be

Fitness(day+1) = Fitness(day) + (StressScore(day+1)-Fitness(day)) x ...
Fatigue(day+1) = Fatigue(day) + (StressScore(day+1)-Fatigue(day)) x ...

I look at my TrainingPeaks curve, and major effort significantly affects both Fitness and Fatigue on the same day, and Form next day.

screen shot 2018-10-16 at 9 33 13 am

jayti74 commented 6 years ago

yupp that's also my point:

@thomaschampagne I don't think so. The TrainingPeaks model is

Form(day+1) = Fitness(day) - Fatigue(day)

thomaschampagne commented 6 years ago

Ok i didn't understood. I reopened the ticket. Thanks for the digging. FYI code is here: https://github.com/thomaschampagne/elevate/blob/990b5d0fc11113b2c4d120e6aec9f0ba3dc0e844/plugin/app/src/app/fitness-trend/shared/services/fitness.service.ts#L243

thomaschampagne commented 6 years ago

Making a PR is something possible to you or i handle it myself?

aprokop commented 6 years ago

Never worked with this programming language, but I could try if you think it's easy enough. I guess my main concern is the scope of affected code, and whether it requires any reorganization.

thomaschampagne commented 6 years ago

@aprokop The scope should be only that method https://github.com/thomaschampagne/elevate/blob/990b5d0fc11113b2c4d120e6aec9f0ba3dc0e844/plugin/app/src/app/fitness-trend/shared/services/fitness.service.ts#L226

But don't worry i'll do it. Just need your help to test and validate changes.

thomaschampagne commented 6 years ago

@aprokop Are you sure at 100% about this below?

Fitness(day+1) = Fitness(day) + (StressScore(day+1)-Fitness(day)) x ...
Fatigue(day+1) = Fatigue(day) + (StressScore(day+1)-Fatigue(day)) x ...

I fixed the code with your recommendations (via commit https://github.com/thomaschampagne/elevate/commit/ec73ee0c5a6d3c78662c41b94a4e090c70a1572b). To simplify the implementation in existing code, i implemented your formulas like this:

Fitness(day) = Fitness(day-1) + (StressScore(day)-Fitness(day-1)) x ...
Fatigue(day) = Fatigue(day-1) + (StressScore(day)-Fatigue(day-1)) x ...
Form(day) = Fitness(day-1) - Fatigue(day-1)

@aprokop @jayti74

Test Build: v6.6.0_stable_ec73ee0_2018-10-17-16-05.zip

Thanks for your help :)

aprokop commented 6 years ago

@thomaschampagne

Are you sure at 100% about this below?

Yes and no. Yes, I'm sure that it's wrong in the sense that it does not match TrainingPeaks as the effort in the same day would not affect Fitness/Fatigue in the same day. It certainly is necessary to change StressScore(day) to StressScore(day+1). However, I'm not sure if that's the exact formula. One could think that formula like

Fitness(day+1) = Fitness(day) + (StressScore(day+1)-Fitness(day+1)) x ...

could also make sense (one would then need to solve for Fitness(day+1)). But I think it is good as it is.

The commit implements the suggested formulas correctly. You could move out updates of prevCtl and friends out of if (isPreStartDay) clauses, but that's minor.

I'll test it in the evening when I get home.

aprokop commented 6 years ago

Just tried it out, and it behaves as expected. The form dropped next day, while fitness and fatigue went up the same day. So, I think it's all good now. screenshot_20181017_202434

thomaschampagne commented 6 years ago

@aprokop Thanks for your tests and review! So we have 2 formulas:

Fitness(day+1) = Fitness(day) + (StressScore(day+1)-Fitness(day+1)) x ...
-- or --
Fitness(day+1) = Fitness(day) + (StressScore(day+1)-Fitness(day)) x ...

But i think we have a problem with the first one :) It's strange to calculate Fitness(day+1) if Fitness(day+1) is also part of the expression... This is what we want to find.

And if we solve expression, we have:

Fitness(day+1) = (Fitness(day) - k * StressScore(day+1)) /(1 - k)  where k = exp(-1/42)

... does that make sense?!

Is there a way to export csv data from TrainingPeaks day by day? Or get more info from your TrainingPeaks graph screenshot?

On your TrainingPeaks graph screenshot, red dots are day of effort(s)? Do you confirm Fitness and Fatigue curve goes up the day of your effort?

Thanks for your help :)

aprokop commented 5 years ago

@thomaschampagne I agree with you that Fitness(day+1) = Fitness(day) + (StressScore(day+1)-Fitness(day+1)) x ... is a weird one. I only mentioned it because I am not 100% sure.

Is there a way to export csv data from TrainingPeaks day by day? Or get more info from your TrainingPeaks graph screenshot?

This is a really good idea. I believe it's possible, and I've done that in the past. Let me try to get the recent data.

On your TrainingPeaks graph screenshot, red dots are day of effort(s)? Do you confirm Fitness and Fatigue curve goes up the day of your effort?

Yes, red dots are the values of TSS for each day, and blue ones are the intensity.

Do you confirm Fitness and Fatigue curve goes up the day of your effort?

Yes.

aprokop commented 5 years ago

OK, so one can export workouts but not the curve values. Fine. Lets test it.

Lets look at the recent hard race (marking as - non-important data). The data had to be taken from a graph, and it was rounded to the nearest integer by TrainingPeaks.

pre race day race day after race day
TSS - 395 -
Fatigue 54 102 -
Fitness 61 69 -
Form - 8 -33

So:

  1. Fatigue (day) - Fitness (day) = 69 - 102 = 33 = Form(day+1), so we have the correct formula for Form.
  2. Fatigue(day-1) + (TSS - Fatigue(day-1)) x ... = 54 + (395-54)x(1-e**(-1/7)) = 99, so approximately 102?
  3. (Fatigue(day-1) + TSS(1-k))/(2-k) = (54 + 395(1-k))/(2-k) = 94, so way off. (k = exp(-1/7)). I fixed the fomula calculations here, as the one in the previous comment is wrong.
  4. Fitness(day-1) + (TSS - Fitness(day-1) x ... = 61 + (395-61)x(1-e**(-1/42) = 69, so that's correct.
  5. (Fitness(day-1) + TSS(1-K))/(2-K) = (61 + 395(1-K))/(2-K) = 69, so it's also about right (here, K = exp(-1/42)).

In summary, these seem the best option:

Form(day) = Fitness(day-1) - Fatigue(day-1)
Fitness(day) = Fitness(day-1) + (StressScore(day)-Fitness(day-1)) x (1-exp(-1/42))
Fatigue(day) = Fatigue(day-1) + (StressScore(day)-Fatigue(day-1)) x (1-exp(-1/7))

P.S. I still don't quite get why in 2. we only get 99 and not 102.

aprokop commented 5 years ago

Took a look at GoldenCheetah:

double lte = (double)exp(-1.0/ltsDays_);
double ste = (double)exp(-1.0/stsDays_);
 ...
// LTS
if (day) lastLTS = lts_[day-1];
lts_[day] = (stress_[day] * (1.0 - lte)) + (lastLTS * lte);

// STS
if (day) lastSTS = sts_[day-1];
sts_[day] = (stress_[day] * (1.0 - ste)) + (lastSTS * ste);

There is also this.

So, I think we are good.

thomaschampagne commented 5 years ago

@aprokop Thanks a lot for this deep analysis! It's clear, unambiguous!