mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18: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";
|
||||
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
|
||||
MetricDetail trimpLTS;
|
||||
trimpLTS.type = METRIC_PM;
|
||||
|
||||
@@ -151,7 +151,7 @@ class RideItem : public QObject
|
||||
QDateTime dateTime;
|
||||
QString present;
|
||||
QColor color;
|
||||
bool planned;
|
||||
bool planned = false;
|
||||
QString sport;
|
||||
bool isBike,isRun,isSwim,isXtrain,isAero;
|
||||
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
|
||||
if (start_ == QDate(9999,12,31)) start_ = QDate();
|
||||
|
||||
QVector<double> expected_stress;
|
||||
// We got a valid range ?
|
||||
if (start_ != QDate() && end_ != QDate() && start_ < end_) {
|
||||
|
||||
@@ -182,6 +183,7 @@ void PMCData::refresh()
|
||||
planned_sb_.resize(days_+1); // for SB tomorrow!
|
||||
planned_rr_.resize(days_);
|
||||
|
||||
expected_stress.resize(days_);
|
||||
expected_lts_.resize(days_);
|
||||
expected_sts_.resize(days_);
|
||||
expected_sb_.resize(days_+1); // for SB tomorrow!
|
||||
@@ -220,8 +222,8 @@ void PMCData::refresh()
|
||||
// STEP TWO What are the seedings and ride values
|
||||
//
|
||||
bool sbToday = appsettings->cvalue(context->athlete->cyclist, GC_SB_TODAY).toInt();
|
||||
double lte = (double)exp(-1.0/ltsDays_);
|
||||
double ste = (double)exp(-1.0/stsDays_);
|
||||
const double lte = (double)exp(-1.0/ltsDays_);
|
||||
const double ste = (double)exp(-1.0/stsDays_);
|
||||
|
||||
// clear what's there
|
||||
stress_.fill(0);
|
||||
@@ -236,6 +238,7 @@ void PMCData::refresh()
|
||||
planned_sb_.fill(0);
|
||||
planned_rr_.fill(0);
|
||||
|
||||
expected_stress.fill(0);
|
||||
expected_lts_.fill(0);
|
||||
expected_sts_.fill(0);
|
||||
expected_sb_.fill(0);
|
||||
@@ -255,6 +258,10 @@ void PMCData::refresh()
|
||||
|
||||
DataFilter* df = new DataFilter(this, context);
|
||||
|
||||
int todayOffset = -1;
|
||||
double todayActualStress = 0;
|
||||
double todayPlannedStress = 0;
|
||||
|
||||
// add the stress scores
|
||||
foreach(RideItem *item, context->athlete->rideCache->rides()) {
|
||||
|
||||
@@ -276,180 +283,93 @@ void PMCData::refresh()
|
||||
else
|
||||
stress_[offset] += value;
|
||||
//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;
|
||||
|
||||
//
|
||||
// STEP THREE Calculate sts/lts, sb and 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
calculateMetrics(days_, stress_, lts_, sts_, sb_, 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_);
|
||||
|
||||
//qDebug()<<"refresh PMC in="<<timer.elapsed()<<"ms";
|
||||
|
||||
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
|
||||
PMCData::indexOf(QDate date)
|
||||
{
|
||||
|
||||
@@ -132,6 +132,8 @@ class PMCData : public QObject {
|
||||
QVector<double> expected_lts_, expected_sts_, expected_sb_, expected_rr_;
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user