Alternative load calculation for mountainbiking

I am experimenting with getting the correct load for mountainbiking workouts. After I couple of rides, I found that calculating IF and load based on NP excluding zeros reflects my RPE better than using the regular NP (including zeros).

What is the JavaScript code to be added in custom fields to add this metric on my activity details?

Side note: IF based on Garmin’s [average power excluding zeros / FTP] is an even better estimate, but I could not find how Garmin calculates this value.

It is possible to calculate training load using a custom activity field and also update the intensity and load on the activity. Under the ride timeline chart choose “Custom” and “Add Field”. Name it MTBLoadCalc or similar and paste this on the script tab:

{
  let fixed_watts = icu.streams.fixed_watts
  let ans
  if (fixed_watts) {
    let tot = 0, n = 0
    for (let v of fixed_watts) {
      if (v) {
        tot += v
        ++n
      }
    }
    if (n > 0) {
      ans = tot / n
      if (icu.activity.type === 'MountainBikeRide') {
        //console.log("ftp", icu.activity.icu_ftp)
        let intensity = ans / icu.activity.icu_ftp
        //console.log("intensity", intensity)
        let hours = icu.activity.moving_time / 3600.0;
        //console.log("hours", hours)
        let load = intensity * intensity * hours * 100.0;
        //console.log("load", load)
        icu.activity.icu_training_load = Math.floor(load + 0.5)
        icu.activity.intensity = Math.floor(intensity * 100 + 0.5)
      }
    }
  }
  ans
}

You can see it is calculating the average power excluding zeros and returning that. However it also updates intensity and icu_training_load on the activity as a side effect … you need to re-analyse existing activities to get the new load calculation.

Many thanks for this. I have updated the code with load based on NP excluding zeros using a simple 30s moving average. See code below. This reflects the load of MTB rides even better than the average excluding zeros.

{
  let fixed_watts = icu.streams.fixed_watts;
  let ans;

  if (fixed_watts) {
    let df_nonzero = fixed_watts.filter(value => value !== 0);
    let movingAvgNonzero = [];
    const windowSize = 30;

    for (let i = 0; i < df_nonzero.length; i++) {
      let sum = 0;
      let count = 0;

      for (let j = i; count < windowSize && j < df_nonzero.length; j++) {
        sum += df_nonzero[j];
        count++;
      }

      if (count === windowSize) {
        movingAvgNonzero.push(sum / windowSize);
      }
    }

    let normalizedPowerSum = 0;

    for (let power of movingAvgNonzero) {
      normalizedPowerSum += Math.pow(power, 4);
    }

    let normalizedPower = Math.pow(normalizedPowerSum / movingAvgNonzero.length, 1 / 4);

    let hours = icu.activity.moving_time / 3600.0;
    let intensity = normalizedPower / icu.activity.icu_ftp;
    let load = intensity * intensity * hours * 100;

    icu.activity.icu_training_load = Math.floor(load + 0.5);

    if (icu.activity.type === 'MountainBikeRide') {
      icu.activity.intensity = Math.floor(intensity * 100 + 0.5);
    }

    ans = normalizedPower;
  }
  ans;
}

1 Like