Formula which() function

.. To select in a vector. The notation
   is a selection criteria followed by
   a list of values / vectors

   e.g. which(x>0, TSS[date:date])

   will create a vector where TSS is non
   zero for the day.

   "x" is replaced with the vector values
   and as such is a special symbol.

.. also fixed a bug with the snip cache.
This commit is contained in:
Mark Liversedge
2015-08-22 17:18:54 +01:00
parent b703465a83
commit 769c4eaf55
4 changed files with 139 additions and 60 deletions

View File

@@ -120,6 +120,8 @@ DataFilter::functions()
foreach(QString model, pdmodels())
returning << "estimate(" + model + ", x)";
} else if (i == 31) { // which example
returning << "which(x>0, ...)";
} else {
function = DataFilterFunctions[i].name + "(";
@@ -647,9 +649,18 @@ Leaf::toString()
break;
case Leaf::Function :
if (series) {
if (lvalue.l) return QString("%1(%2,%3)")
.arg(function).arg(*(series->lvalue.n))
.arg(lvalue.l->toString());
if (lvalue.l) {
return QString("%1(%2,%3)")
.arg(function)
.arg(*(series->lvalue.n))
.arg(lvalue.l->toString());
} else {
return QString("%1(%2)")
.arg(function)
.arg(*(series->lvalue.n));
}
} else {
QString f= function + "(";
bool first=true;
@@ -775,6 +786,7 @@ bool Leaf::isNumber(DataFilter *df, Leaf *leaf)
{
QString symbol = *(leaf->lvalue.n);
if (symbol == "isRun") return true;
if (symbol == "x") return true;
else if (symbol == "isSwim") return true;
else if (!symbol.compare("Date", Qt::CaseInsensitive)) return true;
else if (!symbol.compare("Today", Qt::CaseInsensitive)) return true;
@@ -843,6 +855,7 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf)
// isRun isa special, we may add more later (e.g. date)
if (symbol.compare("Date", Qt::CaseInsensitive) &&
symbol.compare("x", Qt::CaseInsensitive) && // used by which
symbol.compare("Today", Qt::CaseInsensitive) &&
symbol.compare("Current", Qt::CaseInsensitive) &&
symbol != "isSwim" && symbol != "isRun" && !isCoggan(symbol)) {
@@ -932,7 +945,18 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf)
bool found=false;
// are the parameters well formed ?
if (leaf->function == "estimate") {
if (leaf->function == "which") {
// 2 or more
if (leaf->fparms.count() < 2) {
leaf->inerror = true;
DataFiltererrors << QString(QObject::tr("which function has at least 2 parameters."));
}
// still normal parm check !
foreach(Leaf *p, leaf->fparms) validateFilter(df, p);
} else if (leaf->function == "estimate") {
// we only want two parameters and they must be
// a model name and then either ftp, cp, pmax, w'
@@ -942,7 +966,7 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf)
if (leaf->fparms[0]->type != Leaf::Symbol) {
leaf->fparms[0]->inerror = true;
DataFiltererrors << QString(QObject::tr("estimate function expects model name as first parameter"));
DataFiltererrors << QString(QObject::tr("estimate function expects model name as first parameter."));
} else {
@@ -1105,12 +1129,9 @@ DataFilter::DataFilter(QObject *parent, Context *context, QString formula) : QOb
Result DataFilter::evaluate(RideItem *item)
{
// clear the micro-cache
snips.clear();
if (!item || !treeRoot || DataFiltererrors.count()) return Result(0);
Result res = treeRoot->eval(context, this, treeRoot, item);
Result res = treeRoot->eval(context, this, treeRoot, 0, item);
return res;
}
@@ -1151,6 +1172,7 @@ QStringList DataFilter::parseFilter(QString query, QStringList *list)
// remember where we apply
this->list = list;
isdynamic=false;
snips.clear();
// regardless of fail/pass set the signature
setSignature(query);
@@ -1198,7 +1220,7 @@ QStringList DataFilter::parseFilter(QString query, QStringList *list)
foreach(RideItem *item, context->athlete->rideCache->rides()) {
// evaluate each ride...
Result result = treeRoot->eval(context, this, treeRoot, item);
Result result = treeRoot->eval(context, this, treeRoot, 0, item);
if (result.isNumber && result.number) {
filenames << item->fileName;
}
@@ -1224,7 +1246,7 @@ DataFilter::dynamicParse()
foreach(RideItem *item, context->athlete->rideCache->rides()) {
// evaluate each ride...
Result result = treeRoot->eval(context, this, treeRoot, item);
Result result = treeRoot->eval(context, this, treeRoot, 0, item);
if (result.isNumber && result.number)
filenames << item->fileName;
}
@@ -1275,7 +1297,7 @@ void DataFilter::configChanged(qint32)
}
Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, float x, RideItem *m)
{
// if error state all bets are off
if (inerror) return Result(0);
@@ -1290,26 +1312,26 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
switch (leaf->op) {
case AND :
{
Result left = eval(context, df, leaf->lvalue.l, m);
Result left = eval(context, df, leaf->lvalue.l, x, m);
if (left.isNumber && left.number) {
Result right = eval(context, df, leaf->rvalue.l, m);
Result right = eval(context, df, leaf->rvalue.l, x, m);
if (right.isNumber && right.number) return Result(true);
}
return Result(false);
}
case OR :
{
Result left = eval(context, df, leaf->lvalue.l, m);
Result left = eval(context, df, leaf->lvalue.l, x, m);
if (left.isNumber && left.number) return Result(true);
Result right = eval(context, df, leaf->rvalue.l, m);
Result right = eval(context, df, leaf->rvalue.l, x, m);
if (right.isNumber && right.number) return Result(true);
return Result(false);
}
default : // parenthesis
return (eval(context, df, leaf->lvalue.l, m));
return (eval(context, df, leaf->lvalue.l, x, m));
}
}
break;
@@ -1433,7 +1455,7 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
default:
case Leaf::Function :
{
duration = eval(context, df, leaf->lvalue.l, m).number; // duration will be zero if string
duration = eval(context, df, leaf->lvalue.l, x, m).number; // duration will be zero if string
}
break;
@@ -1444,6 +1466,8 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
if (df->lookupType.value(*(leaf->lvalue.l->lvalue.n)) == true) {
// numeric
duration = m->getForSymbol(rename=df->lookupMap.value(*(leaf->lvalue.l->lvalue.n),""));
} else if (*(leaf->lvalue.l->lvalue.n) == "x") {
duration = x;
} else {
duration = 0;
}
@@ -1492,36 +1516,36 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
if (fnum < 0) return Result(0);
switch (fnum) {
case 0 : { return Result(cos(eval(context, df, leaf->fparms[0], m).number)); } // COS(x)
case 1 : { return Result(tan(eval(context, df, leaf->fparms[0], m).number)); } // TAN(x)
case 2 : { return Result(sin(eval(context, df, leaf->fparms[0], m).number)); } // SIN(x)
case 3 : { return Result(acos(eval(context, df, leaf->fparms[0], m).number)); } // ACOS(x)
case 4 : { return Result(atan(eval(context, df, leaf->fparms[0], m).number)); } // ATAN(x)
case 5 : { return Result(asin(eval(context, df, leaf->fparms[0], m).number)); } // ASIN(x)
case 6 : { return Result(cosh(eval(context, df, leaf->fparms[0], m).number)); } // COSH(x)
case 7 : { return Result(tanh(eval(context, df, leaf->fparms[0], m).number)); } // TANH(x)
case 8 : { return Result(sinh(eval(context, df, leaf->fparms[0], m).number)); } // SINH(x)
case 9 : { return Result(acosh(eval(context, df, leaf->fparms[0], m).number)); } // ACOSH(x)
case 10 : { return Result(atanh(eval(context, df, leaf->fparms[0], m).number)); } // ATANH(x)
case 11 : { return Result(asinh(eval(context, df, leaf->fparms[0], m).number)); } // ASINH(x)
case 0 : { return Result(cos(eval(context, df, leaf->fparms[0], x, m).number)); } // COS(x)
case 1 : { return Result(tan(eval(context, df, leaf->fparms[0], x, m).number)); } // TAN(x)
case 2 : { return Result(sin(eval(context, df, leaf->fparms[0], x, m).number)); } // SIN(x)
case 3 : { return Result(acos(eval(context, df, leaf->fparms[0], x, m).number)); } // ACOS(x)
case 4 : { return Result(atan(eval(context, df, leaf->fparms[0], x, m).number)); } // ATAN(x)
case 5 : { return Result(asin(eval(context, df, leaf->fparms[0], x, m).number)); } // ASIN(x)
case 6 : { return Result(cosh(eval(context, df, leaf->fparms[0], x, m).number)); } // COSH(x)
case 7 : { return Result(tanh(eval(context, df, leaf->fparms[0], x, m).number)); } // TANH(x)
case 8 : { return Result(sinh(eval(context, df, leaf->fparms[0], x, m).number)); } // SINH(x)
case 9 : { return Result(acosh(eval(context, df, leaf->fparms[0], x, m).number)); } // ACOSH(x)
case 10 : { return Result(atanh(eval(context, df, leaf->fparms[0], x, m).number)); } // ATANH(x)
case 11 : { return Result(asinh(eval(context, df, leaf->fparms[0], x, m).number)); } // ASINH(x)
case 12 : { return Result(exp(eval(context, df, leaf->fparms[0], m).number)); } // EXP(x)
case 13 : { return Result(log(eval(context, df, leaf->fparms[0], m).number)); } // LOG(x)
case 14 : { return Result(log10(eval(context, df, leaf->fparms[0], m).number)); } // LOG10(x)
case 12 : { return Result(exp(eval(context, df, leaf->fparms[0], x, m).number)); } // EXP(x)
case 13 : { return Result(log(eval(context, df, leaf->fparms[0], x, m).number)); } // LOG(x)
case 14 : { return Result(log10(eval(context, df, leaf->fparms[0], x, m).number)); } // LOG10(x)
case 15 : { return Result(ceil(eval(context, df, leaf->fparms[0], m).number)); } // CEIL(x)
case 16 : { return Result(floor(eval(context, df, leaf->fparms[0], m).number)); } // FLOOR(x)
case 17 : { return Result(round(eval(context, df, leaf->fparms[0], m).number)); } // ROUND(x)
case 15 : { return Result(ceil(eval(context, df, leaf->fparms[0], x, m).number)); } // CEIL(x)
case 16 : { return Result(floor(eval(context, df, leaf->fparms[0], x, m).number)); } // FLOOR(x)
case 17 : { return Result(round(eval(context, df, leaf->fparms[0], x, m).number)); } // ROUND(x)
case 18 : { return Result(fabs(eval(context, df, leaf->fparms[0], m).number)); } // FABS(x)
case 19 : { return Result(std::isinf(eval(context, df, leaf->fparms[0], m).number)); } // ISINF(x)
case 20 : { return Result(std::isnan(eval(context, df, leaf->fparms[0], m).number)); } // ISNAN(x)
case 18 : { return Result(fabs(eval(context, df, leaf->fparms[0], x, m).number)); } // FABS(x)
case 19 : { return Result(std::isinf(eval(context, df, leaf->fparms[0], x, m).number)); } // ISINF(x)
case 20 : { return Result(std::isnan(eval(context, df, leaf->fparms[0], x, m).number)); } // ISNAN(x)
case 21 : { /* SUM( ... ) */
double sum=0;
foreach(Leaf *l, leaf->fparms) {
sum += eval(context, df, l, m).number; // for vectors number is sum
sum += eval(context, df, l, x, m).number; // for vectors number is sum
}
return Result(sum);
}
@@ -1532,7 +1556,7 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
int count=0;
foreach(Leaf *l, leaf->fparms) {
Result res = eval(context, df, l, m); // for vectors number is sum
Result res = eval(context, df, l, x, m); // for vectors number is sum
sum += res.number;
if (res.vector.count()) count += res.vector.count();
else count++;
@@ -1546,7 +1570,7 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
bool set=false;
foreach(Leaf *l, leaf->fparms) {
Result res = eval(context, df, l, m);
Result res = eval(context, df, l, x, m);
if (res.vector.count()) {
foreach(double x, res.vector) {
if (set && x>max) max=x;
@@ -1567,7 +1591,7 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
bool set=false;
foreach(Leaf *l, leaf->fparms) {
Result res = eval(context, df, l, m);
Result res = eval(context, df, l, x, m);
if (res.vector.count()) {
foreach(double x, res.vector) {
if (set && x<min) min=x;
@@ -1587,7 +1611,7 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
int count = 0;
foreach(Leaf *l, leaf->fparms) {
Result res = eval(context, df, l, m);
Result res = eval(context, df, l, x, m);
if (res.vector.count()) count += res.vector.count();
else count++;
}
@@ -1633,7 +1657,7 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
// what we looking for ?
QString parm = leaf->fparms[1]->type == Leaf::Symbol ? *leaf->fparms[1]->lvalue.n : "";
bool toDuration = parm == "" ? true : false;
double duration = toDuration ? eval(context, df, leaf->fparms[1], m).number : 0;
double duration = toDuration ? eval(context, df, leaf->fparms[1], x, m).number : 0;
// get the PD Estimate for this date - note we always work with the absolulte
// power estimates in formulas, since the user can just divide by config(weight)
@@ -1677,6 +1701,50 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
}
break;
case 31 :
{ // WHICH ( expr, ... )
Result returning(0);
// runs through all parameters, evaluating expression
// in first param, and if true, adding to the results
// this is a select statement.
// e.g. which(x > 0, 1,2,3,-5,-6,-7) would return
// (1,2,3). More meaningfully it is used when
// working with vectors
if (leaf->fparms.count() < 2) return returning;
for(int i=1; i< leaf->fparms.count(); i++) {
// evaluate the parameter
Result ex = eval(context, df, leaf->fparms[i], x, m);
if (ex.vector.count()) {
// tiz a vector
foreach(double x, ex.vector) {
// did it get selected?
Result which = eval(context, df, leaf->fparms[0], x, m);
if (which.number) {
returning.vector << x;
returning.number += x;
}
}
} else {
// does the parameter get selected ?
Result which = eval(context, df, leaf->fparms[0], ex.number, m);
if (which.number) {
returning.vector << ex.number;
returning.number += ex.number;
}
}
}
return Result(returning);
}
break;
default:
return Result(0);
}
@@ -1695,7 +1763,12 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
QString symbol = *(leaf->lvalue.n);
// is it isRun ?
if (symbol == "isRun") {
if (symbol == "x") {
lhsdouble = x;
lhsisNumber = true;
} else if (symbol == "isRun") {
lhsdouble = m->isRun ? 1 : 0;
lhsisNumber = true;
@@ -1784,7 +1857,7 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
case Leaf::UnaryOperation :
{
// get result
Result lhs = eval(context, df, leaf->lvalue.l, m);
Result lhs = eval(context, df, leaf->lvalue.l, x, m);
return Result(lhs.number * -1);
}
break;
@@ -1796,8 +1869,8 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
case Leaf::Operation :
{
// lhs and rhs
Result lhs = eval(context, df, leaf->lvalue.l, m);
Result rhs = eval(context, df, leaf->rvalue.l, m);
Result lhs = eval(context, df, leaf->lvalue.l, x, m);
Result rhs = eval(context, df, leaf->rvalue.l, x, m);
// NOW PERFORM OPERATION
switch (leaf->op) {
@@ -1907,9 +1980,9 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
case Leaf::Conditional :
{
Result cond = eval(context, df, leaf->cond.l, m);
if (cond.isNumber && cond.number) return eval(context, df, leaf->lvalue.l, m);
else return eval(context, df, leaf->rvalue.l, m);
Result cond = eval(context, df, leaf->cond.l, x, m);
if (cond.isNumber && cond.number) return eval(context, df, leaf->lvalue.l, x, m);
else return eval(context, df, leaf->rvalue.l, x, m);
}
break;
@@ -1932,11 +2005,13 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
// is this already snipped?
Result snipped = df->snips.value(leaf->signature(), Result(0));
if (snipped.vector.count() != 0) return snipped;
if (snipped.vector.count() > 0) {
return snipped;
}
// get date range
int fromDS = eval(context, df, leaf->fparms[0], m).number;
int toDS = eval(context, df, leaf->fparms[1], m).number;
int fromDS = eval(context, df, leaf->fparms[0], x, m).number;
int toDS = eval(context, df, leaf->fparms[1], x, m).number;
// swap dates if needed
if (toDS < fromDS) {
@@ -1955,7 +2030,7 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
if (!spec.pass(ride)) continue;
// calculate value
Result res = eval(context, df, leaf->lvalue.l, ride);
Result res = eval(context, df, leaf->lvalue.l, x, ride);
if (res.isNumber) {
returning.number += res.number; // sum for easy access
returning.vector << res.number;
@@ -1963,8 +2038,9 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m)
}
// vectors of more than 100 items need snipping
if (returning.vector.count() > 100)
if (returning.vector.count() > 100) {
df->snips.insert(leaf->signature(), returning);
}
// always return as sum number (for now)
return returning;

View File

@@ -24,6 +24,7 @@
#include <QDebug>
#include <QList>
#include <QMap>
#include <QHash>
#include <QStringList>
#include <QTextDocument>
#include "RideCache.h"
@@ -41,6 +42,7 @@ class Result {
// construct a result
Result (double value) : isNumber(true), string(""), number(value) {}
Result (QString value) : isNumber(false), string(value), number(0.0f) {}
Result () : isNumber(true), string(""), number(0) {}
// we can't use QString with union
bool isNumber; // if true, value is numeric
@@ -56,7 +58,7 @@ class Leaf {
Leaf(int loc, int leng) : type(none),op(0),series(NULL),dynamic(false),loc(loc),leng(leng),inerror(false) { }
// evaluate against a RideItem
Result eval(Context *context, DataFilter *df, Leaf *, RideItem *m);
Result eval(Context *context, DataFilter *df, Leaf *, float x, RideItem *m);
// tree traversal etc
void print(Leaf *, int level); // print leaf and all children
@@ -123,7 +125,7 @@ class DataFilter : public QObject
QList <PDModel*>models;
// microcache for oft-repeated vector operations
QMap<QString, Result> snips;
QHash<QString, Result> snips;
public slots:
QStringList parseFilter(QString query, QStringList *list=0);

View File

@@ -82,6 +82,7 @@ int DataFiltercolumn = 1;
[Dd]\' return SYMBOL; /* special case */
[Ss][Dd]\' return SYMBOL; /* special case */
[a-zA-Z0-9][a-zA-Z0-9_%™]+ return SYMBOL; /* symbols can start with 0-9 */
"x" return SYMBOL; /* the only 1 char symbol allowed */
"+" DataFilterlval.op = ADD; return ADD;

View File

@@ -216,7 +216,7 @@ void PMCData::refresh()
// although metrics are cleansed, we check here because development
// builds have a rideDB.json that has nan and inf values in it.
double value = 0;;
if (fromDataFilter) value = expr->eval(context, df, expr, item).number;
if (fromDataFilter) value = expr->eval(context, df, expr, 0, item).number;
else value = item->getForSymbol(metricName_);
if (!std::isinf(value) && !std::isnan(value))