mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
Workout (ErgFile) computes metrics
When a workout file is opened, the contents are used to calculate the NP,xPower thru TSS/Bikescore values for the workout. This is in preparation for on-the-fly adjustment of the intensity of a workout and displaying the target stress load (TSS/BikeScore) the workout will produce. No user functional change.
This commit is contained in:
131
src/ErgFile.cpp
131
src/ErgFile.cpp
@@ -19,7 +19,7 @@
|
||||
#include "ErgFile.h"
|
||||
|
||||
|
||||
ErgFile::ErgFile(QString filename, int &mode, double Cp)
|
||||
ErgFile::ErgFile(QString filename, int &mode, double Cp, MainWindow *main) : main(main)
|
||||
{
|
||||
QFile ergFile(filename);
|
||||
int section = NOMANSLAND; // section 0=init, 1=header data, 2=course data
|
||||
@@ -188,7 +188,7 @@ ErgFile::ErgFile(QString filename, int &mode, double Cp)
|
||||
// gradient and altitude
|
||||
add.val = absoluteSlope.cap(2).toDouble();
|
||||
add.y = ralt;
|
||||
ralt += distance * add.val /100.00 ; /* paused */
|
||||
ralt += distance * add.val / 100; /* paused */
|
||||
|
||||
Points.append(add);
|
||||
if (add.y > MaxWatts) MaxWatts=add.y;
|
||||
@@ -236,6 +236,8 @@ ErgFile::ErgFile(QString filename, int &mode, double Cp)
|
||||
leftPoint = 0;
|
||||
rightPoint = 1;
|
||||
|
||||
calculateMetrics();
|
||||
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
@@ -259,7 +261,7 @@ ErgFile::wattsAt(long x, int &lapnum)
|
||||
// point in time in msecs.
|
||||
|
||||
// is it in bounds?
|
||||
if (x < 0 || x > Duration) return -100; // out of bounds!!!
|
||||
if (x < 0 || x > Duration) return 0; // out of bounds!!!
|
||||
|
||||
// do we need to return the Lap marker?
|
||||
if (Laps.count() > 0) {
|
||||
@@ -350,3 +352,126 @@ int ErgFile::nextLap(long x)
|
||||
}
|
||||
return -1; // nope, no marker ahead of there
|
||||
}
|
||||
|
||||
void
|
||||
ErgFile::calculateMetrics()
|
||||
{
|
||||
|
||||
// reset metrics
|
||||
XP = CP = AP = NP = IF = RI = TSS = BS = SVI = VI = 0;
|
||||
ELE = ELEDIST = GRADE = 0;
|
||||
|
||||
if (format == CRS) {
|
||||
|
||||
ErgFilePoint last;
|
||||
bool first = true;
|
||||
foreach (ErgFilePoint p, Points) {
|
||||
|
||||
if (first == true) {
|
||||
first = false;
|
||||
} else if (p.y > last.y) {
|
||||
|
||||
ELEDIST += p.x - last.x;
|
||||
ELE += p.y - last.y;
|
||||
}
|
||||
last = p;
|
||||
}
|
||||
GRADE = ELE/ELEDIST * 100;
|
||||
|
||||
} else {
|
||||
|
||||
QVector<double> rolling(30);
|
||||
rolling.fill(0.0f);
|
||||
int index = 0;
|
||||
double sum = 0; // 30s rolling average
|
||||
double apsum = 0; // average power used in VI calculation
|
||||
|
||||
double total = 0;
|
||||
double count = 0;
|
||||
long nextSecs = 0;
|
||||
|
||||
static const double EPSILON = 0.1;
|
||||
static const double NEGLIGIBLE = 0.1;
|
||||
|
||||
double secsDelta = 1;
|
||||
double sampsPerWindow = 25.0;
|
||||
double attenuation = sampsPerWindow / (sampsPerWindow + secsDelta);
|
||||
double sampleWeight = secsDelta / (sampsPerWindow + secsDelta);
|
||||
|
||||
double lastSecs = 0.0;
|
||||
double weighted = 0.0;
|
||||
|
||||
double sktotal = 0.0;
|
||||
int skcount = 0;
|
||||
|
||||
ErgFilePoint last;
|
||||
foreach (ErgFilePoint p, Points) {
|
||||
|
||||
while (nextSecs < p.x) {
|
||||
|
||||
// CALCULATE NP
|
||||
apsum += last.y;
|
||||
sum += last.y;
|
||||
sum -= rolling[index];
|
||||
|
||||
// update 30s circular buffer
|
||||
rolling[index] = last.y;
|
||||
if (index == 29) index=0;
|
||||
else index++;
|
||||
|
||||
total += pow(sum/30, 4);
|
||||
count ++;
|
||||
|
||||
// CALCULATE XPOWER
|
||||
while ((weighted > NEGLIGIBLE) && ((nextSecs / 1000) > (lastSecs/1000) + 1000 + EPSILON)) {
|
||||
weighted *= attenuation;
|
||||
lastSecs += 1000;
|
||||
sktotal += pow(weighted, 4.0);
|
||||
skcount++;
|
||||
}
|
||||
weighted *= attenuation;
|
||||
weighted += sampleWeight * last.y;
|
||||
lastSecs = p.x;
|
||||
sktotal += pow(weighted, 4.0);
|
||||
skcount++;
|
||||
|
||||
nextSecs += 1000;
|
||||
}
|
||||
last = p;
|
||||
}
|
||||
|
||||
// XP, NP and AP
|
||||
XP = pow(sktotal / skcount, 0.25);
|
||||
NP = pow(total / count, 0.25);
|
||||
AP = apsum / count;
|
||||
|
||||
// CP
|
||||
if (main->zones()) {
|
||||
int zonerange = main->zones()->whichRange(QDateTime::currentDateTime().date());
|
||||
if (zonerange >= 0) CP = main->zones()->getCP(zonerange);
|
||||
}
|
||||
|
||||
// IF
|
||||
if (CP) {
|
||||
IF = NP / CP;
|
||||
RI = XP / CP;
|
||||
}
|
||||
|
||||
// TSS
|
||||
double normWork = NP * (Duration / 1000); // msecs
|
||||
double rawTSS = normWork * IF;
|
||||
double workInAnHourAtCP = CP * 3600;
|
||||
TSS = rawTSS / workInAnHourAtCP * 100.0;
|
||||
|
||||
// BS
|
||||
double xWork = XP * (Duration / 1000); // msecs
|
||||
double rawBS = xWork * RI;
|
||||
BS = rawBS / workInAnHourAtCP * 100.0;
|
||||
|
||||
// VI and RI
|
||||
if (AP) {
|
||||
VI = NP / AP;
|
||||
SVI = XP / AP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#ifndef _ErgFile_h
|
||||
#define _ErgFile_h
|
||||
#include "GoldenCheetah.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <QtGui>
|
||||
#include <QObject>
|
||||
@@ -45,6 +46,9 @@
|
||||
class ErgFilePoint
|
||||
{
|
||||
public:
|
||||
|
||||
ErgFilePoint() : x(0), y(0), val(0) {}
|
||||
|
||||
long x; // x axis - time in msecs or distance in meters
|
||||
long y; // y axis - load in watts or altitude
|
||||
|
||||
@@ -61,7 +65,7 @@ class ErgFileLap
|
||||
class ErgFile
|
||||
{
|
||||
public:
|
||||
ErgFile(QString, int&, double Cp); // constructor uses filename
|
||||
ErgFile(QString, int&, double Cp, MainWindow *main); // constructor uses filename
|
||||
~ErgFile(); // delete the contents
|
||||
|
||||
bool isValid(); // is the file valid or not?
|
||||
@@ -80,10 +84,21 @@ class ErgFile
|
||||
int MaxWatts; // maxWatts in this ergfile (scaling)
|
||||
bool valid; // did it parse ok?
|
||||
|
||||
int leftPoint, rightPoint; // current points we are between
|
||||
int leftPoint, rightPoint; // current points we are between
|
||||
|
||||
QList<ErgFilePoint> Points; // points in workout
|
||||
QList<ErgFileLap> Laps; // interval markers in the file
|
||||
QList<ErgFilePoint> Points; // points in workout
|
||||
QList<ErgFileLap> Laps; // interval markers in the file
|
||||
|
||||
void calculateMetrics(); // calculate NP value for ErgFile
|
||||
|
||||
// Metrics for this workout
|
||||
double CP;
|
||||
double AP, NP, IF, TSS, VI; // Coggan for erg / mrc
|
||||
double XP, RI, BS, SVI; // Skiba for erg / mrc
|
||||
double ELE, ELEDIST, GRADE; // crs
|
||||
|
||||
private:
|
||||
MainWindow *main;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -408,7 +408,7 @@ TrainTool::workoutTreeWidgetSelectionChanged()
|
||||
// Get users CP for relative watts calculations
|
||||
QDate today = QDate::currentDate();
|
||||
|
||||
ergFile = new ErgFile(fileName, mode, FTP);
|
||||
ergFile = new ErgFile(fileName, mode, FTP, main);
|
||||
if (ergFile->isValid()) {
|
||||
|
||||
status |= RT_WORKOUT;
|
||||
|
||||
Reference in New Issue
Block a user