Permalink
Browse files

CP Plot show w/kg percentiles

.. should also add 3,7,12,20 concrete percentiles so can review
   regardless of model fit.
  • Loading branch information...
liversedge committed Nov 3, 2018
1 parent c580259 commit b5ed04b7c1f98041a7d798c4796aeca13c8ac65c
Showing with 146 additions and 108 deletions.
  1. +14 −13 src/Charts/CPPlot.cpp
  2. +0 −61 src/Metrics/PDModel.cpp
  3. +0 −31 src/Metrics/PDModel.h
  4. +57 −0 src/Metrics/PowerProfile.cpp
  5. +71 −0 src/Metrics/PowerProfile.h
  6. +4 −3 src/src.pro
@@ -23,6 +23,7 @@
#include "Zones.h"
#include "Colors.h"
#include "CPPlot.h"
#include "PowerProfile.h"
#include "RideCache.h"
#include <QDebug>
@@ -732,7 +733,7 @@ CPPlot::updateModelHelper()
cpw->wprimeTitle->setText(tr("W'"));
if (pdModel->hasWPrime()) {
cpw->wprimeValue->setText(QString(tr("%1 kJ")).arg(pdModel->WPrime() / 1000.0, 0, 'f', 1));
cpw->wprimeRank->setText(PDModel::rank(PDModel::WParm,pdModel->WPrime()));
cpw->wprimeRank->setText(PowerProfile::rank(PowerProfile::abs_w,pdModel->WPrime()));
} else {
cpw->wprimeValue->setText("");
cpw->wprimeRank->setText("");
@@ -741,13 +742,13 @@ CPPlot::updateModelHelper()
//CP
cpw->cpTitle->setText(tr("CP"));
cpw->cpValue->setText(QString(tr("%1 w")).arg(pdModel->CP(), 0, 'f', 0));
cpw->cpRank->setText(PDModel::rank(PDModel::CPParm,pdModel->CP()));
cpw->cpRank->setText(PowerProfile::rank(PowerProfile::abs_cp,pdModel->CP()));
// P-MAX and P-MAX ranking
cpw->pmaxTitle->setText(tr("Pmax"));
if (pdModel->hasPMax()) {
cpw->pmaxValue->setText(QString(tr("%1 w")).arg(pdModel->PMax(), 0, 'f', 0));
cpw->pmaxRank->setText(PDModel::rank(PDModel::PmaxParm, pdModel->PMax()));
cpw->pmaxRank->setText(PowerProfile::rank(PowerProfile::abs_pmax, pdModel->PMax()));
} else {
cpw->pmaxValue->setText(tr("n/a"));
@@ -762,29 +763,29 @@ CPPlot::updateModelHelper()
} else if (rideSeries == RideFile::wattsKg || rideSeries == RideFile::aPowerKg) {
// Reset Rank
cpw->titleRank->setText(tr("Rank"));
cpw->titleRank->setText(tr("Percentile"));
//WPrime
cpw->wprimeTitle->setText(tr("W'"));
if (pdModel->hasWPrime()) cpw->wprimeValue->setText(QString(tr("%1 J/kg")).arg(pdModel->WPrime(), 0, 'f', 0));
else cpw->wprimeValue->setText(tr("n/a"));
if (pdModel->hasWPrime()) {
cpw->wprimeValue->setText(QString(tr("%1 J/kg")).arg(pdModel->WPrime(), 0, 'f', 0));
cpw->wprimeRank->setText(PowerProfile::rank(PowerProfile::wpk_w,pdModel->WPrime()));
} else {
cpw->wprimeValue->setText(tr("n/a"));
cpw->wprimeRank->setText(tr("n/a"));
}
//CP
cpw->cpTitle->setText(tr("CP"));
cpw->cpValue->setText(QString(tr("%1 w/kg")).arg(pdModel->CP(), 0, 'f', 2));
cpw->cpRank->setText(tr("n/a"));
cpw->cpRank->setText(PowerProfile::rank(PowerProfile::wpk_cp, pdModel->CP()));
// P-MAX and P-MAX ranking
cpw->pmaxTitle->setText(tr("Pmax"));
if (pdModel->hasPMax()) {
cpw->pmaxValue->setText(QString(tr("%1 w/kg")).arg(pdModel->PMax(), 0, 'f', 2));
cpw->pmaxRank->setText(PowerProfile::rank(PowerProfile::wpk_pmax, pdModel->PMax()));
// Reference 22.5W/kg -> untrained 8W/kg
int _pMaxLevel = 15 * (pdModel->PMax() - 8) / (23-8) ;
if (_pMaxLevel > 0 && _pMaxLevel < 16) // check bounds
cpw->pmaxRank->setText(QString("%1").arg(_pMaxLevel));
else
cpw->pmaxRank->setText(tr("n/a"));
} else {
cpw->pmaxValue->setText(tr("n/a"));
cpw->pmaxRank->setText(tr("n/a"));
@@ -20,67 +20,6 @@
#include "LTMTrend.h"
#include "lmcurve.h"
struct ztable PD_ZTABLE[] = {
{ -1.282, 10 },
{ -1.04, 15 },
{ -0.842, 20 },
{ -0.674, 25 },
{ -0.524, 30 },
{ -0.39, 35 },
{ -0.253, 40 },
{ -0.13, 45 },
{ 0, 50 },
{ 0.13, 55 },
{ 0.253, 60 },
{ 0.39, 65 },
{ 0.524, 70 },
{ 0.674, 75 },
{ 0.842, 80 },
{ 1.04, 85 },
{ 1.282, 90 },
{ 1.645, 95 },
{ 2.054, 98 },
{ 2.326, 99 },
{ 4.000, 100 },
{ 0, 0 } // tail marker
};
// derived from GC OpenData (see https://osf.io/6hfpz/)
struct power_profile POWER_PROFILE[] = {
{ PDModel::CPParm, 264.5, 58.8 }, // 264.5w is mean wattage
{ PDModel::WParm, 17566.8, 7212.9 }, // 17.5kJ is mean W'
{ PDModel::PmaxParm, 1143.4, 408.0}, // 1143.4 is mean Pmax
{ PDModel::None, 0, 0 }
};
QString
PDModel::rank(PDModel::parmtype type, double value)
{
// is this a supported parmtype, get mu and sigma
double mu=-1, sigma=-1;
for(int i=0; POWER_PROFILE[i].type != PDModel::None; i++) {
if (POWER_PROFILE[i].type == type) {
mu = POWER_PROFILE[i].mu;
sigma = POWER_PROFILE[i].sigma;
break;
}
}
// unknown?
if (mu < 0 && sigma < 0) return ("");
// lets work through the zscore till the value we have
// is no longer within the percentile
QString returning = "10%";
for(int i=0; PD_ZTABLE[i].percentile != 0; i++) {
if (value > (mu + (PD_ZTABLE[i].zscore * sigma)))
returning = QString("%1%").arg(PD_ZTABLE[i+1].percentile);
else
break;
}
return returning;
}
//extern ztable PD_ZTABLE;
// base class for all models
PDModel::PDModel(Context *context) :
@@ -65,8 +65,6 @@ class PDModel : public QObject, public QwtSyntheticPointData
public:
enum parmtype { PmaxParm, CPParm, WParm, None };
enum fittype { Envelope=0, // envelope fit
LeastSquares=1, // uses Levenberg-Marquardt Damped Least Squares
LinearRegression=2 // using slope and intercept of linear regression
@@ -146,9 +144,6 @@ class PDModel : public QObject, public QwtSyntheticPointData
void calcSummary();
QString fitsummary;
// provide a ranking string for a parameter estimate
static QString rank(parmtype t, double value);
protected:
signals:
@@ -485,30 +480,4 @@ class ExtendedModel : public PDModel
void deriveExtCPParameters();
};
// ranking values for CP, Pmax and W' is done against the normalised
// distributions that emerged from analysis of the GC OpenData contributed
// by users since 2018
//
// The mean (mu) and std deviataion (sigma) values describe the normal
// distribution such that percentiled can then be calculated using
// z-scores.
//
// So we record the mu and sigma values for each paramater estimate
// for the general population so we can then rank a user's value
// using z-scores to identify the percentile it is from
struct power_profile {
public:
PDModel::parmtype type;
double mu, sigma;
};
struct ztable {
public:
double zscore;
int percentile; // table has from 10-99 last entry is zero
};
extern power_profile POWER_PROFILE[];
extern ztable PD_ZTABLE[];
#endif
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2018 Mark Liversedge (liversedge@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "PowerProfile.h"
struct PowerProfile powerProfile[]={
// based upon V0.2 of the OpenData Power profile
{ 99.99,593.3736,8.511083789,540.6868,7.28851235,492.9682,6.873482126,459.8246216,6.39594671,436.4760517,6.267013201,39929.81539,661.8325434,2495.3434,39.4455708 },
{ 99,516.48,7.442829332,453.24,6.416262295,438.24,6.136744928,414.4469154,5.8191388,401.9391371,5.671368232,37949.52507,526.9470541,2451.24,34.09693072 },
{ 98,504,7.115806452,440.48,6.199790105,410.24,5.873734266,397.1888316,5.5906764,378.4551804,5.36084711,36252.91351,498.9983697,2395.36,33.06969395 },
{ 95,475.2,6.62504867,407.2,5.804530319,383,5.477230769,367.665045,5.259987,351.772508,4.983962631,32177.61056,442.8424905,2128.3,29.72908576 },
{ 90,446.2,6.284169884,387,5.43251311,363,5.093714286,347.4375,4.883274,332.7666377,4.614217173,28717.99565,393.922918,1932.4,25.92047167 },
{ 85,428.3,5.97338843,369.3,5.153253169,347,4.821903968,335.589169,4.670734,319.7532853,4.437054365,26410.59251,361.8326987,1717.2,23.27294337 },
{ 80,412,5.784285714,356,4.955685624,335,4.680879065,321.606502,4.488434,308.0188973,4.281274492,24281.06186,337.9441078,1489,20.84863362 },
{ 75,399,5.551119403,344,4.834084084,325,4.548088518,313.42792,4.37803,298.845795,4.161000332,22558.67185,316.3123818,1399.5,19.44322992 },
{ 70,387.6,5.401463415,336,4.69396728,318,4.413378026,304.012918,4.232546,289.5113431,4.011858016,21537.10107,294.7501785,1329,18.2034371 },
{ 65,378,5.256818182,330,4.577460467,311,4.305830973,297.52703,4.14874,282.6207701,3.912565095,20453.01805,275.2567854,1266,17.34846154 },
{ 60,372,5.108607079,322,4.455961875,305,4.186619718,292.96867,4.024048,277.1309134,3.814979977,19368.50974,261.6989624,1201,16.43307576 },
{ 55,363,4.96106088,317.9,4.329325735,298,4.071344538,285.933962,3.88735,269.2429466,3.695022723,18423.4887,251.8202718,1160,15.86292898 },
{ 50,354,4.817073171,309,4.207792208,289,3.984375,276.55,3.78647,261.4774677,3.576334131,17279.58259,236.4562234,1120,15.03125 },
{ 45,345,4.702744932,300,4.08385914,282,3.846524785,268.060583,3.693006,254.6373505,3.488324083,16271.61241,224.5534225,1072.08,14.4734879 },
{ 40,335,4.534371513,292,3.986409517,275,3.746516432,262.56617,3.580864,248.752736,3.391197883,15443.15691,211.3098027,1015.2,13.81834851 },
{ 35,325.3,4.376516425,284,3.849315068,266,3.649344054,255.287,3.48246,242.3614973,3.283477464,14439.95701,194.8085742,967.3,13.28068241 },
{ 30,311,4.26407644,274,3.75,258,3.519184613,245.910502,3.374102,233.6858191,3.187564461,13429.78285,176.3658671,927,12.6242049 },
{ 25,301,4.079416623,266,3.619452786,250,3.408604452,239.27083,3.273685,225.986018,3.076581297,12211.28783,163.6486562,867.5,11.79395207 },
{ 20,288.6,3.90972549,256.6,3.462034739,240,3.290151515,229.24517,3.118474,216.91052,2.893825896,10741.54876,146.244684,808.7828,11.00553652 },
{ 15,275.7,3.743016555,245,3.301874235,228.7,3.096475926,218.346829,2.957496,206.7232226,2.766966507,9436.101133,128.6555223,753.4,10.17099286 },
{ 10,257,3.418423665,226.8,3.027478916,213,2.82431746,202.345666,2.697628,191.2953882,2.560877734,8002.893643,108.5086484,667.1616,9.048702554 },
{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }};
QString
PowerProfile::rank(type x, double value)
{
for(int i=0; powerProfile[i].percentile > 0; i++) {
if (value > powerProfile[i].value(x)) {
return QString("%1%").arg(powerProfile[i].percentile);
}
}
return "10%";
}
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2018 Mark Liversedge (liversedge@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <QString>
#ifndef GC_PowerProfile
#define GC_PowerProfile 1
struct PowerProfile {
public:
// when trying to reference the series
enum type { wpk_3min, wpk_7min, wpk_12min, wpk_cp, wpk_w,wpk_pmax,
abs_3min, abs_7min, abs_12min, abs_cp, abs_w, abs_pmax };
double percentile,
_3_min_Power, // members cannot start with a number
_3_min_WPK,
_7_min_Power,
_7_min_WPK,
_12_min_Power,
_12_min_WPK,
_20min_Power,
_20min_WPK,
CP,
CP_wpk,
W,
W_jpk,
Pmax,
Pmax_wpk;
// which value?
double value(type x) {
switch(x) {
case wpk_3min: return _3_min_WPK;
case wpk_7min: return _7_min_WPK;
case wpk_12min: return _12_min_WPK;
case wpk_cp: return CP_wpk;
case wpk_w: return W_jpk;
case wpk_pmax: return Pmax_wpk;
case abs_3min: return _3_min_Power;
case abs_7min: return _7_min_Power;
case abs_12min: return _12_min_Power;
case abs_cp: return CP;
case abs_w: return W;
case abs_pmax: return Pmax;
default: return 0;
}
}
// return a string that ranks the value for the type
static QString rank(type x, double value);
};
typedef PowerProfile PowerProfile;
extern PowerProfile powerProfile[];
#endif
@@ -719,7 +719,7 @@ HEADERS += Gui/AboutDialog.h Gui/AddIntervalDialog.h Gui/AnalysisSidebar.h Gui/C
# metrics and models
HEADERS += Metrics/CPSolver.h Metrics/Estimator.h Metrics/ExtendedCriticalPower.h Metrics/HrZones.h Metrics/PaceZones.h Metrics/PDModel.h \
Metrics/PMCData.h Metrics/RideMetadata.h Metrics/RideMetric.h Metrics/SpecialFields.h Metrics/Statistic.h \
Metrics/PMCData.h Metrics/PowerProfile.h Metrics/RideMetadata.h Metrics/RideMetric.h Metrics/SpecialFields.h Metrics/Statistic.h \
Metrics/UserMetricParser.h Metrics/UserMetricSettings.h Metrics/VDOTCalculator.h Metrics/WPrime.h Metrics/Zones.h
## Planning and Compliance
@@ -818,8 +818,9 @@ SOURCES += Gui/AboutDialog.cpp Gui/AddIntervalDialog.cpp Gui/AnalysisSidebar.cpp
SOURCES += Metrics/aBikeScore.cpp Metrics/aCoggan.cpp Metrics/AerobicDecoupling.cpp Metrics/BasicRideMetrics.cpp Metrics/BikeScore.cpp \
Metrics/Coggan.cpp Metrics/CPSolver.cpp Metrics/DanielsPoints.cpp Metrics/Estimator.cpp Metrics/ExtendedCriticalPower.cpp \
Metrics/GOVSS.cpp Metrics/HrTimeInZone.cpp Metrics/HrZones.cpp Metrics/LeftRightBalance.cpp Metrics/PaceTimeInZone.cpp \
Metrics/PaceZones.cpp Metrics/PDModel.cpp Metrics/PeakPace.cpp Metrics/PeakPower.cpp Metrics/PeakHr.cpp Metrics/PMCData.cpp Metrics/RideMetadata.cpp \
Metrics/RideMetric.cpp Metrics/RunMetrics.cpp Metrics/SwimMetrics.cpp Metrics/SpecialFields.cpp Metrics/Statistic.cpp Metrics/SustainMetric.cpp Metrics/SwimScore.cpp \
Metrics/PaceZones.cpp Metrics/PDModel.cpp Metrics/PeakPace.cpp Metrics/PeakPower.cpp Metrics/PeakHr.cpp Metrics/PMCData.cpp \
Metrics/PowerProfile.cpp Metrics/RideMetadata.cpp Metrics/RideMetric.cpp Metrics/RunMetrics.cpp Metrics/SwimMetrics.cpp \
Metrics/SpecialFields.cpp Metrics/Statistic.cpp Metrics/SustainMetric.cpp Metrics/SwimScore.cpp \
Metrics/TimeInZone.cpp Metrics/TRIMPPoints.cpp Metrics/UserMetric.cpp Metrics/UserMetricParser.cpp Metrics/VDOTCalculator.cpp \
Metrics/VDOT.cpp Metrics/WattsPerKilogram.cpp Metrics/WPrime.cpp Metrics/Zones.cpp Metrics/HrvMetrics.cpp

0 comments on commit b5ed04b

Please sign in to comment.