Calculating expected load also based on past stress (#4651)

Additional:
* Allow charts with planned load
* Initialize RideItem::planned to false (was: uninitialized)
This commit is contained in:
Joachim Kohlhammer
2025-05-30 22:20:47 +02:00
committed by GitHub
parent f31fa924f5
commit 83797126bc
4 changed files with 132 additions and 165 deletions

View File

@@ -1039,6 +1039,51 @@ QList<MetricDetail> LTMTool::providePMmetrics() {
cogganExpectedTSB.uunits = "TSB"; cogganExpectedTSB.uunits = "TSB";
metrics.append(cogganExpectedTSB); metrics.append(cogganExpectedTSB);
MetricDetail cogganPlannedCTL;
cogganPlannedCTL.type = METRIC_PM;
cogganPlannedCTL.symbol = "planned_coggan_ctl";
cogganPlannedCTL.metric = NULL; // not a factory metric
cogganPlannedCTL.penColor = QColor(Qt::blue);
cogganPlannedCTL.curveStyle = QwtPlotCurve::Dots;
cogganPlannedCTL.symbolStyle = QwtSymbol::NoSymbol;
cogganPlannedCTL.smooth = false;
cogganPlannedCTL.trendtype = 0;
cogganPlannedCTL.topN = 1;
cogganPlannedCTL.uname = cogganPlannedCTL.name = tr("Coggan Planned Chronic Training Load");
cogganPlannedCTL.units = "CTL";
cogganPlannedCTL.uunits = "CTL";
metrics.append(cogganPlannedCTL);
MetricDetail cogganPlannedATL;
cogganPlannedATL.type = METRIC_PM;
cogganPlannedATL.symbol = "planned_coggan_atl";
cogganPlannedATL.metric = NULL; // not a factory metric
cogganPlannedATL.penColor = QColor(Qt::magenta);
cogganPlannedATL.curveStyle = QwtPlotCurve::Dots;
cogganPlannedATL.symbolStyle = QwtSymbol::NoSymbol;
cogganPlannedATL.smooth = false;
cogganPlannedATL.trendtype = 0;
cogganPlannedATL.topN = 1;
cogganPlannedATL.uname = cogganPlannedATL.name = tr("Coggan Planned Acute Training Load");
cogganPlannedATL.units = "ATL";
cogganPlannedATL.uunits = "ATL";
metrics.append(cogganPlannedATL);
MetricDetail cogganPlannedTSB;
cogganPlannedTSB.type = METRIC_PM;
cogganPlannedTSB.symbol = "planned_coggan_tsb";
cogganPlannedTSB.metric = NULL; // not a factory metric
cogganPlannedTSB.penColor = QColor(Qt::yellow);
cogganPlannedTSB.curveStyle = QwtPlotCurve::Dots;
cogganPlannedTSB.symbolStyle = QwtSymbol::NoSymbol;
cogganPlannedTSB.smooth = false;
cogganPlannedTSB.trendtype = 0;
cogganPlannedTSB.topN = 1;
cogganPlannedTSB.uname = cogganPlannedTSB.name = tr("Coggan Planned Training Stress Balance");
cogganPlannedTSB.units = "TSB";
cogganPlannedTSB.uunits = "TSB";
metrics.append(cogganPlannedTSB);
// TRIMP LTS // TRIMP LTS
MetricDetail trimpLTS; MetricDetail trimpLTS;
trimpLTS.type = METRIC_PM; trimpLTS.type = METRIC_PM;

View File

@@ -151,7 +151,7 @@ class RideItem : public QObject
QDateTime dateTime; QDateTime dateTime;
QString present; QString present;
QColor color; QColor color;
bool planned; bool planned = false;
QString sport; QString sport;
bool isBike,isRun,isSwim,isXtrain,isAero; bool isBike,isRun,isSwim,isXtrain,isAero;
bool samples; // has samples data bool samples; // has samples data

View File

@@ -165,6 +165,7 @@ void PMCData::refresh()
// back to null date if not set, just to get round date arithmetic // back to null date if not set, just to get round date arithmetic
if (start_ == QDate(9999,12,31)) start_ = QDate(); if (start_ == QDate(9999,12,31)) start_ = QDate();
QVector<double> expected_stress;
// We got a valid range ? // We got a valid range ?
if (start_ != QDate() && end_ != QDate() && start_ < end_) { if (start_ != QDate() && end_ != QDate() && start_ < end_) {
@@ -182,6 +183,7 @@ void PMCData::refresh()
planned_sb_.resize(days_+1); // for SB tomorrow! planned_sb_.resize(days_+1); // for SB tomorrow!
planned_rr_.resize(days_); planned_rr_.resize(days_);
expected_stress.resize(days_);
expected_lts_.resize(days_); expected_lts_.resize(days_);
expected_sts_.resize(days_); expected_sts_.resize(days_);
expected_sb_.resize(days_+1); // for SB tomorrow! expected_sb_.resize(days_+1); // for SB tomorrow!
@@ -220,8 +222,8 @@ void PMCData::refresh()
// STEP TWO What are the seedings and ride values // STEP TWO What are the seedings and ride values
// //
bool sbToday = appsettings->cvalue(context->athlete->cyclist, GC_SB_TODAY).toInt(); bool sbToday = appsettings->cvalue(context->athlete->cyclist, GC_SB_TODAY).toInt();
double lte = (double)exp(-1.0/ltsDays_); const double lte = (double)exp(-1.0/ltsDays_);
double ste = (double)exp(-1.0/stsDays_); const double ste = (double)exp(-1.0/stsDays_);
// clear what's there // clear what's there
stress_.fill(0); stress_.fill(0);
@@ -236,6 +238,7 @@ void PMCData::refresh()
planned_sb_.fill(0); planned_sb_.fill(0);
planned_rr_.fill(0); planned_rr_.fill(0);
expected_stress.fill(0);
expected_lts_.fill(0); expected_lts_.fill(0);
expected_sts_.fill(0); expected_sts_.fill(0);
expected_sb_.fill(0); expected_sb_.fill(0);
@@ -255,6 +258,10 @@ void PMCData::refresh()
DataFilter* df = new DataFilter(this, context); DataFilter* df = new DataFilter(this, context);
int todayOffset = -1;
double todayActualStress = 0;
double todayPlannedStress = 0;
// add the stress scores // add the stress scores
foreach(RideItem *item, context->athlete->rideCache->rides()) { foreach(RideItem *item, context->athlete->rideCache->rides()) {
@@ -276,180 +283,93 @@ void PMCData::refresh()
else else
stress_[offset] += value; stress_[offset] += value;
//qDebug()<<"stress_["<<offset<<"] :"<<stress_[offset]; //qDebug()<<"stress_["<<offset<<"] :"<<stress_[offset];
if (start_.addDays(offset).daysTo(QDate::currentDate()) == 0) {
// Collect todays stress separately to decide later whether to use planned or actual stress
todayOffset = offset;
if (item->planned) {
todayPlannedStress += value;
} else {
todayActualStress += value;
}
} else if (start_.addDays(offset).daysTo(QDate::currentDate()) < 0) {
if (item->planned) {
expected_stress[offset] += value;
}
} else {
if (! item->planned) {
expected_stress[offset] += value;
}
}
} }
} }
} }
if (todayOffset > -1) {
// Special case today: Use actual stress if available, otherwise planned
expected_stress[todayOffset] = (todayActualStress > 0) ? todayActualStress : todayPlannedStress;
}
delete df; delete df;
// calculateMetrics(days_, stress_, lts_, sts_, sb_, rr_);
// STEP THREE Calculate sts/lts, sb and rr calculateMetrics(days_, planned_stress_, planned_lts_, planned_sts_, planned_sb_, planned_rr_);
// calculateMetrics(days_, expected_stress, expected_lts_, expected_sts_, expected_sb_, expected_rr_);
double lastLTS=0.0f;
double lastSTS=0.0f;
double rollingStress=0;
double planned_lastLTS=0.0f;
double planned_lastSTS=0.0f;
double planned_rollingStress=0;
#if notyet
double expected_lastLTS=0.0f;
double expected_lastSTS=0.0f;
#endif
double expected_rollingStress=0;
for(int day=0; day < days_; day++) {
// not seeded
if (lts_[day] >=0 || sts_[day]>=0) {
// 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);
} else if (lts_[day]< 0 || sts_[day]<0) {
lts_[day] *= -1;
sts_[day] *= -1;
}
// rolling stress for STS days
if (day && day <= stsDays_) {
// just starting out
rollingStress += lts_[day] - lts_[day-1];
rr_[day] = rollingStress;
} else if (day) {
rollingStress += lts_[day] - lts_[day-1];
rollingStress -= lts_[day-stsDays_] - lts_[day-stsDays_-1];
rr_[day] = rollingStress;
}
// SB (stress balance) long term - short term
// We allow it to be shown today or tomorrow where
// most (sane/thinking) folks usually show SB on the following day
sb_[day+(sbToday ? 0 : 1)] = lts_[day] - sts_[day];
// *******************
// **** PLANNED ****
// *******************
// not seeded
if (planned_lts_[day] >=0 || planned_sts_[day]>=0) {
// LTS
if (day) planned_lastLTS = planned_lts_[day-1];
planned_lts_[day] = (planned_stress_[day] * (1.0 - lte)) + (planned_lastLTS * lte);
// STS
if (day) planned_lastSTS = planned_sts_[day-1];
planned_sts_[day] = (planned_stress_[day] * (1.0 - ste)) + (planned_lastSTS * ste);
} else if (planned_lts_[day]< 0 || planned_sts_[day]<0) {
planned_lts_[day] *= -1;
planned_sts_[day] *= -1;
}
// rolling stress for STS days
if (day && day <= stsDays_) {
// just starting out
planned_rollingStress += planned_lts_[day] - planned_lts_[day-1];
planned_rr_[day] = planned_rollingStress;
} else if (day) {
planned_rollingStress += planned_lts_[day] - planned_lts_[day-1];
planned_rollingStress -= planned_lts_[day-stsDays_] - planned_lts_[day-stsDays_-1];
planned_rr_[day] = planned_rollingStress;
}
// SB (stress balance) long term - short term
// We allow it to be shown today or tomorrow where
// most (sane/thinking) folks usually show SB on the following day
planned_sb_[day+(sbToday ? 0 : 1)] = planned_lts_[day] - planned_sts_[day];
// ********************
// **** EXPECTED ****
// ********************
if (start_.addDays(day).daysTo(QDate::currentDate())<0) {
double lastLts = 0.0;
double lastSts = 0.0;
double ltsAtStsDays1 = 0.0;
double ltsAtStsDays2 = 0.0;
if (day) {
if (start_.addDays(day).daysTo(QDate::currentDate())<-1) {
lastLts = expected_lts_[day-1];
lastSts = expected_sts_[day-1];
} else {
lastLts = lts_[day-1];
lastSts = sts_[day-1];
}
if (day > stsDays_) {
if (start_.addDays(day).daysTo(QDate::currentDate())<-1-stsDays_) {
ltsAtStsDays1 = expected_lts_[day-stsDays_-1];
} else {
ltsAtStsDays1 = lts_[day-stsDays_-1];
}
if (start_.addDays(day).daysTo(QDate::currentDate())<-stsDays_) {
ltsAtStsDays2 = expected_lts_[day-stsDays_];
} else {
ltsAtStsDays2 = lts_[day-stsDays_];
}
}
}
// not seeded
if (expected_lts_[day] >=0 || expected_sts_[day]>=0) {
// LTS
expected_lts_[day] = (planned_stress_[day] * (1.0 - lte)) + (lastLts * lte);
// STS
expected_sts_[day] = (planned_stress_[day] * (1.0 - ste)) + (lastSts * ste);
} else if (expected_lts_[day]< 0 || expected_sts_[day]<0) {
expected_lts_[day] *= -1;
expected_sts_[day] *= -1;
}
// rolling stress for STS days
if (day && day <= stsDays_) {
// just starting out
expected_rollingStress += expected_lts_[day] - lastLts;
expected_rr_[day] = expected_rollingStress;
} else if (day) {
expected_rollingStress += expected_lts_[day] - lastLts;
expected_rollingStress -= ltsAtStsDays2 - ltsAtStsDays1;
expected_rr_[day] = expected_rollingStress;
}
// SB (stress balance) long term - short term
// We allow it to be shown today or tomorrow where
// most (sane/thinking) folks usually show SB on the following day
expected_sb_[day+(sbToday ? 0 : 1)] = expected_lts_[day] - expected_sts_[day];
} else {
expected_lts_[day] = 0;
expected_sts_[day] = 0;
expected_sb_[day] = 0;
expected_rr_[day] = 0;
}
}
//qDebug()<<"refresh PMC in="<<timer.elapsed()<<"ms"; //qDebug()<<"refresh PMC in="<<timer.elapsed()<<"ms";
isstale=false; isstale=false;
} }
void
PMCData::calculateMetrics
(int days, const QVector<double> &stress, QVector<double> &lts, QVector<double> &sts, QVector<double> &sb, QVector<double> &rr) const
{
const bool sbToday = appsettings->cvalue(context->athlete->cyclist, GC_SB_TODAY).toInt();
const double lte = (double)exp(-1.0/ltsDays_);
const double ste = (double)exp(-1.0/stsDays_);
double lastLTS=0.0f;
double lastSTS=0.0f;
double rollingStress=0;
for(int day=0; day < days; day++) {
// not seeded
if (lts[day] >=0 || sts[day]>=0) {
// 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);
} else if (lts[day]< 0 || sts[day]<0) {
lts[day] *= -1;
sts[day] *= -1;
}
// rolling stress for STS days
if (day && day <= stsDays_) {
// just starting out
rollingStress += lts[day] - lts[day-1];
rr[day] = rollingStress;
} else if (day) {
rollingStress += lts[day] - lts[day-1];
rollingStress -= lts[day-stsDays_] - lts[day-stsDays_-1];
rr[day] = rollingStress;
}
// SB (stress balance) long term - short term
// We allow it to be shown today or tomorrow where
// most (sane/thinking) folks usually show SB on the following day
sb[day+(sbToday ? 0 : 1)] = lts[day] - sts[day];
}
}
int int
PMCData::indexOf(QDate date) PMCData::indexOf(QDate date)
{ {

View File

@@ -132,6 +132,8 @@ class PMCData : public QObject {
QVector<double> expected_lts_, expected_sts_, expected_sb_, expected_rr_; QVector<double> expected_lts_, expected_sts_, expected_sb_, expected_rr_;
bool isstale; // needs refreshing bool isstale; // needs refreshing
void calculateMetrics(int days, const QVector<double> &stress, QVector<double> &lts, QVector<double> &sts, QVector<double> &sb, QVector<double> &rr) const;
}; };
#endif // _GC_StressCalculator_h #endif // _GC_StressCalculator_h