Skip to content

[Feature Request] Multi-Formula 1RM Estimation with Reliability-Weighted Average #155

Description

@DuarteSantos8

Migrated from GitLab issue #76, opened by Giulio Leuci (@leuci.giulio on GitLab) on 2026-09-09. openGym moved back to GitHub on 2026-09-11; the GitLab thread is closed and points here.

Labels: enhancement, training-engine, algorithm

1. Current Statea

In frontend/src/lib/onerm.js, openGym currently implements only three 1RM equations: Epley, Brzycki, and Lombardi.

  • Epley is hardcoded as the default formula.
  • Sets above REP_CAP = 12 are rejected.
  • Each formula has recognized structural biases: Epley progressively overestimates 1RM at moderate-to-high repetitions (> 6 reps), Brzycki diverges sharply near 10–12 reps, and Lombardi is overly conservative at low repetitions.

2. Motivation

Estimated 1RM is a critical metric in openGym: it directly powers percentage-based load resolution (%1RM), the in-session load calculator, and PR tracking. Relying on a single equation introduces systemic distortion. A multi-formula composite average weighted by empirical reliability and penalized for high reps creates a stable, smooth, and robust estimate across all rep ranges up to 15 reps.

3. Feature to Implement

  1. Extend Formula Suite:
    • Add 4 peer-reviewed submaximal equations: O'Conner, Mayhew, Wathan, and Lander.
  2. Reliability-Weighted Average (weightedEstimate):
    • At r = 1: Return the exact lifted weight (1RM = w).
    • For r > 1: Compute estimates across all 7 equations. Weight each formula by its empirical reliability factor a_i and apply a logarithmic rep penalty factor w_i = \frac{1}{\log_{10}(r) + 0.1} \cdot a_i.
    • Set the default formula in openGym to 'weighted'.
    • Extend the safe calculation cap to WEIGHTED_REP_CAP = 15.
  3. Preserve Legacy Signatures:
    • Preserve exact backwards compatibility for explicit calls (e.g. estimate1RM(w, r, 'epley')).

4. Technical Design & Code Changes

// In frontend/src/lib/onerm.js






export const FORMULAS = {
  epley:    (w, r) => w * (1 + r / 30),
  brzycki:  (w, r) => w * 36 / (37 - r),
  lombardi: (w, r) => w * Math.pow(r, 0.1),
  oconner:  (w, r) => w * (1 + r / 40),
  mayhew:   (w, r) => (w * 100) / (52.2 + 41.9 * Math.exp(-0.055 * r)),
  wathan:   (w, r) => (w * 100) / (48.8 + 53.8 * Math.exp(-0.075 * r)),
  lander:   (w, r) => (w * 100) / (101.3 - 2.67123 * r)
}

const RELIABILITY = {
  epley: 1.0, brzycki: 1.1, lombardi: 0.95,
  oconner: 0.9, mayhew: 1.05, wathan: 0.9, lander: 0.85
}

export const DEFAULT_FORMULA = 'weighted'
export const WEIGHTED_REP_CAP = 15

export function weightedEstimate(w, r) {
  const reps = Math.round(Number(r))
  const weight = Number(w)
  if (!weight || weight <= 0 || !reps || reps <= 0) return null
  if (reps === 1) return weight
  if (reps > WEIGHTED_REP_CAP) return null

  const repPenalty = 1 / (Math.abs(Math.log10(reps)) + 0.1)
  let sum = 0, sumW = 0

  for (const [name, fn] of Object.entries(FORMULAS)) {
    const est = fn(weight, reps)
    if (!Number.isFinite(est) || est <= 0) continue
    const coeff = repPenalty * (RELIABILITY[name] || 1)
    sum += coeff * est
    sumW += coeff
  }

  return sumW > 0 ? Math.round((sum / sumW) * 10) / 10 : null
}

export function estimate1RM(w, r, formula = DEFAULT_FORMULA) {
  if (formula === 'weighted') return weightedEstimate(w, r)
  const fn = FORMULAS[formula] || FORMULAS.epley
  return fn(w, r)
}

Giulio Leuci (@leuci.giulio on GitLab) commented on 2026-09-09:

I've partially implemented this feature in my fork available in https://github.com/giulioleuci/simple-opengym-range using Claude Code and Codex. This fork, however, is simplified because I've eliminated the entire AI infrastructure, for example.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions