Files
GoldenCheetah/src/SwimScore.cpp
Alejandro Martinez eefa8db75b Changed cached paceZoneRange to be sport specific
And use it to compute Run and Swim metrics
2015-12-16 13:30:45 -03:00

403 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net)
* 2010 Mark Liversedge (liversedge@gmail.com)
* 2014 Alejandro Martinez (amtriathlon@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 "RideMetric.h"
#include "PaceZones.h"
#include "Units.h"
#include "Settings.h"
#include "RideItem.h"
#include "Context.h"
#include "Athlete.h"
#include "Specification.h"
#include <cmath>
#include <algorithm>
#include <QApplication>
// NOTE: This code follows the description of SwimScore in
// "Calculating Power Output and Training Stress in Swimmers:
// The Development of the SwimScoreTM Algorithm", by Dr. Phil Skiba:
// http://www.physfarm.com/swimscore.pdf
// Swimming Power from Speed
static inline double swimming_power( double weight, double speed ) {
const double K = 0.35 * weight + 2; // Drag Factor (Eq. 6)
const double ep = 0.6; // Toussaints propelling efficiency
return (K / ep) * pow(speed, 3); // Eq. 5
}
// Swimming Speed from Power
static inline double swimming_speed( double weight, double power ) {
const double K = 0.35 * weight + 2; // Drag Factor (Eq. 6)
const double ep = 0.6; // Toussaints propelling efficiency
return pow((ep / K) * power, 1/3.0); // Eq. 5
}
// XPowerSwim, used for SwimScore and xPaceSwim calculation
class XPowerSwim : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(XPowerSwim)
double xpower;
double secs;
public:
XPowerSwim() : xpower(0.0), secs(0.0)
{
setSymbol("swimscore_xpower");
setInternalName("xPower Swim");
}
void initialize() {
setName(tr("xPower Swim"));
setType(RideMetric::Average);
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) ||
// xPowerSwim only makes sense for running and it needs recIntSecs > 0
!item->isSwim || item->ride()->recIntSecs() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double weight = item->getWeight();
static const double EPSILON = 0.1;
static const double NEGLIGIBLE = 0.1;
double secsDelta = item->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;
double total = 0.0;
int count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
while ((weighted > NEGLIGIBLE)
&& (point->secs > lastSecs + secsDelta + EPSILON)) {
weighted *= attenuation;
lastSecs += secsDelta;
total += pow(weighted, 3.0);
count++;
}
weighted *= attenuation;
weighted += sampleWeight * swimming_power(weight, point->kph/3.6);
lastSecs = point->secs;
total += pow(weighted, 3.0);
count++;
}
xpower = count ? pow(total / count, 1/3.0) : 0.0;
secs = count * secsDelta;
setValue(xpower);
setCount(secs);
}
bool isRelevantForRide(const RideItem*ride) const { return ride->isSwim; }
RideMetric *clone() const { return new XPowerSwim(*this); }
};
// xPaceSwim: constant Pace which requires the same xPowerSwim
class XPaceSwim : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(XPaceSwim)
double xPaceSwim;
public:
XPaceSwim() : xPaceSwim(0.0)
{
setSymbol("swimscore_xpace");
setInternalName("xPace Swim");
}
// Swim Pace ordering is reversed
bool isLowerBetter() const { return true; }
// Overrides to use Swim Pace units setting
QString units(bool) const {
bool metricRunPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool();
return RideMetric::units(metricRunPace);
}
double value(bool) const {
bool metricRunPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool();
return RideMetric::value(metricRunPace);
}
QString toString(bool metric) const {
return time_to_string(value(metric)*60);
}
void initialize() {
setName(tr("xPace Swim"));
setType(RideMetric::Average);
setMetricUnits(tr("min/100m"));
setImperialUnits(tr("min/100yd"));
setPrecision(1);
setConversion(METERS_PER_YARD);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &deps) {
// xPowerSwim only makes sense for running and it needs recIntSecs > 0
if (!item->isSwim || item->ride()->recIntSecs() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double weight = item->getWeight();
assert(deps.contains("swimscore_xpower"));
XPowerSwim *xPowerSwim = dynamic_cast<XPowerSwim*>(deps.value("swimscore_xpower"));
assert(xPowerSwim);
double watts = xPowerSwim->value(true);
double speed = swimming_speed(weight, watts);
xPaceSwim = speed ? (100.0/60.0) / speed : 0.0;
setValue(xPaceSwim);
setCount(xPowerSwim->count());
}
bool isRelevantForRide(const RideItem *ride) const { return ride->isSwim; }
RideMetric *clone() const { return new XPaceSwim(*this); }
};
// Swimming Threshold Power based on CV, used for SwimScore calculation
class STP : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(STP)
public:
STP()
{
setSymbol("swimscore_tp");
setInternalName("STP");
}
void initialize() {
setName(tr("STP"));
setType(RideMetric::Average);
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
setPrecision(0);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
// xPowerSwim only makes sense for running and it needs recIntSecs > 0
if (!item->isSwim || item->ride()->recIntSecs() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double weight = item->getWeight();
const PaceZones *zones = item->context->athlete->paceZones(true);
int zoneRange = item->paceZoneRange;
// did user override for this ride?
double cv = item->getText("CV","0").toInt();
// not overriden so use the set value
// if it has been set at all
if (!cv && zones && zoneRange >= 0)
cv = zones->getCV(zoneRange);
// Swimming power at cv
double watts = swimming_power(weight, cv/3.6);
setValue(watts);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->isSwim; }
RideMetric *clone() const { return new STP(*this); }
};
// Swimming Relative Intensity
class SRI : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(SRI)
double reli;
double secs;
public:
SRI() : reli(0.0), secs(0.0)
{
setSymbol("swimscore_ri");
setInternalName("SRI");
}
void initialize() {
setName(tr("SRI"));
setType(RideMetric::Average);
setMetricUnits(tr(""));
setImperialUnits(tr(""));
setPrecision(2);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &deps) {
// xPowerSwim only makes sense for running and it needs recIntSecs > 0
if (!item->isSwim || item->ride()->recIntSecs() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
assert(deps.contains("swimscore_xpower"));
XPowerSwim *xPowerSwim = dynamic_cast<XPowerSwim*>(deps.value("swimscore_xpower"));
assert(xPowerSwim);
assert(deps.contains("swimscore_tp"));
STP *stp = dynamic_cast<STP*>(deps.value("swimscore_tp"));
assert(stp);
reli = stp->value(true) ? xPowerSwim->value(true) / stp->value(true) : 0;
secs = xPowerSwim->count();
setValue(reli);
setCount(secs);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->isSwim; }
RideMetric *clone() const { return new SRI(*this); }
};
// SwimScore Metric for swimming
class SwimScore : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(SwimScore)
double score;
public:
SwimScore() : score(0.0)
{
setSymbol("swimscore");
setInternalName("SwimScore");
}
void initialize() {
setName("SwimScore");
setType(RideMetric::Total);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &deps) {
// xPowerSwim only makes sense for running and it needs recIntSecs > 0
if (!item->isSwim || item->ride()->recIntSecs() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
assert(deps.contains("swimscore_xpower"));
assert(deps.contains("swimscore_ri"));
assert(deps.contains("swimscore_tp"));
XPowerSwim *xPowerSwim = dynamic_cast<XPowerSwim*>(deps.value("swimscore_xpower"));
assert(xPowerSwim);
RideMetric *sri = deps.value("swimscore_ri");
assert(sri);
RideMetric *stp = deps.value("swimscore_tp");
assert(stp);
double normWork = xPowerSwim->value(true) * xPowerSwim->count();
double rawGOVSS = normWork * sri->value(true);
// No samples in manual workouts, use power at average speed and duration
if (rawGOVSS == 0.0) {
// unconst naughty boy, get athlete's data
double weight = item->getWeight();
assert(deps.contains("average_speed"));
assert(deps.contains("workout_time"));
double watts = swimming_power(weight, deps.value("average_speed")->value(true) / 3.6);
double secs = deps.value("workout_time")->value(true);
double sri = stp->value(true) ? watts / stp->value(true) : 0.0;
rawGOVSS = watts * secs * sri;
}
double workInAnHourAtSTP = stp->value(true) * 3600;
score = workInAnHourAtSTP ? rawGOVSS / workInAnHourAtSTP * 100.0 : 0;
setValue(score);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->isSwim; }
RideMetric *clone() const { return new SwimScore(*this); }
};
static bool addAllSwimScore() {
RideMetricFactory::instance().addMetric(XPowerSwim());
RideMetricFactory::instance().addMetric(STP());
QVector<QString> deps;
deps.append("swimscore_xpower");
RideMetricFactory::instance().addMetric(XPaceSwim(), &deps);
deps.append("swimscore_tp");
RideMetricFactory::instance().addMetric(SRI(), &deps);
deps.append("swimscore_ri");
deps.append("average_speed");
deps.append("workout_time");
RideMetricFactory::instance().addMetric(SwimScore(), &deps);
return true;
}
static bool SwimScoreAdded = addAllSwimScore();
// TriScore Metric for triathlon
class TriScore : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TriScore)
double score;
public:
TriScore() : score(0.0)
{
setSymbol("triscore");
setInternalName("TriScore");
}
void initialize() {
setName("TriScore");
setType(RideMetric::Total);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &deps) {
if (item->isSwim) {
assert(deps.contains("swimscore"));
score = deps.value("swimscore")->value(true);
} else if (item->isRun) {
assert(deps.contains("govss"));
score = deps.value("govss")->value(true);
} else {
assert(deps.contains("skiba_bike_score"));
score = deps.value("skiba_bike_score")->value(true);
}
setValue(score);
}
RideMetric *clone() const { return new TriScore(*this); }
};
static bool addTriScore() {
QVector<QString> deps;
deps.append("swimscore");
deps.append("govss");
deps.append("skiba_bike_score");
RideMetricFactory::instance().addMetric(TriScore(), &deps);
return true;
}
static bool TriScoreAdded = addTriScore();