Decoupling for running

I’ve tried to modify the pace at zone script as you suggested and created the Pace Decoupling field.

There is still some issues with numbers precision, so the result is not precise for all activities :frowning:

If someone with more knowledge of javascript can help to solve this issue it would be great. Below is the current implementation we have

// only considers run activities
if(activity.type=='Run')
{

// get data streams
hrStr = streams.get("fixed_heartrate").data
velStr = streams.get("velocity_smooth").data 

// initializes counters
count1 = 0
velMed1 = 0
hrMed1 = 0
count2 = 0
velMed2 = 0
hrMed2 = 0

// iterate over first half of data stream
for (let i=0; i<velStr.length/2; i++){
    hr=hrStr[i]
    vel=velStr[i]
    
    // count number of points
    count1++

    // acumulates velocity and hr points
    velMed1 = velMed1 + vel
    hrMed1 = hrMed1 + hr
}

// iterate over second half of data stream
for (let i=((velStr.length/2)+1); i<velStr.length; i++){
    hr=hrStr[i]
    vel=velStr[i]
    
    // count number of points
    count2++

    // acumulates velocity and hr points
    velMed2 = velMed2 + vel
    hrMed2 = hrMed2 + hr
}

// calculates averages
velMed1 = velMed1/count1
hrMed1 = hrMed1/count1
velMed2 = velMed2/count2
hrMed2 = hrMed2/count2

// calculates pace/hr for each half of the run
pchr1 = (velMed1/hrMed1)*100
pchr2 = (velMed2/hrMed2)*100

// calculates aerobic pace decoupling
// issue with rounding in the calculation below, this number is too small and apparently precision is lost in the operation
paceDec = ((pchr1-pchr2)/pchr1)

}```

Hi @Matheus_Silva,

I also wrote some Javascript using normalised GAP smoothed over a chosen period, im using 60s but you could use whatever you like. This is some edited code from another custom field. Let me know what you think.

EDIT: I also made this public so you should be able to get it on the interval field explorer.

EDIT2: I also made this public for the activity and intervals, so its there for both now. Let me know if anyone has any ideas to make this better / more accurate.

if ((activity.type == "Run" || activity.type == "VirtualRun" || activity.type == "Ride" || activity.type == "Walk" || activity.type == "Hike") && !activity.icu_ignore_hr)
{
    smoothBy = 60 //seconds
    
    gaStr = streams.get("ga_velocity").data
    hrStr = streams.get("fixed_heartrate").data
    hrSum = 0
    xgap1 = 0
    xgap2 = 0
    gaSmooth1 = 0
    gaSmooth2 = 0
    count = interval.end_index - interval.start_index

    // Number of points in first half of activity
    count1 = Math.floor(count / 2) 

    // Number of points in second half of activity
    count2 = count - count1

    // Find EF for first half of activity
    for (let i=0; i<count1; i++)
    {
        hrSum = hrSum + hrStr[interval.start_index + i]

        gaSmooth1 =  gaSmooth1 + gaStr[interval.start_index + i]
        if(i%smoothBy == 0 || i+1 == count1)
        {
           xgap1 = xgap1 + ((gaSmooth1/smoothBy ) ** 4)
           gaSmooth1 = 0
       }
    }

    // Calculates average HR for first half
    avgHR1 = hrSum / count1

    //calculates normalized average
    xgap1 = (xgap1 / Math.ceil(count1/smoothBy))**(1/4)

    eff1 = (xgap1 * 60) / (avgHR1)
    eff1 = +eff1.toFixed(3)

    // Reset Hr sum to zero
    hrSum = 0

    // Find EF for second half of activity
    for (let i=0; i<count2; i++)
    {
        hrSum = hrSum + hrStr[interval.start_index + count1 + i]

        gaSmooth2 =  gaSmooth2 + gaStr[interval.start_index + count1 + i]
        if(i%smoothBy == 0 || i+1 == count2)
        {
           xgap2 = xgap2 + ((gaSmooth2/smoothBy ) ** 4)
           gaSmooth2 = 0
       }
    }

    // Calculates average HR for first half
    avgHR2 = hrSum / count2

    //calculates normalized average
    xgap2 = (xgap2 / Math.ceil(count2/smoothBy))**(1/4)

    eff2 = (xgap2 * 60) / (avgHR2)
    eff2 = +eff2.toFixed(3)

    decoupling = (((eff1 - eff2) / eff1) * 100)
    decoupling = +decoupling.toFixed(3)
}
2 Likes

I found that this only worked with about half of my workouts, took a look at the code and found a problem in how it is parsing the second half of the data streams.

When velStr.lenght (the number of datapoints in the data) is an odd number, halving it will not give an integer, e.g: if velStr.length === 2409, then velStr.length/2 === 1204.5.

The easiest way to correct it is to change 1 line

// iterate over second half of data stream
for (let i=((velStr.length/2)+1); i<velStr.length; i++) {

with

// iterate over second half of data stream
for (let i=(Math.floor(velStr.length/2)+1); i<velStr.length; i++) {

The Math.floor() function will round down the result of (velStr.lenght/2) to an integer, which can then be used as an index in the loop.

Hope it helps!

I couldn’t find a way to use this script in a way that also calculates the data for intervals and not only the whole activity, but I haven’t read any documentation yet.

Best regards and hope it helps! It worked like a charm for me

Wow this works great too, and with intervals! Many thanks!

In JavaScript, the index of a for loop must be an integer. If velStr.length equals 4221, then velStr.length / 2 will result in 2110.5, which is a floating-point number. Therefore, the loop won’t work as intended.

To make the loop work correctly, you’ll need to convert velStr.length / 2 to an integer. You can use Math.floor() for this, which rounds down to the nearest integer:

Hello Adam. I am using your script but noticed it doesn’t compute decoupling on my treadmill runs. I guess the problem is that in these runs the GAP is (in my opinion wrongly) calculated as zero. The best solution would be to correct the GAP computation to be = to pace on the treadmill, but while that happens (@david ) would it be possible to get in your script the pace when the GAP is reported to be zero? Thank you!

Hi Marco,

I attempted to add this, I’ve got it working for the overall activity using the activity velocity, however, it is good to remember that this is an estimation from your watch and will not be very accurate in general, since there is no GPS data. Remember the data is only useful if it is accurate.

I added it to the interval as well, but currently it does not work. It seems it still processes all of the data, rather than only the data in the given interval. I am using the velocity_smoothed stream as my data point for this. If anyone is aware of a way to get to work, please let me know and I will add it.

EDIT:

I have refactored and updated the script, I found a few bugs in it that prevented it from working in Intervals, it actually never worked correctly for them.

Changes:

  1. Now working for intervals correctly
  2. Not using GAP anymore, just velocity. GAP is very noisy and does not produce appropriate values for almost any run. I have compared this new version with Power:HR decoupling and it is very close for most activities on flat-ish ground. Obviously this will not work for runs that have variable elevtation gain, rolling runs may work fine, but not for intervals on hilly terrain.
  3. Uses the Warmup and Cooldown times specified in the settings to remove the warmup and cooldown from the calculation.
  4. Works for Treadmill runs as well.

This will not be appropriate for trail running / hill running. Use a power monitor, like RunPoweModel for Garmin or a pod on your shoe for these activities. This is a limitation of pace data and our current GAP models.

GREAT! THANK YOU!

Just one comment. My speed data on the treadmill is very accurate, more than outside because it doesn’t come from my watch. I have a Technogym that transmits its speed data via Bluetooth so it is the equipment I use for all my tests. I can have very accurate control of speed, temperature, etc.

Thank you again!

I’m glad you find it useful, let me know if you notice any more issues or bugs :slight_smile: Oh right, that is good to be able to connect your treadmill to your devices, makes that data much more accurate.

1 Like

Hello @Adam_Donaldson. The filed is not working anymore on my treadmill runs :(. Any idea why? Thank you.

Hi Marco, sorry its not working, it works for me at the moment, but I am analysing old activities as I haven’t run on the treadmil in a while.

Does it not work at all for you, or not for specific activities? Does it not work for the overall activtiy or for intervals or both?

It doesn’t work for my new treadmill runs. I always have at least 2 intervals on those runs (warm-up


and actual run). I am attaching a screenshot. Thank you!

Your zones are not setup for this type of activity.

image

Make sure that the activity is logged as something that is part of your Run category!

This has nothing to do with the problem. In running, FTP is not needed for decoupling computations. Actually, not even in cycling. Pace or Power and HR with time are the only things needed.

Hello @Adam_Donaldson . Update from today. It stopped working also for my outside runs with 2 intervals. Attaching another screenshot. The ? mark fild


is set to show your “Aerobic Pace Decoupling”.

Works perfectly fine for me. Both in Intervals and in selections.

@Adam_Donaldson today it worked only on the second interval. Screenshot attached.

Hi Marco, I haven’t been able to reproduce the failures you saw, it works for me. Let me know if this continues happening, I may be able to reproduce it if you send me the data from an activity that does not work.

Sorry @Adam_Donaldson, I didn’t get the notification for your answer. Ye, it is still happening. I noticed that it happens when I let the intervals be the ones from lap presses on Garmin. Then if I move the intervals manually, sometimes it works and sometimes I have strange values like “Infinity”. I am sending some more screenshots. What file would you prefer? The one straight from Garmin? Thanks!




@Adam_Donaldson It’s the same behavior also on outside runs (first screenshot w/ laps from my Garmin watch). When I manually move the intervals everything works (second screenshot).