Files
GoldenCheetah/src/DanielsPoints.cpp
Alejandro Martinez 36991ae45e Use Duration when Time Moving is zero in Session RPE and Daniels EqP
Similar to TRIMP points, Fixes #1265
2015-02-05 10:35:53 -03:00

163 lines
5.3 KiB
C++

/*
* Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net)
*
* 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 "RideMetric.h"
#include "Zones.h"
#include <cmath>
#include <QApplication>
// The idea: Fit a curve to the points system in Table 2.2 of "Daniel's Running
// Formula", Second Edition, assume that power at VO2Max is 1.2 * FTP, further
// assume that pace is proportional to power (which is not too far off in
// running), and scale so that one hour at FTP is worth 33 points (which is
// the arbitrary value that Daniels chose). Then you get a nice fit where
//
// points per second = 33/3600 * (watts / FTP) ^ 4.
class DanielsPoints : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(DanielsPoints)
double score;
void inc(double secs, double watts, double cp) {
score += K * secs * pow(watts / cp, 4);
}
public:
static const double K;
DanielsPoints() : score(0.0)
{
setSymbol("daniels_points");
setInternalName("Daniels Points");
}
void initialize() {
setName(tr("Daniels Points"));
setMetricUnits("");
setImperialUnits("");
setType(RideMetric::Total);
}
void compute(const RideFile *ride, const Zones *zones, int zoneRange,
const HrZones *, int,
const QHash<QString,RideMetric*> &,
const Context *) {
if (!zones || zoneRange < 0) {
setValue(0);
return;
}
static const double EPSILON = 0.1;
static const double NEGLIGIBLE = 0.1;
double secsDelta = ride->recIntSecs();
double sampsPerWindow = 25.0 / secsDelta;
double attenuation = sampsPerWindow / (sampsPerWindow + secsDelta);
double sampleWeight = secsDelta / (sampsPerWindow + secsDelta);
double lastSecs = 0.0;
double weighted = 0.0;
score = 0.0;
double cp = zones->getCP(zoneRange);
foreach(const RideFilePoint *point, ride->dataPoints()) {
while ((weighted > NEGLIGIBLE)
&& (point->secs > lastSecs + secsDelta + EPSILON)) {
weighted *= attenuation;
lastSecs += secsDelta;
inc(secsDelta, weighted, cp);
}
weighted *= attenuation;
weighted += sampleWeight * point->watts;
lastSecs = point->secs;
inc(secsDelta, weighted, cp);
}
while (weighted > NEGLIGIBLE) {
weighted *= attenuation;
lastSecs += secsDelta;
inc(secsDelta, weighted, cp);
}
setValue(score);
}
RideMetric *clone() const { return new DanielsPoints(*this); }
};
// Choose K such that 1 hour at FTP yields a score of 100.
const double DanielsPoints::K = 100.0 / 3600.0;
class DanielsEquivalentPower : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(DanielsEquivalentPower)
double watts;
public:
DanielsEquivalentPower() : watts(0.0)
{
setSymbol("daniels_equivalent_power");
setInternalName("Daniels EqP");
}
void initialize() {
setName(tr("Daniels EqP"));
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
setType(RideMetric::Average);
}
void compute(const RideFile *, const Zones *zones, int zoneRange,
const HrZones *, int,
const QHash<QString,RideMetric*> &deps,
const Context *)
{
if (!zones || zoneRange < 0) {
setValue(0);
return;
}
double cp = zones->getCP(zoneRange);
assert(deps.contains("daniels_points"));
assert(deps.contains("time_riding"));
assert(deps.contains("workout_time"));
const RideMetric *danielsPoints = deps.value("daniels_points");
const RideMetric *timeRiding = deps.value("time_riding");
const RideMetric *workoutTime = deps.value("workout_time");
assert(danielsPoints);
assert(timeRiding);
assert(workoutTime);
double score = danielsPoints->value(true);
double secs = timeRiding->value(true) ? timeRiding->value(true) :
workoutTime->value(true);
watts = secs == 0.0 ? 0.0 : cp * pow(score / DanielsPoints::K / secs, 0.25);
setValue(watts);
}
RideMetric *clone() const { return new DanielsEquivalentPower(*this); }
};
static bool added() {
RideMetricFactory::instance().addMetric(DanielsPoints());
QVector<QString> deps;
deps.append("time_riding");
deps.append("workout_time");
deps.append("daniels_points");
RideMetricFactory::instance().addMetric(DanielsEquivalentPower(), &deps);
return true;
}
static bool added_ = added();