mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 08:59:55 +00:00
.. gets rid of an iritating compiler warning when compiling with VS2015 and MSVC .. is good because user metrics aren't fixed so the assert assumption is wrong now .. means we have to add assert.h to all the source files that had it from including RideMetric.h and want to use it.
404 lines
13 KiB
C++
404 lines
13 KiB
C++
/*
|
||
* 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 <assert.h>
|
||
#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; // Toussaint’s 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; // Toussaint’s 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();
|