mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
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:
committed by
GitHub
parent
f31fa924f5
commit
83797126bc
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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> <s, 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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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> <s, QVector<double> &sts, QVector<double> &sb, QVector<double> &rr) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _GC_StressCalculator_h
|
#endif // _GC_StressCalculator_h
|
||||||
|
|||||||
Reference in New Issue
Block a user