From a3d123984a4062df7a8b91ef6bb2f363bbfa70f5 Mon Sep 17 00:00:00 2001 From: Damien Date: Mon, 2 Dec 2013 20:25:32 +0100 Subject: [PATCH] ExtendedCriticalPower: Add Model for ExtendedCP - One version of the model (Version 4.3) --- src/CpintPlot.cpp | 80 +++- src/CpintPlot.h | 22 +- src/CriticalPowerWindow.cpp | 66 +++- src/CriticalPowerWindow.h | 27 +- src/ExtendedCriticalPower.cpp | 682 ++++++++++++++++++++++++++++++++++ src/ExtendedCriticalPower.h | 128 +++++++ src/src.pro | 2 + 7 files changed, 989 insertions(+), 18 deletions(-) create mode 100644 src/ExtendedCriticalPower.cpp create mode 100644 src/ExtendedCriticalPower.h diff --git a/src/CpintPlot.cpp b/src/CpintPlot.cpp index 66a44788e..8348b721a 100644 --- a/src/CpintPlot.cpp +++ b/src/CpintPlot.cpp @@ -81,7 +81,7 @@ CpintPlot::CpintPlot(Context *context, QString p, const Zones *zones, bool range curveTitle.attach(this); curveTitle.setXValue(5); - curveTitle.setYValue(20); + curveTitle.setYValue(60); curveTitle.setLabel(QwtText("", QwtText::PlainText)); // default to no title zoomer = new penTooltip(static_cast(this->canvas())); @@ -93,6 +93,8 @@ CpintPlot::CpintPlot(Context *context, QString p, const Zones *zones, bool range connect(canvasPicker, SIGNAL(pointHover(QwtPlotCurve*, int)), this, SLOT(pointHover(QwtPlotCurve*, int))); configChanged(); // apply colors + + ecp = new ExtendedCriticalPower(context); } void @@ -329,6 +331,8 @@ CpintPlot::deriveCPParameters() ); } + + void CpintPlot::plot_CP_curve(CpintPlot *thisPlot, // the plot we're currently displaying double cp, @@ -395,7 +399,7 @@ CpintPlot::plot_CP_curve(CpintPlot *thisPlot, // the plot we're currently di if (series == RideFile::wattsKg) curveTitle.setYValue(0.6); else - curveTitle.setYValue(20); + curveTitle.setYValue(70); CPCurve = new QwtPlotCurve(curve_title); if (appsettings->value(this, GC_ANTIALIAS, false).toBool() == true) @@ -406,6 +410,46 @@ CpintPlot::plot_CP_curve(CpintPlot *thisPlot, // the plot we're currently di CPCurve->setPen(pen); CPCurve->setSamples(cp_curve_time.data(), cp_curve_power.data(), curve_points); CPCurve->attach(thisPlot); + + + // Extended CP 2 + /*if (extendedCPCurve2) { + delete extendedCPCurve2; + extendedCPCurve2 = NULL; + } + extendedCPCurve2 = ecp->getPlotCurveForExtendedCP_2_3(athleteModeleCP2); + extendedCPCurve2->attach(thisPlot); + + if (extendedCurveTitle) { + delete extendedCurveTitle; + extendedCurveTitle = NULL; + } + extendedCurveTitle = ecp->getPlotMarkerForExtendedCP_2_3(athleteModeleCP2); + extendedCurveTitle->setXValue(5); + extendedCurveTitle->setYValue(45); + extendedCurveTitle->attach(thisPlot);*/ + + + // Extended CP 4 + if (extendedCPCurve4) { + delete extendedCPCurve4; + extendedCPCurve4 = NULL; + } + if (extendedCurveTitle2) { + delete extendedCurveTitle2; + extendedCurveTitle2 = NULL; + } + + if (useExtendedCP) { + extendedCPCurve4 = ecp->getPlotCurveForExtendedCP_4_3(athleteModeleCP4); + extendedCPCurve4->attach(thisPlot); + + + extendedCurveTitle2 = ecp->getPlotMarkerForExtendedCP_4_3(athleteModeleCP4); + extendedCurveTitle2->setXValue(5); + extendedCurveTitle2->setYValue(20); + extendedCurveTitle2->attach(thisPlot); + } } void @@ -601,6 +645,22 @@ CpintPlot::calculate(RideItem *rideItem) // calculate CP model from all-time best data cp = tau = t0 = 0; deriveCPParameters(); + + if (useExtendedCP) { + // calculate extended CP model from all-time best data + //athleteModeleCP2 = ecp->deriveExtendedCP_2_3_Parameters(bests, series, sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2); + + + athleteModeleCP4 = ecp->deriveExtendedCP_4_3_Parameters(true, bests, series, sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2); + + + /*double best5sec = context->ride->ride()->getWeight() * 24.04; + double best1min = context->ride->ride()->getWeight() * 11.50; + double best5min = context->ride->ride()->getWeight() * 7.60; + double best1hour = context->ride->ride()->getWeight() * 6.40; + //worldClassModeleCP2 = ecp->deriveExtendedCP_2_3_ParametersForBest(best5sec, best1min, best5min, best1hour); + worldClassModeleCP4 = ecp->deriveExtendedCP_4_3_ParametersForBest(best5sec, best1min, best5min, best1hour);*/ + } } // @@ -858,14 +918,20 @@ CpintPlot::setShadeMode(int x) // model parameters! void -CpintPlot::setModel(int i1, int i2, int i3, int i4, bool useT0) +CpintPlot::setModel(int sanI1, int sanI2, int anI1, int anI2, int aeI1, int aeI2, int laeI1, int laeI2, bool useT0, bool useExtendedCP) { - anI1 = double(i1) / double(60.00f); - anI2 = double(i2) / double(60.00f); - aeI1 = double(i3) / double(60.00f); - aeI2 = double(i4) / double(60.00f); + this->anI1 = double(anI1) / double(60.00f); + this->anI2 = double(anI2) / double(60.00f); + this->aeI1 = double(aeI1) / double(60.00f); + this->aeI2 = double(aeI2) / double(60.00f); + + this->sanI1 = double(sanI1) / double(60.00f); + this->sanI2 = double(sanI2) / double(60.00f); + this->laeI1 = double(laeI1) / double(60.00f); + this->laeI2 = double(laeI2) / double(60.00f); this->useT0 = useT0; + this->useExtendedCP = useExtendedCP; // wipe away previous effort if (CPCurve) { diff --git a/src/CpintPlot.h b/src/CpintPlot.h index ba5c3ea09..bacecf278 100644 --- a/src/CpintPlot.h +++ b/src/CpintPlot.h @@ -21,7 +21,7 @@ #include "GoldenCheetah.h" #include "RideFileCache.h" - +#include "ExtendedCriticalPower.h" #include #include #include @@ -89,15 +89,23 @@ class CpintPlot : public QwtPlot const QwtPlotCurve *getThisCurve() const { return thisCurve; } const QwtPlotCurve *getCPCurve() const { return CPCurve; } - void setModel(int i1, int i2, int i3, int i4, bool useT0); + void setModel(int sanI1, int sanI2, int anI1, int anI2, int aeI1, int aeI2, int laeI1, int laeI2, bool useT0, bool useExtendedCP); // model type & intervals - bool useT0; - int anI1, anI2, aeI1, aeI2; + bool useT0, useExtendedCP; + double sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2; double cp, tau, t0; // CP model parameters + + Model_eCP athleteModeleCP2; + Model_eCP athleteModeleCP4; + Model_eCP worldClassModeleCP2; + Model_eCP worldClassModeleCP4; + double shadingCP; // the CP value we use to draw the shade void deriveCPParameters(); + void deriveExtendedCPParameters(); + void changeSeason(const QDate &start, const QDate &end); void setAxisTitle(int axis, QString label); void setSeries(RideFile::SeriesType); @@ -125,10 +133,11 @@ class CpintPlot : public QwtPlot QString path; QwtPlotCurve *thisCurve; - QwtPlotCurve *CPCurve; + QwtPlotCurve *CPCurve, *extendedCPCurve2, *extendedCPCurve4; QList allCurves; QwtPlotCurve *allCurve; // bests but not zoned QwtPlotMarker curveTitle; + QwtPlotMarker *extendedCurveTitle, *extendedCurveTitle2; QList allZoneLabels; void clear_CP_Curves(); QStringList filterForSeason(QStringList cpints, QDate startDate, QDate endDate); @@ -141,6 +150,8 @@ class CpintPlot : public QwtPlot void refreshReferenceLines(RideItem*); QList referenceLines; + ExtendedCriticalPower *ecp; + RideFileCache *current, *bests; LTMCanvasPicker *canvasPicker; penTooltip *zoomer; @@ -150,6 +161,7 @@ class CpintPlot : public QwtPlot int shadeMode; bool rangemode; + }; #endif // _GC_CpintPlot_h diff --git a/src/CriticalPowerWindow.cpp b/src/CriticalPowerWindow.cpp index 11e54d6b8..297137002 100644 --- a/src/CriticalPowerWindow.cpp +++ b/src/CriticalPowerWindow.cpp @@ -201,7 +201,7 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, Context *context, boo QHBoxLayout *anLayout = new QHBoxLayout; anLayout->addWidget(anI1SpinBox); anLayout->addWidget(anI2SpinBox); - cl->addRow(new QLabel(tr("Interval 1 (seconds)")), anLayout); + cl->addRow(new QLabel(tr("Anaerobic search interval (seconds)")), anLayout); aeI1SpinBox = new QDoubleSpinBox(this); aeI1SpinBox->setDecimals(0); @@ -217,12 +217,57 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, Context *context, boo aeI2SpinBox->setMaximum(3600); aeI2SpinBox->setSingleStep(1.0); aeI2SpinBox->setAlignment(Qt::AlignRight); - aeI2SpinBox->setValue(3600); // 30 minutes + aeI2SpinBox->setValue(3600); // 60 minutes QHBoxLayout *aeLayout = new QHBoxLayout; aeLayout->addWidget(aeI1SpinBox); aeLayout->addWidget(aeI2SpinBox); - cl->addRow(new QLabel(tr("Interval 2 (seconds)")), aeLayout); + cl->addRow(new QLabel(tr("Aerobic search interval (seconds)")), aeLayout); + + ckExtendedCP = new QCheckBox(this); + cl->addRow(new QLabel(tr("Use Extended CP Model")), ckExtendedCP); + + sanI1SpinBox = new QDoubleSpinBox(this); + sanI1SpinBox->setDecimals(0); + sanI1SpinBox->setMinimum(15); + sanI1SpinBox->setMaximum(45); + sanI1SpinBox->setSingleStep(1.0); + sanI1SpinBox->setAlignment(Qt::AlignRight); + sanI1SpinBox->setValue(30); // 30 secs + + sanI2SpinBox = new QDoubleSpinBox(this); + sanI2SpinBox->setDecimals(0); + sanI2SpinBox->setMinimum(15); + sanI2SpinBox->setMaximum(300); + sanI2SpinBox->setSingleStep(1.0); + sanI2SpinBox->setAlignment(Qt::AlignRight); + sanI2SpinBox->setValue(60); // 100 secs + + QHBoxLayout *sanLayout = new QHBoxLayout(this); + sanLayout->addWidget(sanI1SpinBox); + sanLayout->addWidget(sanI2SpinBox); + cl->addRow(new QLabel(tr("Short anaerobic search interval (seconds)")), sanLayout); + + laeI1SpinBox = new QDoubleSpinBox(this); + laeI1SpinBox->setDecimals(0); + laeI1SpinBox->setMinimum(3000); + laeI1SpinBox->setMaximum(9000); + laeI1SpinBox->setSingleStep(1.0); + laeI1SpinBox->setAlignment(Qt::AlignRight); + laeI1SpinBox->setValue(3000); + + laeI2SpinBox = new QDoubleSpinBox(this); + laeI2SpinBox->setDecimals(0); + laeI2SpinBox->setMinimum(4000); + laeI2SpinBox->setMaximum(30000); + laeI2SpinBox->setSingleStep(1.0); + laeI2SpinBox->setAlignment(Qt::AlignRight); + laeI2SpinBox->setValue(30000); + + QHBoxLayout *laeLayout = new QHBoxLayout(this); + laeLayout->addWidget(laeI1SpinBox); + laeLayout->addWidget(laeI2SpinBox); + cl->addRow(new QLabel(tr("Long aerobic search interval (seconds)")), laeLayout); // point 2 + 3 -or- point 1 + 2 in a 2 point model @@ -253,6 +298,12 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, Context *context, boo connect(anI2SpinBox, SIGNAL(valueChanged(double)), this, SLOT(modelParametersChanged())); connect(aeI1SpinBox, SIGNAL(valueChanged(double)), this, SLOT(modelParametersChanged())); connect(aeI2SpinBox, SIGNAL(valueChanged(double)), this, SLOT(modelParametersChanged())); + connect(sanI1SpinBox, SIGNAL(valueChanged(double)), this, SLOT(modelParametersChanged())); + connect(sanI2SpinBox, SIGNAL(valueChanged(double)), this, SLOT(modelParametersChanged())); + connect(laeI1SpinBox, SIGNAL(valueChanged(double)), this, SLOT(modelParametersChanged())); + connect(laeI2SpinBox, SIGNAL(valueChanged(double)), this, SLOT(modelParametersChanged())); + connect(ckExtendedCP, SIGNAL(stateChanged(int)), this, SLOT(modelParametersChanged())); + // redraw on config change -- this seems the simplest approach connect(context, SIGNAL(filterChanged()), this, SLOT(forceReplot())); @@ -320,11 +371,16 @@ CriticalPowerWindow::modelParametersChanged() if (active == true) return; // tell the plot - cpintPlot->setModel(anI1SpinBox->value(), + cpintPlot->setModel(sanI1SpinBox->value(), + sanI2SpinBox->value(), + anI1SpinBox->value(), anI2SpinBox->value(), aeI1SpinBox->value(), aeI2SpinBox->value(), - modelCombo->currentIndex() > 0 ? true : false); // true=use 3point model + laeI1SpinBox->value(), + laeI2SpinBox->value(), + modelCombo->currentIndex() > 0 ? true : false, + ckExtendedCP->checkState()); // and apply if (amVisible() && myRideItem != NULL) { diff --git a/src/CriticalPowerWindow.h b/src/CriticalPowerWindow.h index 4376a5db2..dc60d5d83 100644 --- a/src/CriticalPowerWindow.h +++ b/src/CriticalPowerWindow.h @@ -55,6 +55,12 @@ class CriticalPowerWindow : public GcChartWindow Q_PROPERTY(int ani2 READ anI2 WRITE setAnI2 USER true) Q_PROPERTY(int aei1 READ aeI1 WRITE setAeI1 USER true) Q_PROPERTY(int aei2 READ aeI2 WRITE setAeI2 USER true) + Q_PROPERTY(int sani1 READ sanI1 WRITE setSanI1 USER true) + Q_PROPERTY(int sani2 READ sanI2 WRITE setSanI2 USER true) + Q_PROPERTY(int laei1 READ laeI1 WRITE setLaeI1 USER true) + Q_PROPERTY(int laei2 READ laeI2 WRITE setLaeI2 USER true) + Q_PROPERTY(int laei2 READ laeI2 WRITE setLaeI2 USER true) + Q_PROPERTY(int useExtendedCP READ useExtendedCP WRITE setUseExtendedCP USER true) Q_PROPERTY(QDate fromDate READ fromDate WRITE setFromDate USER true) Q_PROPERTY(QDate toDate READ toDate WRITE setToDate USER true) @@ -111,6 +117,21 @@ class CriticalPowerWindow : public GcChartWindow int aeI2() const { return aeI2SpinBox->value(); } void setAeI2(int x) { return aeI2SpinBox->setValue(x); } + int sanI1() const { return sanI1SpinBox->value(); } + void setSanI1(int x) { return sanI1SpinBox->setValue(x); } + + int sanI2() const { return sanI2SpinBox->value(); } + void setSanI2(int x) { return sanI2SpinBox->setValue(x); } + + int laeI1() const { return laeI1SpinBox->value(); } + void setLaeI1(int x) { return laeI1SpinBox->setValue(x); } + + int laeI2() const { return laeI2SpinBox->value(); } + void setLaeI2(int x) { return laeI2SpinBox->setValue(x); } + + int useExtendedCP() const { return ckExtendedCP->checkState(); } + void setUseExtendedCP(int x) { return ckExtendedCP->setChecked(x); } + RideFile::SeriesType series() { return static_cast (seriesCombo->itemData(seriesCombo->currentIndex()).toInt()); @@ -182,6 +203,7 @@ class CriticalPowerWindow : public GcChartWindow QComboBox *modelCombo; QComboBox *cComboSeason; QComboBox *shadeCombo; + QCheckBox *ckExtendedCP; QwtPlotPicker *picker; void addSeries(); Seasons *seasons; @@ -193,7 +215,10 @@ class CriticalPowerWindow : public GcChartWindow #endif QList intervalCurves; - QDoubleSpinBox *anI1SpinBox, *anI2SpinBox, *aeI1SpinBox, *aeI2SpinBox; + QDoubleSpinBox *sanI1SpinBox, *sanI2SpinBox; + QDoubleSpinBox *anI1SpinBox, *anI2SpinBox; + QDoubleSpinBox *aeI1SpinBox, *aeI2SpinBox; + QDoubleSpinBox *laeI1SpinBox, *laeI2SpinBox; bool rangemode; bool isfiltered; diff --git a/src/ExtendedCriticalPower.cpp b/src/ExtendedCriticalPower.cpp new file mode 100644 index 000000000..d85457cd7 --- /dev/null +++ b/src/ExtendedCriticalPower.cpp @@ -0,0 +1,682 @@ +/* + * Copyright (c) 2013 Damien Grauser (Damien.Grauser@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 "ExtendedCriticalPower.h" +#include "Colors.h" +#include "Settings.h" +#include "RideFileCache.h" + +#include + +ExtendedCriticalPower::ExtendedCriticalPower(Context *context) : context(context) +{ +} + +ExtendedCriticalPower::~ExtendedCriticalPower() +{ +} + +Model_eCP +ExtendedCriticalPower::deriveExtendedCP_2_3_Parameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2) +{ + Model_eCP model; + model.version = "eCP v2.3"; + + // bounds on anaerobic interval in minutes + const double t1 = sanI1; + const double t2 = sanI2; + + // bounds on anaerobic interval in minutes + const double t3 = anI1; + const double t4 = anI2; + + // bounds on aerobic interval in minutes + const double t5 = aeI1; + const double t6 = aeI2; + + // bounds on long aerobic interval in minutes + const double t7 = laeI1; + const double t8 = laeI2; + + // bounds of these time valus in the data + int i1, i2, i3, i4, i5, i6, i7, i8; + + // find the indexes associated with the bounds + // the first point must be at least the minimum for the anaerobic interval, or quit + for (i1 = 0; i1 < 60 * t1; i1++) + if (i1 + 1 >= bests->meanMaxArray(series).size()) + return model; + // the second point is the maximum point suitable for anaerobicly dominated efforts. + for (i2 = i1; i2 + 1 <= 60 * t2; i2++) + if (i2 + 1 >= bests->meanMaxArray(series).size()) + return model; + + // the third point is the beginning of the minimum duration for aerobic efforts + for (i3 = i2; i3 < 60 * t3; i3++) + if (i3 + 1 >= bests->meanMaxArray(series).size()) + return model; + for (i4 = i3; i4 + 1 <= 60 * t4; i4++) + if (i4 + 1 >= bests->meanMaxArray(series).size()) + break; + + // the fifth point is the beginning of the minimum duration for aerobic efforts + for (i5 = i4; i5 < 60 * t5; i5++) + if (i5 + 1 >= bests->meanMaxArray(series).size()) + return model; + for (i6 = i5; i6 + 1 <= 60 * t6; i6++) + if (i6 + 1 >= bests->meanMaxArray(series).size()) + break; + + // the first point must be at least the minimum for the anaerobic interval, or quit + for (i7 = i6; i7 < 60 * t7; i7++) + if (i7 + 1 >= bests->meanMaxArray(series).size()) + return model; + // the second point is the maximum point suitable for anaerobicly dominated efforts. + for (i8 = i7; i8 + 1 <= 60 * t8; i8++) + if (i8 + 1 >= bests->meanMaxArray(series).size()) + break; + + bool withoutEcpDec = false; + + // initial estimate of tau + if (model.paa == 0) + model.paa = 300; + + if (model.paa_dec == 0) + model.paa_dec = -2; + + if (model.etau == 0) + model.etau = 1; + + if (model.ecp == 0) + model.ecp = 300; + + if (model.ecp_del == 0) + model.ecp_del = -1; + + if (model.ecp_dec == 0) + model.ecp_dec = -0.2; + + if (model.ecp_dec_del == 0) + model.ecp_dec_del = -180; + + + // lower bound on tau + const double paa_min = 100; + //const double paa_max = 2000; + const double etau_min = 0.5; + const double paa_dec_max = -0.25; + const double paa_dec_min = -3; + const double ecp_dec_min = -1.5; + + // convergence delta for tau + //const double ecp_delta_max = 1e-4; + const double etau_delta_max = 1e-4; + const double paa_delta_max = 1e-2; + const double paa_dec_delta_max = 1e-4; + const double ecp_del_delta_max = 1e-4; + const double ecp_dec_delta_max = 1e-8; + + // previous loop value of tau and t0 + double etau_prev; + double paa_prev; + double paa_dec_prev; + double ecp_del_prev; + double ecp_dec_prev; + + // maximum number of loops + const int max_loops = 100; + + // loop to convergence + int iteration = 0; + do { + if (iteration ++ > max_loops) { + QMessageBox::warning( + NULL, "Warning", + QString("Maximum number of loops %1 exceeded in ecp2 model" + "extraction").arg(max_loops), + QMessageBox::Ok, + QMessageBox::NoButton); + break; + } + + // record the previous version of tau, for convergence + etau_prev = model.etau; + paa_prev = model.paa; + paa_dec_prev = model.paa_dec; + ecp_del_prev = model.ecp_del; + ecp_dec_prev = model.ecp_dec; + + + if (withoutEcpDec) + model.ecp_dec = 0; + + // P = paa*exp(paa_dec*(x/60)) + ecp * (1-exp(ecp_del*x/60)) * (1+ecp_dec*exp(-180/x/60) (1 + etau/(x/60)) + // bests->meanMaxArray(series)[i] = paa*exp(paa_dec*(i/60.0)) + ecp * (1-exp(ecp*i/60.0)) * (exp(40+ecp_dec*i/60.0)) * ( 1 + etau/(i/60.0)); + + // estimate ecp + int i; + model.ecp = 0; + for (i = i5; i <= i6; i++) { + double ecpn = (bests->meanMaxArray(series)[i] - model.paa*exp(model.paa_dec*(i/60.0))) / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) / ( 1 + model.etau/(i/60.0)); + + if (model.ecp < ecpn) + model.ecp = ecpn; + } + //qDebug() << "estimate ecp" << model.ecp; + + // if ecp = 0; no valid data; give up + if (model.ecp == 0.0) + return model; + + // estimate etau + model.etau = etau_min; + for (i = i3; i <= i4; i++) { + double etaun = ((bests->meanMaxArray(series)[i] - model.paa*exp(model.paa_dec*(i/60.0))) /model. ecp / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) - 1) * (i/60.0); + + if (model.etau < etaun) + model.etau = etaun; + } + //qDebug() << "estimate etau" << model.etau; + + model.paa_dec = paa_dec_min; + for (i = i1; i <= i2; i++) { + double paa_decn = log((bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0)) ) / model.paa) / (i/60.0); + if (model.paa_dec < paa_decn && paa_decn < paa_dec_max) { + model.paa_dec = paa_decn; + } + } + //qDebug() << "estimate paa_dec" << model.paa_dec; + + model.paa = paa_min; + double _avg_paa = 0.0; + int count=1; + for (i = 1; i <= 8; i++) { + double paan = (bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0))) / exp(model.paa_dec*(i/60.0)); + _avg_paa = (double)((count-1)*_avg_paa+paan)/count; + + if (model.paa < paan) + model.paa = paan; + + count++; + } + //qDebug() << "estimate paa" << model.paa; + + // Max power can be wrong data + // Use avg if the diff is too big + if (_avg_paa<0.95*model.paa) { + model.paa = _avg_paa; + //qDebug() << "use avg paa" << model.paa; + } + + if (!withoutEcpDec) { + model.ecp_dec = ecp_dec_min; + for (i = i7; i <= i8; i=i+120) { + double ecp_decn = ((bests->meanMaxArray(series)[i] - model.paa*exp(model.paa_dec*(i/60.0))) / model.ecp / (1-exp(model.ecp_del*i/60.0)) / ( 1 + model.etau/(i/60.0)) - 1) / exp(model.ecp_dec_del/(i / 60.0)); + + if (ecp_decn > 0) + ecp_decn = 0; + + if (model.ecp_dec < ecp_decn) + model.ecp_dec = ecp_decn; + } + //qDebug() << "estimate ecp_dec" << model.ecp_dec; + } + } while ((fabs(model.etau - etau_prev) > etau_delta_max) || + (fabs(model.paa - paa_prev) > paa_delta_max) || + (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max) || + (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max) || + (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max) + ); + + //qDebug() << iteration << "iterations"; + + model.pMax = model.paa*exp(model.paa_dec*(1/60.0)) + model.ecp * (1-exp(model.ecp_del*(1/60.0))) * (1+model.ecp_dec*exp(model.ecp_dec_del/(1/60.0))) * ( 1 + model.etau/(1/60.0)); + model.cp60 = model.paa*exp(model.paa_dec*(60.0)) + model.ecp * (1-exp(model.ecp_del*60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(60.0))) * ( 1 + model.etau/(60.0)); + + qDebug() <<"eCP(2.3) " << "paa" << model.paa << "paa_dec" << model.paa_dec << "ecp" << model.ecp << "etau" << model.etau << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec << "ecp_dec_del" << model.ecp_dec_del; + qDebug() <<"eCP(2.3) " << "pmax" << model.pMax << "cp60" << model.cp60; + + return model; +} + +QwtPlotCurve* +ExtendedCriticalPower::getPlotCurveForExtendedCP_2_3(Model_eCP model) +{ + const int extendedCurve2_points = 1000; + + QVector extended_cp_curve2_power(extendedCurve2_points); + QVector extended_cp_curve2_time(extendedCurve2_points); + double tmin = 1.0/60; + double tmax = 600; + + for (int i = 0; i < extendedCurve2_points; i ++) { + double x = (double) i / (extendedCurve2_points - 1); + double t = pow(tmax, x) * pow(tmin, 1-x); + extended_cp_curve2_time[i] = t; + extended_cp_curve2_power[i] = model.paa * exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t)); + } + + QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP"); + if (appsettings->value(NULL, GC_ANTIALIAS, false).toBool() == true) + extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased); + QPen e2pen(GColor(CHEARTRATE)); + e2pen.setWidth(1); + e2pen.setStyle(Qt::DashLine); + extendedCPCurve2->setPen(e2pen); + extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points); + + return extendedCPCurve2; +} + +Model_eCP +ExtendedCriticalPower::deriveExtendedCP_4_3_Parameters(bool usebest, RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2) +{ + Model_eCP model; + model.version = "eCP v2.3"; + + // bounds on anaerobic interval in minutes + const double t1 = sanI1; + const double t2 = sanI2; + + // bounds on anaerobic interval in minutes + const double t3 = anI1; + const double t4 = anI2; + + // bounds on aerobic interval in minutes + const double t5 = aeI1; + const double t6 = aeI2; + + // bounds on long aerobic interval in minutes + const double t7 = laeI1; + const double t8 = laeI2; + + // bounds of these time valus in the data + int i1, i2, i3, i4, i5, i6, i7, i8; + + // find the indexes associated with the bounds + // the first point must be at least the minimum for the anaerobic interval, or quit + for (i1 = 0; i1 < 60 * t1; i1++) + if (i1 + 1 >= bests->meanMaxArray(series).size()) + return model; + // the second point is the maximum point suitable for anaerobicly dominated efforts. + for (i2 = i1; i2 + 1 <= 60 * t2; i2++) + if (i2 + 1 >= bests->meanMaxArray(series).size()) + return model; + + // the third point is the beginning of the minimum duration for aerobic efforts + for (i3 = i2; i3 < 60 * t3; i3++) + if (i3 + 1 >= bests->meanMaxArray(series).size()) + return model; + for (i4 = i3; i4 + 1 <= 60 * t4; i4++) + if (i4 + 1 >= bests->meanMaxArray(series).size()) + break; + + // the fifth point is the beginning of the minimum duration for aerobic efforts + for (i5 = i4; i5 < 60 * t5; i5++) + if (i5 + 1 >= bests->meanMaxArray(series).size()) + return model; + for (i6 = i5; i6 + 1 <= 60 * t6; i6++) + if (i6 + 1 >= bests->meanMaxArray(series).size()) + break; + + // the first point must be at least the minimum for the anaerobic interval, or quit + for (i7 = i6; i7 < 60 * t7; i7++) + if (i7 + 1 >= bests->meanMaxArray(series).size()) + return model; + // the second point is the maximum point suitable for anaerobicly dominated efforts. + for (i8 = i7; i8 + 1 <= 60 * t8; i8++) + if (i8 + 1 >= bests->meanMaxArray(series).size()) + break; + + bool withoutEd = false; + + // initial estimate of tau + if (model.paa == 0) + model.paa = 300; + + if (model.etau == 0) + model.etau = 1; + + if (model.ecp == 0) + model.ecp = 300; + + if (model.paa_dec == 0) + model.paa_dec = -2; + + if (model.ecp_del == 0) + model.ecp_del = -1; + + if (model.ecp_dec == 0) + model.ecp_dec = -1; + + if (model.ecp_dec_del == 0) + model.ecp_dec_del = -180; + + + // lower bound on tau + const double paa_min = 100; + const double paa_max = 2000; + const double etau_min = 0.5; + const double paa_dec_max = -0.25; + const double paa_dec_min = -3; + const double ecp_dec_min = -5; // Long + + // convergence delta for tau + //const double ecp_delta_max = 1e-4; + const double etau_delta_max = 1e-4; + const double paa_delta_max = 1e-2; + const double paa_dec_delta_max = 1e-4; + const double ecp_del_delta_max = 1e-4; + const double ecp_dec_delta_max = 1e-8; + + // previous loop value of tau and t0 + double etau_prev; + double paa_prev; + double paa_dec_prev; + double ecp_del_prev; + double ecp_dec_prev; + + // maximum number of loops + const int max_loops = 100; + + // loop to convergence + int iteration = 0; + do { + if (iteration ++ > max_loops) { + QMessageBox::warning( + NULL, "Warning", + QString("Maximum number of loops %1 exceeded in ecp5 model" + "extraction").arg(max_loops), + QMessageBox::Ok, + QMessageBox::NoButton); + break; + } + + // record the previous version of tau, for convergence + etau_prev = model.etau; + paa_prev = model.paa; + paa_dec_prev = model.paa_dec; + ecp_del_prev = model.ecp_del; + ecp_dec_prev = model.ecp_dec; + + if (withoutEd) + model.ecp_dec = 0; + + // P = paa* (2.0-exp(-1*(x/60.0)))*exp(paa_dec*(x/60)) + ecp * (1-exp(ecp_del*x/60)) * (1+ecp_dec*exp(-180/x/60) (1 + etau/(x/60)) + // bests->meanMaxArray(series)[i] = paa*(2.0-exp(-1*(i/60.0)))*exp(paa_dec*(i/60.0)) + ecp * (1-exp(ecp*i/60.0)) * (1+2d*exp(180/i/60.0)) * ( 1 + etau/(i/60.0)); + + // estimate ecp + int i; + + model.ecp = 0; + double _avg_ecp = 0.0; + int count=1; + for (i = i5; i <= i6; i++) { + double ecpn = (bests->meanMaxArray(series)[i] - model.paa * (2.0-exp(-1*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) / ( 1 + model.etau/(i/60.0)); + _avg_ecp = (double)((count-1)*_avg_ecp+ecpn)/count; + + if (model.ecp < ecpn) + model.ecp = ecpn; + + count++; + } + //qDebug() << "estimate ecp" << model.ecp; + if (!usebest) { + model.ecp = _avg_ecp; + } + + // if ecp = 0; no valid data; give up + if (model.ecp == 0.0) + return model; + + // estimate etau + model.etau = etau_min; + double _avg_etau = 0.0; + count=1; + for (i = i3; i <= i4; i++) { + double etaun = ((bests->meanMaxArray(series)[i] - model.paa * (2.0-exp(-1*(i/60.0))) * exp(model.paa_dec*(i/60.0))) /model. ecp / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) - 1) * (i/60.0); + _avg_etau = (double)((count-1)*_avg_etau+etaun)/count; + + if (model.etau < etaun) + model.etau = etaun; + count++; + } + //qDebug() << "estimate etau" << model.etau; + if (!usebest) { + model.etau = _avg_etau; + } + + + + model.paa_dec = paa_dec_min; + double _avg_paa_dec = 0.0; + count=1; + for (i = i1; i <= i2; i++) { + double paa_decn = log((bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0)) ) / model.paa / (2.0-exp(-1*(i/60.0))) ) / (i/60.0); + _avg_paa_dec = (double)((count-1)*_avg_paa_dec+paa_decn)/count; + + if (model.paa_dec < paa_decn && paa_decn < paa_dec_max) { + model.paa_dec = paa_decn; + } + count++; + } + //qDebug() << "estimate paa_dec" << model.paa_dec; + if (!usebest) { + model.paa_dec = _avg_paa_dec; + } + + model.paa = paa_min; + double _avg_paa = 0.0; + count=1; + for (i = 1; i <= 8; i++) { + double paan = (bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0))) / exp(model.paa_dec*(i/60.0)) / (2.0-exp(-1*(i/60.0))); + _avg_paa = (double)((count-1)*_avg_paa+paan)/count; + + //qDebug() << " estimate paan" << paan; + if (model.paa < paan) + model.paa = paan; + count++; + } + //qDebug() << "estimate paa" << model.paa; + if (!usebest || _avg_paa<0.9*model.paa) { + model.paa = _avg_paa; + } + + if (!withoutEd) { + model.ecp_dec = ecp_dec_min; + double _avg_ecp_dec = 0.0; + count=1; + for (i = i7; i <= i8; i=i+120) { + double ecp_decn = ((bests->meanMaxArray(series)[i] - model.paa * (2.0-exp(-1*(i/60.0)))*exp(model.paa_dec*(i/60.0))) / model.ecp / (1-exp(model.ecp_del*i/60.0)) / ( 1 + model.etau/(i/60.0)) -1 ) / exp(model.ecp_dec_del/(i / 60.0)); + _avg_ecp_dec = (double)((count-1)*_avg_ecp_dec+ecp_decn)/count; + + if (ecp_decn > 0) + ecp_decn = 0; + + if (model.ecp_dec < ecp_decn) + model.ecp_dec = ecp_decn; + count++; + } + //qDebug() << "estimate ecp_dec" << model.ecp_dec; + if (!usebest) { + model.ecp_dec = _avg_ecp_dec; + } + + } + } while ((fabs(model.etau - etau_prev) > etau_delta_max) || + (fabs(model.paa - paa_prev) > paa_delta_max) || + (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max) || + (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max) || + (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max) + ); + + //qDebug() << iteration << "iterations"; + + model.pMax = model.paa*(2.0-exp(-1*(1/60.0)))*exp(model.paa_dec*(1/60.0)) + model.ecp * (1-exp(model.ecp_del*(1/60.0))) * (1+model.ecp_dec*exp(model.ecp_dec_del/(1/60.0))) * ( 1 + model.etau/(1/60.0)); + model.cp60 = model.paa*(2.0-exp(-1*60.0))*exp(model.paa_dec*(60.0)) + model.ecp * (1-exp(model.ecp_del*60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/60.0)) * ( 1 + model.etau/(60.0)); + + qDebug() <<"eCP(4.3) " << "paa" << model.paa << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec << "ecp_dec_del" << model.ecp_dec_del; + qDebug() <<"eCP(4.3) " << "pmax" << model.pMax << "cp60" << model.cp60; + + return model; +} + +Model_eCP +ExtendedCriticalPower::deriveExtendedCP_4_3_ParametersForBest(double best5s, double best1min, double best5min, double best1hour) +{ + Model_eCP model; + + bool withoutEd = true; + + // initial estimate of tau + model.paa = 300; + model.etau = 1; + model.ecp = 300; + model.paa_dec = -2; + model.ecp_del = -1; + model.ecp_dec = 0; + + // convergence delta for tau + //const double ecp_delta_max = 1e-4; + const double etau_delta_max = 1e-4; + const double paa_delta_max = 1e-2; + const double paa_dec_delta_max = 1e-4; + const double ecp_del_delta_max = 1e-4; + const double ecp_dec_delta_max = 1e-8; + + // previous loop value of tau and t0 + double etau_prev; + double paa_prev; + double paa_dec_prev; + double ecp_del_prev; + double ecp_dec_prev; + + // maximum number of loops + const int max_loops = 100; + + // loop to convergence + int iteration = 0; + do { + if (iteration ++ > max_loops) { + QMessageBox::warning( + NULL, "Warning", + QString("Maximum number of loops %1 exceeded in ecp2 model" + "extraction").arg(max_loops), + QMessageBox::Ok, + QMessageBox::NoButton); + break; + } + + if (model.paa_dec>0) + model.paa_dec = -0.5; + + // record the previous version of tau, for convergence + etau_prev = model.etau; + paa_prev = model.paa; + paa_dec_prev = model.paa_dec; + ecp_del_prev = model.ecp_del; + ecp_dec_prev = model.ecp_dec; + + //qDebug() << "best1hour" << best1hour; + model.ecp = (best1hour - model.paa * (2.0-exp(-1*(3600/60.0))) * (exp(model.paa_dec*(3600/60.0))) ) / (1-exp(model.ecp_del*3600/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(3600/60.0))) / ( 1 + model.etau/(3600/60.0)); + //qDebug() << "model.ecp" << model.ecp; + + //qDebug() << "best5min" << best5min; + model.etau = ((best5min - model.paa * (2.0-exp(-1*(300/60.0))) * (exp(model.paa_dec*(300/60.0))) ) / model.ecp / (1-exp(model.ecp_del*300/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(300/60.0))) - 1) * (300/60.0); + //qDebug() << "model.etau" << model.etau; + + //qDebug() << "best1min" << best1min; + model.paa_dec = log((best1min - model.ecp * (1-exp(model.ecp_del*60/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(60/60.0))) * ( 1 + model.etau/(60/60.0)) ) / model.paa / (2.0-exp(-1*(60/60.0)))) / (60/60.0); + //qDebug() << "model.paa_dec" << model.paa_dec; + + //qDebug() << "best5s" << best5s; + model.paa = (best5s - model.ecp * (1-exp(model.ecp_del*5/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(5/60.0))) * ( 1 + model.etau/(5/60.0))) / exp(model.paa_dec*(5/60.0)) / (2.0-exp(-1*(5/60.0))); + //qDebug() << "model.paa" << model.paa; + + } while ((fabs(model.etau - etau_prev) > etau_delta_max) || + (fabs(model.paa - paa_prev) > paa_delta_max) || + (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max) || + (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max) || + (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max) + ); + + //qDebug() << iteration << "iterations"; + qDebug() <<"BEST eCP(4.3) " << "paa" << model.paa << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec ; + + return model; +} + +QwtPlotCurve* +ExtendedCriticalPower::getPlotCurveForExtendedCP_4_3(Model_eCP model) +{ + //qDebug() <<"getPlotCurveForExtendedCP_4_3()"; + //qDebug() <<"Model eCP(4.3) " << "paa" << model.paa << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec ; + + const int extendedCurve2_points = 1000; + + QVector extended_cp_curve2_power(extendedCurve2_points); + QVector extended_cp_curve2_time(extendedCurve2_points); + double tmin = 1.0/60; + double tmax = 600; + + for (int i = 0; i < extendedCurve2_points; i ++) { + double x = (double) i / (extendedCurve2_points - 1); + double t = pow(tmax, x) * pow(tmin, 1-x); + extended_cp_curve2_time[i] = t; + extended_cp_curve2_power[i] = model.paa*(2.0-exp(-1*t))*exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t)); + } + + QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP2"); + if (appsettings->value(NULL, GC_ANTIALIAS, false).toBool() == true) + extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased); + QPen e2pen(GColor(CCP)); + e2pen.setWidth(1); + e2pen.setStyle(Qt::DashLine); + extendedCPCurve2->setPen(e2pen); + extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points); + + return extendedCPCurve2; +} + +QwtPlotMarker* +ExtendedCriticalPower::getPlotMarkerForExtendedCP_2_3(Model_eCP model) +{ + QwtPlotMarker* extendedCurveTitle2 = new QwtPlotMarker(); + QString extendedCurve2_title; + + extendedCurve2_title.sprintf("CP60=%.0d W, Pmax=%.0d W (eCP 2.3)", model.cp60 , model.pMax); + extendedCurveTitle2->setLabel(QwtText(extendedCurve2_title, QwtText::PlainText)); + + return extendedCurveTitle2; +} + + +QwtPlotMarker* +ExtendedCriticalPower::getPlotMarkerForExtendedCP_4_3(Model_eCP model) +{ + QwtPlotMarker* extendedCurveTitle2 = new QwtPlotMarker(); + QString extendedCurve2_title; + + extendedCurve2_title.sprintf("CP60=%.0d W, Pmax=%.0d W (eCP 4.3)", model.cp60, model.pMax); + extendedCurveTitle2->setLabel(QwtText(extendedCurve2_title, QwtText::PlainText)); + + return extendedCurveTitle2; +} + diff --git a/src/ExtendedCriticalPower.h b/src/ExtendedCriticalPower.h new file mode 100644 index 000000000..f2d5deacc --- /dev/null +++ b/src/ExtendedCriticalPower.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013 Damien Grauser (Damien.Grauser@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 + */ + +#ifndef _gc_extendedcriticalpower_include +#define _gc_extendedcriticalpower_include + +#include "RideFile.h" +#include "Context.h" +#include "Athlete.h" +#include "Zones.h" +#include "RideMetric.h" +#include + +#include +#include + +// Model +class Model_eCP +{ + public: + + Model_eCP() : + version(""), + paa(0), paa_dec(0), ecp(0), etau(0), ecp_del(0), ecp_dec(0), ecp_dec_del(0), + pMax(0), cp60(0) {} + + /*Model_eCP(double paa, double e2cp, double e2tau, double e2b, double e2c, double e2d) : + paa(paa), ecp(ecp), e2tau(e2tau), e2b(e2b), e2c(e2c), e2d(e2d) {}*/ + + QString version; + // Parameters + double paa, paa_dec, ecp, etau, ecp_del, ecp_dec, ecp_dec_del; + + int pMax, cp60; +}; + +/*class Model_eCP +{ + public: + + Model_eCP() : + ecp(0), etau(0), eb(0), ec(0), ed(0) {} + + Model_eCP(double ecp, double etau, double eb, double ec, double ed) : + ecp(ecp), etau(etau), eb(eb), ec(ec), ed(ed) {} + + double ecp; + double etau; + double eb; + double ec; + double ed; +}; + +class Model_wsCP +{ + public: + + Model_wsCP() : + wa(0), wb(0), wcp(0), wpmax(0), wd(0) {} + + Model_wsCP(double wa, double wb, double wcp, double wpmax, double wd) : + wa(wa), wb(0), wcp(wcp), wpmax(wpmax), wd(wd) {} + + double wcp, wa, wb, wpmax, wd; // Ward-Smith CP model parameters +}; +*/ + + + + + +class ExtendedCriticalPower +{ + + public: + // construct and calculate series/metrics + ExtendedCriticalPower(Context *context); + ~ExtendedCriticalPower(); + + //Model_eCP deriveExtendedCPParameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2); + + /*Model_wsCP deriveWardSmithCPParameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2); + QwtPlotCurve* getPlotCurveForWScp(Model_wsCP athleteModeleWSCP);*/ + + + Model_eCP deriveExtendedCP_2_1_Parameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2); + QwtPlotCurve* getPlotCurveForExtendedCP_2_1(Model_eCP athleteModeleCP2); + QwtPlotMarker* getPlotMarkerForExtendedCP_2_1(Model_eCP athleteModeleCP2); + + Model_eCP deriveExtendedCP_2_3_Parameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2); + //Model_eCP deriveExtendedCP_2_3_ParametersForBest(double best5s, double best1min, double best5min, double best60min); + QwtPlotCurve* getPlotCurveForExtendedCP_2_3(Model_eCP model); + QwtPlotMarker* getPlotMarkerForExtendedCP_2_3(Model_eCP model); + + Model_eCP deriveExtendedCP_3_1_Parameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2); + QwtPlotCurve* getPlotCurveForExtendedCP_3_1(Model_eCP athleteModeleCP2); + + Model_eCP deriveExtendedCP_4_1_Parameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2); + QwtPlotCurve* getPlotCurveForExtendedCP_4_1(Model_eCP athleteModeleCP2); + + Model_eCP deriveExtendedCP_4_2_Parameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2); + QwtPlotCurve* getPlotCurveForExtendedCP_4_2(Model_eCP athleteModeleCP2); + + Model_eCP deriveExtendedCP_4_3_Parameters(bool usebest, RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2); + Model_eCP deriveExtendedCP_4_3_ParametersForBest(double best5s, double best1min, double best5min, double best60min); + QwtPlotCurve* getPlotCurveForExtendedCP_4_3(Model_eCP athleteModeleCP2); + QwtPlotMarker* getPlotMarkerForExtendedCP_4_3(Model_eCP athleteModeleCP2); + + private: + Context *context; +}; + +#endif diff --git a/src/src.pro b/src/src.pro index eb0efdb35..32dd44e42 100644 --- a/src/src.pro +++ b/src/src.pro @@ -298,6 +298,7 @@ HEADERS += \ ErgDB.h \ ErgDBDownloadDialog.h \ ErgFilePlot.h \ + ExtendedCriticalPower.h \ FitlogRideFile.h \ FitlogParser.h \ FitRideFile.h \ @@ -486,6 +487,7 @@ SOURCES += \ ErgDBDownloadDialog.cpp \ ErgFile.cpp \ ErgFilePlot.cpp \ + ExtendedCriticalPower.cpp \ FitlogRideFile.cpp \ FitlogParser.cpp \ FitRideFile.cpp \