Enable Performance Tests for CV charts

Show them in CV charts and use them for fitting,
just like they work in CP charts.
This commit is contained in:
Alejandro Martinez
2024-09-19 20:55:10 -03:00
parent 7e7ffd3cba
commit ee992ad130
3 changed files with 46 additions and 40 deletions

View File

@@ -57,6 +57,36 @@
#include "qwt_spline_curve_fitter.h"
#include "LTMTrend.h"
QString
CPPlot::paceString(double secs, double kph)
{
QString paceStr;
const PaceZones *zones = context->athlete->paceZones(sport=="Swim");
const bool metricPace = zones ? appsettings->value(this, zones->paceSetting(), GlobalContext::context()->useMetricUnits).toBool() : GlobalContext::context()->useMetricUnits;
if (sport == "Run" || sport == "Swim") {
if (zones) paceStr = QString("\n%1 %2").arg(zones->kphToPaceString(kph, metricPace))
.arg(zones->paceUnits(metricPace));
} else if (sport == "Row") {
paceStr = QString("\n%1 %2").arg(kphToPace(kph*2, true, false)).arg(tr("min/500m"));
}
const double km = kph*secs/60.0; // distance in km
if (sport == "Swim") {
if (metricPace) paceStr += tr("\n%1 m").arg(1000*km, 0, 'f', 0);
else paceStr += tr("\n%1 yd").arg(1000*km/METERS_PER_YARD, 0, 'f', 0);
} else {
if (metricPace) paceStr += tr("\n%1 km").arg(km, 0, 'f', 3);
else paceStr += tr("\n%1 mi").arg(MILES_PER_KM*km, 0, 'f', 3);
}
return paceStr;
}
CPPlot::CPPlot(CriticalPowerWindow *parent, Context *context, bool rangemode) : QwtPlot(parent), parent(parent),
@@ -1064,7 +1094,8 @@ CPPlot::plotTests(RideItem *rideitem)
QVector<QPointF> points;
// just plot tests as power duration for now, will reiterate to add others later.
if (rideSeries == RideFile::watts || rideSeries == RideFile::wattsKg || criticalSeries == CriticalPowerWindow::work) {
if (rideSeries == RideFile::watts || rideSeries == RideFile::wattsKg ||
criticalSeries == CriticalPowerWindow::work || rideSeries == RideFile::kph) {
// rides to search, this one only -or- all in the date range selected?
QList<RideItem*> rides;
@@ -1091,7 +1122,9 @@ CPPlot::plotTests(RideItem *rideitem)
if (interval->istest()) {
double duration = (interval->stop - interval->start) + 1; // add offset used on log axis
double watts = interval->getForSymbol("average_power", GlobalContext::context()->useMetricUnits);
double watts = rideSeries == RideFile::kph ?
interval->getForSymbol("average_speed", GlobalContext::context()->useMetricUnits) :
interval->getForSymbol("average_power", GlobalContext::context()->useMetricUnits);
// ignore where no power present
@@ -1121,10 +1154,11 @@ CPPlot::plotTests(RideItem *rideitem)
test->setSymbol(sym);
test->setValue(duration/60.00f, watts);
QString desc = QString("%3\n%1 %4\n%2").arg(watts,0, 'f', rideSeries == RideFile::watts ? 0 : 2)
QString desc = QString("%3\n%1 %4%5\n%2").arg(watts,0, 'f', rideSeries == RideFile::watts ? 0 : 2)
.arg(interval_to_str(duration))
.arg(interval->name)
.arg(criticalSeries == CriticalPowerWindow::work ? tr("kJ") : RideFile::unitName(rideSeries, context));
.arg(criticalSeries == CriticalPowerWindow::work ? tr("kJ") : RideFile::unitName(rideSeries, context))
.arg(criticalSeries == CriticalPowerWindow::kph ? paceString(duration/60.0, watts) : "");
QwtText text(desc);
QFont font; // default
font.setPointSize(8);
@@ -2114,7 +2148,7 @@ CPPlot::pointHover(QwtPlotCurve *curve, int index)
const double xvalue = curve->sample(index).x();
const double yvalue = curve->sample(index).y();
QString text, dateStr, paceStr;
QString text, dateStr;
QString currentRidePercentStr;
QString units1;
QString units2;
@@ -2192,35 +2226,6 @@ CPPlot::pointHover(QwtPlotCurve *curve, int index)
}
#endif
// for speed series add pace with units according to settings
if (criticalSeries == CriticalPowerWindow::kph) {
if (sport == "Run" || sport == "Swim") {
const PaceZones *zones = context->athlete->paceZones(sport=="Swim");
if (zones) paceStr = QString("\n%1 %2").arg(zones->kphToPaceString(yvalue, metricPace))
.arg(zones->paceUnits(metricPace));
} else if (sport == "Row") {
paceStr = QString("\n%1 %2").arg(kphToPace(yvalue*2, true, false)).arg(tr("min/500m"));
}
const double km = yvalue*xvalue/60.0; // distance in km
if (sport == "Swim") {
if (metricPace) paceStr += tr("\n%1 m").arg(1000*km, 0, 'f', 0);
else paceStr += tr("\n%1 yd").arg(1000*km/METERS_PER_YARD, 0, 'f', 0);
} else {
if (metricPace) paceStr += tr("\n%1 km").arg(km, 0, 'f', 3);
else paceStr += tr("\n%1 mi").arg(MILES_PER_KM*km, 0, 'f', 3);
}
}
// output the tooltip
text = QString("%1%2\n%3 %4%5%6")
.arg(criticalSeries == CriticalPowerWindow::veloclinicplot ?
@@ -2229,7 +2234,7 @@ CPPlot::pointHover(QwtPlotCurve *curve, int index)
.arg(units1)
.arg(units2)
.arg(currentRidePercentStr)
.arg(paceStr)
.arg(criticalSeries == CriticalPowerWindow::kph ? paceString(xvalue, yvalue) : "")
.arg(dateStr);
// set that text up

View File

@@ -149,6 +149,7 @@ class CPPlot : public QwtPlot
void refreshReferenceLines(RideItem*);
QString kphToString(double kph);
QString kmToString(double km);
QString paceString(double secs, double kph);
// Models and Extended Models
int model, modelVariant;

View File

@@ -210,7 +210,7 @@ PDModel::deriveCPParameters(int model)
// RMSE
double RMSE=sqrt(mean);
double CV=(RMSE/MEAN) * 100;
fitsummary = tr("RMSE %1w CV %4% R<sup>2</sup>=%3 [LR] %2 points").arg(RMSE, 0, 'f', 0)
fitsummary = tr("RMSE %1 CV %4% R<sup>2</sup>=%3 [LR] %2 points").arg(RMSE, 0, 'f', 0)
.arg(t.size())
.arg(R2, 0, 'f', 3)
.arg(CV, 0, 'f', 1);
@@ -329,7 +329,7 @@ PDModel::deriveCPParameters(int model)
// RMSE and CV
double RMSE=sqrt(mean);
double CV=(RMSE/MEAN) * 100;
fitsummary = tr("RMSE %1w CV %3% [LM] %2 points").arg(RMSE, 0, 'f', 0)
fitsummary = tr("RMSE %1 CV %3% [LM] %2 points").arg(RMSE, 0, 'f', 0)
.arg(p.size())
.arg(CV, 0, 'f', 1);
@@ -476,7 +476,7 @@ PDModel::calcSummary()
// RMSE
double RMSE=sqrt(mean);
double CV=(RMSE/MEAN) *100;
fitsummary = tr("RMSE %1w CV %3% [envelope] %2 points").arg(RMSE, 0, 'f', 0).arg(data.size()).arg(CV,0,'f',1);
fitsummary = tr("RMSE %1 CV %3% [envelope] %2 points").arg(RMSE, 0, 'f', 0).arg(data.size()).arg(CV,0,'f',1);
}
//
@@ -1274,6 +1274,7 @@ ExtendedModel::deriveExtCPParameters()
(fabs(ecp_dec - ecp_dec_prev) > ecp_dec_delta_max)
);
#if 0
// What did we get ...
// To help debug this below we output the derived values
// commented out for release, its quite a mouthful !
@@ -1288,7 +1289,6 @@ ExtendedModel::deriveExtCPParameters()
(1+ecp_dec*exp(ecp_dec_del/60.0)) *
(1+etau/(60.0));
#if 0
qDebug() <<"eCP(5.3) " << "paa" << paa << "ecp" << ecp << "etau" << etau
<< "paa_dec" << paa_dec << "ecp_del" << ecp_del << "ecp_dec"
<< ecp_dec << "ecp_dec_del" << ecp_dec_del;
@@ -1317,7 +1317,7 @@ ExtendedModel::deriveExtCPParameters()
// RMSE
double RMSE=sqrt(mean);
double CV=(RMSE/MEAN)*100;
fitsummary = tr("RMSE %1w CV %3% [envelope] %2 points").arg(RMSE, 0, 'f', 0).arg(data.size()).arg(CV,0,'f',1);
fitsummary = tr("RMSE %1 CV %3% [envelope] %2 points").arg(RMSE, 0, 'f', 0).arg(data.size()).arg(CV,0,'f',1);
}
QList<QPointF>