.. it broke all the builtin functions !
.. we need to check for user functions after all the
builtins e.g. config(cp) was marked as inerror
when it should not have been.
.. User Metrics now integrated into the factory, ride cache
and of course rides and intervals. Which means you can define
a user metric and it will be computed and displayed like any
of the builtin metrics.
.. lots of technical changes to support this:
* DataFilter gets a runtime object to support multi-threading
and uses a context for construction only - item contexts are
used when evaluating an expression
* RideMetric factory can now remove user metrics
* The context in which the user modifies the user metrics will
notify all other contexts of the change ***
*** NOTE: STRONGLY RECOMMEND THAT YOU DO NOT HAVE MULTIPLE ATHLETES
OPEN WHEN DEVELOPING NEW METRICS SINCE IT WILL TRIGGER A
METRIC REFRESH FOR ALL OPEN ATHLETES.
.. datafilter uses a context that was used to create it
which is fine until it is used in user metrics which
are global and shared across multiple contexts.
.. so now when evaluating a ride item we use the context
for the rideitem being evaluated not the context of
the data filter performing the evaluation
.. next couple of commits will need to look at the way
we use RideItem and RideFile when computing both
Ride and Interval metrics.
.. User metrics can be created, deleted and edited
in the preferences pane.
.. A new dialog has been created to create user metrics
and will need to be updated after step 3 of this multi
part update completes the UserMetric code and integrates
it into the RideMetric factory.
We now have named functions that can be called in the datafilter.
This is primarily to support user defined metrics where we will expect
the user to optionally define a bunch of functions we will call as
part of the ridemetric methods (see design mock up).
.. Datafilters can be defimed in three forms;
* a single line, typically as a filter
e.g. TSS > 100
* a block of code, typically as a formula
e.g. { val <- TSS; val > 100; }
* a program made up of functions, typically as a user metric
e.g. { pass { TSS>100; } main { pass(); } }
.. This example is functionally equivalent to "TSS>250":
{
pass {
# only filter rides with a high TSS
TSS > 250;
}
main {
# call our function to filter rides
pass();
}
}
.. Functions can only be defined within a block
.. Functions must be defined before use
.. A "main" function must be defined as an entry point
into the program if any functions are defined.
.. NA equates to RideFile::NA when working with
samples or averages etc and wanting to check
.. RECINTSECS is only really available when working
with ride samples (e.g. in user metrics coming soon)
.. Added if else logic but could not avoid
needing the statement to be terminated with a
semi-colon (;)
.. so examples are:
if (TSS>100) TSS; else 0;
if (TSS>100) {
temp <- TSS;
temp;
} else {
temp <- BikeScore;
temp;
}
.. Doesn't add much beyond the existing conditional statement
using '?' and ':' but is a lot more readable (!)
.. You can now use compound statements in data filters
.. A compound statement is:
{ s1; } evaluates to s1
{ s1; s2; s3; .. sn; } evaluates to sn
.. It is possible to use the recently added user defined
symbols to build up processing logic:
{ temp <- TSS; temp > 100 }
is functionally equivalent to
TSS > 100
.. At present there are no control statements outside of
the ? and ?: operators but these will no doubt be added
in due course.
.. allow users to introduce their own symbols
into a datafilter with:
symbol <- expression
Which will initialise symbol and evaluate it
with the expression. The symbol can override
a metric name, but that would be rather dumb!
.. x ?: y
Evaluates to x if it is non-zero, otherwise it
evaluates to y. y is only evaluated once and only
if x is zero. Similarly, x is only evaluated once
and returned if it is non-zero.
.. allow users to use a set command to add
an override or set metadata under certain
conditions.
e.g. set(Workout_Code, "HT", IF>0.9)
set(TSS, 65, TSS <= 0 && Route = "Fave Loop")
.. we should probably add an unset() command
to do the reverse and clear values under
conditions too.
USE WITH CAUTION -- TAKE A BACKUP BEFORE
MAKING ANY RADICAL CHANGES TO YOUR DATA
.. passed to eval and symbols now recongised.
.. sample data is referenced using symbol names
in all caps to make it clear you are referencing
ride dample data.
.. the next commits will add defining formulas and plotting
the derived data series in allplot.
.. 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.
.. when running a formula it may use a vector
operation to calculate e.g. an average for the
date range.
.. these get called for every ride, but actually they
perform the same calculation over and over
.. we cache the vector operation and result to avoid
repeated calculations; these are only cached whilst
a datafilter is being evaluated, so we do not need
to worry about stale/refreshing.
.. estimate(model, duration) or to get the model
parameters estimate(model, parameter) always
for the date of the ride.
e.g. estimate(2p, cp) gets the estimated CP
for the date of the ride using the classic
2 parameter model
Models are one of;
2p - classic monod scherrer
3p - morton 3p
ext - gc extended
ws - ward-smith
velo - veloclinic
Parameters are one of;
w' - W'
cp - critical power
ftp - functional threshold power
pmax - max power
For watts per kilo just add / config(weight)
or Athlete_Weight to take into account ride
specific weight settings / overrides.
.. can now use a formula in the lts, sts
sb, rr functions.
.. e.g lts(TSS/2) evaluates to the "CTL"
value for the ride if using TSS/2 as
the input.
NOTE: you only get a value where a ride
exists. We may want to think about the
way we scope formulas; are they iterating
over rides or dates ?
.. to use when e.g. using a data filter as an input
into the PMC functions. The data series is stored
against a signature for the function as opposed to
the metric symbol.
.. we need to do it for an expr not the data filter
.. to use when e.g. using a data filter as an input
into the PMC functions. The data series is stored
against a signature for the function as opposed to
the metric symbol.
.. but do continue to highlight bad symbols etc
with a red wavy line.
.. still need to;
a) syntax highlight in the formula edit (just symbols
and literals, not parsing content)
b) add an error navigator to click on errors and have the
text highlighted that incurred that error.
.. the daterange() commit broke almost every builtin
function ! (it converted them to integers due to
and if/else logic error.
.. also tidied parser to separate literals and symbols
whilst making builtin functions expr elements to
look more closely at precedence
.. oopsie on binary expression.
NOTE: it may be better to highlight tokens rather than
via the parser as errors make it impossible to
highlight the "intention" of the user
Particularly useful when working with a vector
but can be used with other things.
e.g. max(xPower, BikeScore)
Or, for getting a ride's TSS expressed as a percentage
of the average TSS for the currently selected daterange.
e.g. TSS / mean(TSS[daterange(start):daterange(stop)])
You can create a vector using the notation:
expr [ from : to ]
Where 'expr' will be evaluated for every activity
between the dates from and to.
e.g. TSS[today - 90 : today]
will evaluate to TSS for the last 90 days
e.g. Activities [ date:date ] > 1
Will find all days with more than one workout.
.. when using a vector in any arithmetic expression it
will be evaluated to a sum; this will be fixed shortly
to enable the use of sum/mean/max/min functions.
.. If needed we can add vector operations but this will
likely confuse many users (hell, they confuse developers)
so we will need a good reason to add them !
Lastly, I have also added daterange(from) and daterange(to)
literals to get access to the currently selected daterange
when working in the trends view.
.. update the editor to do some basic syntax
highlighting; literals in red, comments in
blue and so on.
.. next commit will focus on highlighting errors
with a wavy line and some form of error list.
.. updated the datafilter to handle general functions
and parse them without needing them declared in the lexer
.. makes it much easier to add new functions in the future
.. did this to add core math functions;
* sin, cos, tan, asin, acos, atan,
* sinh, cosh, tanh, asinh, acosh, atanh
* exp, log, log10, ceil, floor, round
* fabs, isinf, isnan
.. we can add more later; erf/gamma spring to mind !
.. uses the C/C++ notation:
expr ? expr2 : expr3
Where expr is the condition e.g. X>2
and expr2 is what to evaluate to if expr is true
and expr3 is what to evaluate to if expr is false
e.g.
(Workout_Code = "1L3") ? 1 : 0
Will evaluate as 1 for all workouts where the
workout code is 1L3 and 0 for all other workouts.