Files
GoldenCheetah/src/TRIMPPoints.cpp
Mark Liversedge 05f1d577db Refactor MainWindow Part 2 of 5
Decoupled classes from MainWindow to reference Context
and Athlete (and introduced a couple of new headers).

We no longer pass around a MainWindow pointer to children
but pass a context instead.

There are still a few pieces left in MainWindow that need
to move to a better place;
    * Setting/clearing filter selection
    * Working with Intervals
    * Adding/Deleting Rides
    * Save on Exit

As mentioned previously there are lots of other parts to
this refactor left to do;
    * break MainWindow Gui elements into Toolbar and Views

    * migrate from RideItem and Ridelist to ActivityCollection
      and Activity classes that are not tied into gui elements.

    * introduce Application Context and AthleteCollection
2013-07-11 14:02:02 +01:00

379 lines
12 KiB
C++

/*
* Copyright (c) 2010 Damien Grauser (Damien.Grauser@pev-geneve.ch)
*
* 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 "Settings.h"
#include "Zones.h"
#include "HrZones.h"
#include <math.h>
#include "Context.h"
#include "Athlete.h"
#include <QApplication>
// This is Morton/Banister with Green et al coefficient.
//
// HR_TRIMP = time(min) * (AvgHR-RHR)/(MaxHR-RHR)*0.64*EXP(Ksex*(AvgHr-RHR)/(MaxHR-RHR))
//
// Ksex = 1.92 for man and 1.67 for woman
// RHR = resting heart rate
//
class TRIMPPoints : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TRIMPPoints)
double score;
public:
static const double K;
TRIMPPoints() : score(0.0)
{
setSymbol("trimp_points");
setInternalName("TRIMP Points");
}
void initialize() {
setName(tr("TRIMP Points"));
setMetricUnits("");
setImperialUnits("");
setType(RideMetric::Total);
}
void compute(const RideFile *rideFile,
const Zones *, int,
const HrZones *hrZones, int hrZoneRange,
const QHash<QString,RideMetric*> &deps,
const Context *context)
{
if (!hrZones || hrZoneRange < 0) {
setValue(0);
return;
}
// use resting HR from zones, but allow it to be
// overriden in ride metadata
double maxHr = hrZones->getMaxHr(hrZoneRange);
double restHr = hrZones->getRestHr(hrZoneRange);
restHr = rideFile->getTag("Rest HR", QString("%1").arg(restHr)).toDouble();
assert(deps.contains("time_riding"));
assert(deps.contains("workout_time"));
assert(deps.contains("average_hr"));
//const RideMetric *workoutTimeMetric = deps.value("workout_time");
const RideMetric *timeRidingMetric = deps.value("time_riding");
const RideMetric *averageHrMetric = deps.value("average_hr");
const RideMetric *durationMetric = deps.value("workout_time");
assert(timeRidingMetric);
assert(durationMetric);
assert(averageHrMetric);
double secs = timeRidingMetric->value(true) ? timeRidingMetric->value(true) :
durationMetric->value(true);;
double hr = averageHrMetric->value(true);
//TRIMP: = t x %HRR x 0.64e1,92(%HRR)
// Can we lookup the athletes gender?
// Default to male if we fail
QString athlete;
double ksex = 1.92;
if ((athlete = rideFile->getTag("Athlete", "unknown")) != "unknown") {
if (appsettings->cvalue(context->athlete->cyclist, GC_SEX).toInt() == 1) ksex = 1.67; // Female
else ksex = 1.92; // Male
}
// ok lets work the score out
score = (secs == 0.0 || hr<restHr) ? 0.0 : secs/60 *
(hr-restHr)/(maxHr-restHr)*0.64*exp(ksex*(hr-restHr)/(maxHr-restHr));
setValue(score);
}
RideMetric *clone() const { return new TRIMPPoints(*this); }
};
class TRIMP100Points : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TRIMP100Points)
double score;
public:
static const double K;
TRIMP100Points() : score(0.0)
{
setSymbol("trimp_100_points");
setInternalName("TRIMP(100) Points");
}
void initialize() {
setName(tr("TRIMP(100) Points"));
setMetricUnits("");
setImperialUnits("");
setType(RideMetric::Total);
}
void compute(const RideFile *rideFile,
const Zones *, int,
const HrZones *hrZones, int hrZoneRange,
const QHash<QString,RideMetric*> &deps,
const Context *context)
{
if (!hrZones || hrZoneRange < 0) {
setValue(0);
return;
}
// use resting HR from zones, but allow it to be
// overriden in ride metadata
double maxHr = hrZones->getMaxHr(hrZoneRange);
double restHr = hrZones->getRestHr(hrZoneRange);
double ltHr = hrZones->getLT(hrZoneRange);
restHr = rideFile->getTag("Rest HR", QString("%1").arg(restHr)).toDouble();
assert(deps.contains("trimp_points"));
const RideMetric *trimpPointsMetric = deps.value("trimp_points");
assert(trimpPointsMetric);
double trimp = trimpPointsMetric->value(true);
//TRIMP: = t x %HRR x 0.64e1,92(%HRR)
// Can we lookup the athletes gender?
// Default to male if we fail
QString athlete;
double ksex = 1.92;
if ((athlete = rideFile->getTag("Athlete", "unknown")) != "unknown") {
if (appsettings->cvalue(context->athlete->cyclist, GC_SEX).toInt() == 1) ksex = 1.67; // Female
else ksex = 1.92; // Male
}
score = trimp == 0.0 ? 0.0 : 100 * trimp /
(60 * (ltHr-restHr)/(maxHr-restHr)*0.64*exp(ksex*(ltHr-restHr)/(maxHr-restHr)));
setValue(score);
}
RideMetric *clone() const { return new TRIMP100Points(*this); }
};
//0.84 (zone 1 64-76%), 1.65 (zone 2 77-83%), 2.57 (zone 3 84-89%), 4.01 (zone 4 90-94%), and 5.91 (zone 5 95-100%)
//1 (zone 1 50-60%), 1.1 (zone 2 60-70%), 1.2 (zone 3 70-80%), 2.2 (zone 4 80-90%), and 4.5 (zone 5 90-100%)
// 0, 68, 83, 94, 105 of LT for LT 80% Max-> 0, 55, 66, 75, 84
// 0.9 (zone 1 0-55%), 1.1 (zone 2 55-66%), 1.2 (zone 3 66-75%), 2 (zone 4 75-84%), and 5 (zone 5 84-100%)
class TRIMPZonalPoints : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TRIMPZonalPoints)
double score;
public:
static const double K;
TRIMPZonalPoints() : score(0.0)
{
setSymbol("trimp_zonal_points");
setInternalName("TRIMP Zonal Points");
}
void initialize() {
setName(tr("TRIMP Zonal Points"));
setMetricUnits("");
setImperialUnits("");
setType(RideMetric::Total);
}
void compute(const RideFile *,
const Zones *, int,
const HrZones *hrZones, int hrZoneRange,
const QHash<QString,RideMetric*> &deps,
const Context *)
{
assert(deps.contains("average_hr"));
const RideMetric *averageHrMetric = deps.value("average_hr");
assert(averageHrMetric);
double hr = averageHrMetric->value(true);
if (hrZoneRange == -1 || hr == 0) {
setValue(0);
return;
}
QList <double> trimpk = hrZones->getZoneTrimps(hrZoneRange);
double value = 0;
if (trimpk.size()>0) {
assert(deps.contains("time_in_zone_H1"));
const RideMetric *time1Metric = deps.value("time_in_zone_H1");
assert(time1Metric);
double time1 = time1Metric->value(true);
double trimpk1 = trimpk[0];
value += trimpk1 * time1;
}
if (trimpk.size()>1) {
assert(deps.contains("time_in_zone_H2"));
const RideMetric *time2Metric = deps.value("time_in_zone_H2");
assert(time2Metric);
double time2 = time2Metric->value(true);
double trimpk2 = trimpk[1];
value += trimpk2 * time2;
}
if (trimpk.size()>2) {
assert(deps.contains("time_in_zone_H3"));
const RideMetric *time3Metric = deps.value("time_in_zone_H3");
assert(time3Metric);
double time3 = time3Metric->value(true);
double trimpk3 = trimpk[2];
value += trimpk3 * time3;
}
if (trimpk.size()>3) {
assert(deps.contains("time_in_zone_H4"));
const RideMetric *time4Metric = deps.value("time_in_zone_H4");
assert(time4Metric);
double time4 = time4Metric->value(true);
double trimpk4 = trimpk[3];
value += trimpk4 * time4;
}
if (trimpk.size()>4) {
assert(deps.contains("time_in_zone_H5"));
const RideMetric *time5Metric = deps.value("time_in_zone_H5");
assert(time5Metric);
double time5 = time5Metric->value(true);
double trimpk5 = trimpk[4];
value += trimpk5 * time5;
}
if (trimpk.size()>5) {
assert(deps.contains("time_in_zone_H6"));
const RideMetric *time6Metric = deps.value("time_in_zone_H6");
assert(time6Metric);
double time6 = time6Metric->value(true);
double trimpk6 = trimpk[5];
value += trimpk6 * time6;
}
if (trimpk.size()>6) {
assert(deps.contains("time_in_zone_H7"));
const RideMetric *time7Metric = deps.value("time_in_zone_H7");
assert(time7Metric);
double time7 = time7Metric->value(true);
double trimpk7 = trimpk[6];
value += trimpk7 * time7;
}
if (trimpk.size()>7) {
assert(deps.contains("time_in_zone_H8"));
const RideMetric *time8Metric = deps.value("time_in_zone_H8");
assert(time8Metric);
double time8 = time8Metric->value(true);
double trimpk8 = trimpk[7];
value += trimpk8 * time8;
}
setValue(value/60);
return;
}
RideMetric *clone() const { return new TRIMPZonalPoints(*this); }
};
// RPE is the rate of percieved exercion (borg scale).
// Is a numerical value the riders give in "average" fatigue of the training session he percieved.
//
// Calculate the session RPE that is the product of RPE * time (minutes) of training/race ride. I
// We have 3 different "training load" parameters:
// - internal load (TRIMPS)
// - external load (bikescore/TSS)
// - perceived load (session RPE)
//
class SessionRPE : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(SessionRPE)
double score;
public:
SessionRPE() : score(0.0)
{
setSymbol("session_rpe");
setInternalName("Session RPE");
}
void initialize() {
setName(tr("Session RPE"));
setMetricUnits("");
setImperialUnits("");
setType(RideMetric::Total);
}
void compute(const RideFile *rideFile,
const Zones *, int,
const HrZones *, int,
const QHash<QString,RideMetric*> &deps,
const Context *)
{
// use RPE value in ride metadata
double rpe = rideFile->getTag("RPE", "0.0").toDouble();
assert(deps.contains("time_riding"));
const RideMetric *timeRidingMetric = deps.value("time_riding");
assert(timeRidingMetric);
double secs = timeRidingMetric->value(true);
// ok lets work the score out
score = ((secs == 0.0 || rpe == 0) ? 0.0 : secs/60 *rpe);
setValue(score);
}
RideMetric *clone() const { return new SessionRPE(*this); }
};
static bool added() {
QVector<QString> deps;
deps.append("time_riding");
deps.append("average_hr");
deps.append("workout_time");
RideMetricFactory::instance().addMetric(TRIMPPoints(), &deps);
deps.clear();
deps.append("trimp_points");
RideMetricFactory::instance().addMetric(TRIMP100Points(), &deps);
deps.clear();
deps.append("average_hr");
deps.append("time_in_zone_H1");
deps.append("time_in_zone_H2");
deps.append("time_in_zone_H3");
deps.append("time_in_zone_H4");
deps.append("time_in_zone_H5");
deps.append("time_in_zone_H6");
deps.append("time_in_zone_H7");
deps.append("time_in_zone_H8");
RideMetricFactory::instance().addMetric(TRIMPZonalPoints(), &deps);
deps.clear();
deps.append("time_riding");
RideMetricFactory::instance().addMetric(SessionRPE(), &deps);
return true;
}
static bool added_ = added();