Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to the Berger formula #84

Closed
WhyAsh5114 opened this issue Sep 13, 2024 · 8 comments · Fixed by #98
Closed

Move to the Berger formula #84

WhyAsh5114 opened this issue Sep 13, 2024 · 8 comments · Fixed by #98
Assignees
Labels
help wanted Extra attention is needed v3-enhancement New feature or request for v3
Milestone

Comments

@WhyAsh5114
Copy link
Owner

Describe the bug
The current formula sometimes (rarely) causes really weird progressions. It increases the load for the first two sets which is great and expected, but does something weird with the last set.
image

Idea to fix
Remove the +/-37 from the Brzycki formula, it doesn't really make a lot of sense and feels arbitrary. Going from 1 rep to 2 reps is a 100% increase in work volume, but according to Brzycki's formula its a 2.8% increase, which feels really wrong. And to avoid any division by 0 errors, just add 1 to both old and new performances to prevent errors.

Would like some suggestions as well @patrick-b-c

@WhyAsh5114 WhyAsh5114 self-assigned this Sep 13, 2024
@WhyAsh5114 WhyAsh5114 added v3-enhancement New feature or request for v3 help wanted Extra attention is needed labels Sep 13, 2024
@WhyAsh5114 WhyAsh5114 added this to the v3-stable milestone Sep 13, 2024
@patrick-b-c
Copy link

patrick-b-c commented Sep 14, 2024

Removing the +/-37 makes no sense.

The 37 is a factor found experimentally by Brzycki, basicly testing a lot of peoples 1 rep max, at various loads and reps, and doing a regression on the data. The regression which best fit the data was 37, therefore 37.

Lets set r_2, r_1 (RIR targets last time and now) = 0
a = 0
F=1 (no progression)

If we remove the 37 the formula reduced becomes.
$R_{2}=\frac{M_{2}R_{1}}{M_{_{1}}}$

Which makes no sense (if you double the weight, you double the reps you must do).

The Brzycki is a good simple formula... But not without its flaws. Things becomes weird >25 reps (incorrect) and breaks at 37 (dividing by 0). If I remember correctly Brzycki mainly did test in the range around and below 10. So at way higher reps (like 25) the formula was never meant to calculate 1 rep max (however my experience says its good up to 20-25 reps)

A better formula which doesnt break at 37 and handles higher reps better is the Berger formula (adjusted by me to be reversible).
$M_2 = F M_1 e^{\frac{131 (r_1 - r_2 + R_1 - R_2)}{5000}} - 0.0434698 F e^{\frac{131 (r_1 - r_2 + R_1 - R_2)}{5000}} + 0.0434698$

Rearranged for R_2

$R_{2}=38.1679\ln\left(\frac{F\left(9745640M_{1}-423641\right)e^{\frac{131(r_{1}+R_{1})}{5000}}}{9745640M_{2}-423641}\right)-r_{2}$

Where:
R_2 is the reps at the new weight.
M_1 is your previous used weight.
M_2 is your new weight
F is the preferred overload percentage as a decimal. (So a 2.5% overload would be F=1.025)
R_1 is your previous performed reps
r_1 is your previous RIR for previous weight
r_2 is your target RIR for the new weight

There's no a (adjustment) setting this time. Again you can solve for R_2 to find reps instead.
However as you see this formula is clearly more complex.

Here I've set M_1 = 100, R_1 = 1, r_1 = 0, r_2=0, F=1. We then have R_2 on the x-axis and M_2 on the y-axis.
The blue line is the Brzycki formula
The red line is the Berger formula
The dots are data from a recent meta analysis (actual measurements)

As you see, the Brzycki formula and Berger formula is similar <15 reps. But diverges thereafter.
The Berger formula also way better fits the data (and is the formula which fits the data the best AND is reversible, of all the published formulas)
Being reversible means if you input 100x1 and get 40x10, then if you input 40x10 you should also get 100x1 back
A non-reversible formula could give something like 100x1 -> 40x10 but 40x10 -> 102x1
Which is really not good, as it makes the formula unreliable when you apply it many times (and we apply it every workout)

image

In regards to how (on your screenshot) it seems like it suggests 0 reps at 110 load. Here it should probably have suggested 105 at 2 reps.
I believe this more has to to with how your implementation is, not the formula it self.
I would probably implement it someway like this:
Always assume the weight doesnt change from last time, and just solve for R_2 (This would almost always be +1 rep from last time)
If the new reps is above some set target reps. We instead increase the weight by some set increment, and then recalculate the reps for this new weight.

However, if the input parameters align correctly, I still believe this approach could lead to negative or 0 reps.
Imagine we're working at low reps (like 2) and have set a high increment (lets say 20 for this example), and a high F (progression) at something like 1.05, and a target max reps at 2.
Last time you did 100 load x 2 reps
The Berger formula would then suggest 3.86 reps, which is above our target, so we increase by 20 in load.
This gives a suggested reps at -3.09

This can be mitigated by making sure the new reps is above some set value (like 0.95, which rounded would be 1. If we set a low threshold like 0.1 or 0, we round that up to 1 rep, but that would be really difficult to lift and way more than 1.05 progression. Remember if we're rounding up, then F is the minimum progression, therefore F should be rather low like 1.005 (I've even set it to 1.001 for my own calculations) as most often the actual increase will be higher.
If its not above that value, we should either:
Allow increments smaller than the set increment (not really feasible in most cases)
or
Allow to break the max target reps.
I suggest breaking the max target reps.

So:
1: Set the same weight as last time (M_2 = M_1), calculate R_2
2: Check the new reps (R_2) at this weight, if its above the max target reps, increase M_2 by some increment (lets call it I)
3: Set M_2 = M_1 + I and calculate R_2
4: Check R_2 if its below some minimum threshold (like 0.95) instead do not add the increment, and lets accept a reps target higher than our max target.
5: In this case recalculate R_2 with M_2 = M_1

If you want the user to add a minimum allowed reps, you could do that... So if the user dont want reps below 5. We use 5 instead of 0.95. However this must mean that we're honoring the minimum target reps as a hard limit, but the max target reps as a soft limit (we're allow to go above max target reps, IF the alternative is to go below our minimum target reps)

Another alternative approach used by powerlifters, is simply to not increase anything at all but accumulate our expected rep max E(RM) (which you get by setting R_2 to 1 and solving for M_2) at each workout, until we're strong enough to bridge the increment gap without negative numbers.
Example (made up numbers):

Workout 1:
100kg x 2 reps (Calculated E(RM) = 100*1.05 = 105, but we're getting -3 reps from the formula, so we keep reps and weight for next workout, but remember the 105)

Workout 2:
100 x 2 reps (Calculated E(RM) = 100 * 1.05 * 1.05 = 110.25, but we're getting -1 reps from the formula, so we keep reps and weight for next workout, but remember the 110.25)

Workout 3:
100 x 2 reps (Calculated E(RM) = 100 * 1.05 * 1.05 * 1.05 = 115.7625, but we're getting 1 reps from the formula, so we keep reps and weight for next workout, but remember the 110.25)

Finally at workout 4, the formula gives us 120kg x 1.8reps rounded up to 100x2 reps, so now we bridge the increment.

Hopefully this makes sense, its a bit difficult to conway in a short comment.

Lastly regarding the 2.8% increase.
Remember: We're not calculating workload here, but strength.
The 2.8% increase amount to you being 2.8% stronger.
So if you we're able to lift 100kg for 1 reps last week, now you should be able to lift 102.8kg for 1 rep this week.

And yes. Being able to lift 100kg x 2 reps or 102.9kg x 1 reps is (around) equally difficult. If youre able to lift 100kg for 2 reps, youre not twice (+100%) as strong as someone who can lift 100kg for 1 rep. Youre +2.8% stronger.
This of course means we're basing our progression on strength, not volume. Volume progression is good. But we cant use reps to increase the volume as this would be infeasible (you cant just suddently jump from doing 100kgx5 to 100kgx10 just because you want twice the volume. If you want twice the volume you should double the number of sets).
In the RP app, these are also seperate. Strength is progressed by increasing load and/or reps. Volume is progressed by adding sets, and intensity is progressed by lowering RIR.

@WhyAsh5114
Copy link
Owner Author

Damn, really really appreciate the great explanation. Thanks a lot for taking the time out to write this all down for me :)

I'm currently not near my workstation, so typing this from a mobile. I do have a bit of trouble wrapping my head around 100 * 2 is very similar to 102.9 * 1 cause I've been using the traditional work volume since so long and I don't really have any proper experience with exercise science (just YouTube)

I do indeed follow some of the things u mentioned for the progressive overload formula but it's difficult to write it all down right now, I'll add a detailed comment about that once I reach home. Again, thanks a lot!

@WhyAsh5114
Copy link
Owner Author

Okay so here's how I've currently implemented the overload function based on my understanding and personal experience:

Drop-off calculations

As we perform sets close enough to failure for each exercise, we expect rep performance to drop slightly for every next set. If that isn't happening, likely, we aren't going close to failure. So based on these assumptions, I do the following:

  1. Calculate average drop-offs in reps from one set to the next
  2. Sort the sets based on the difference between last performance's drop-offs and the average difference (highest to lowest)
  3. Start increasing reps 1 by 1 till the desired overload percentage F is reached based on the sorted sets

For example, consider the following rep numbers for an exercise's 3 sets:

  1. 12 > 11 > 10
  2. 13 > 12 > 10

In this case, the algorithm will attempt to increase the 3rd set's numbers first and the others next. Since we are calculating drop-offs we don't get how much we want to increase the first set directly. So, if the first difference between the average drop-off and the last drop-off is negative (i.e. generally the first set does more reps than the second set), we increase the first set.

Overload percentage adjustment

While creating a mesocycle, there's a variable you can set that instructs the algorithm how much you want set performances to increase. The algorithm then measures your actual performance changes and performs a weighted average of the set performance change and the actual performance change.

Load adjustment

After the reps have been increased and we have reached close to the desired overload percentage, we then check if the reps are within the rep range, if not we use the Brzycki formula to adjust the reps according to the new load. The typical load adjustment is 5 but can be overridden for each exercise.

Set increases

During meso creation, we also set volume landmarks and desired weekly increases. If performance increases are near ideal, we then increase sets for that exercise. I still need to adjust this to get the average performance improvement of the muscle group instead of individual exercises and add sets to exercises with the least number of sets (#90).

RIR adjustment

In this last step, if the planned RIR is different than the RIR performed last time, the reps and RIR are adjusted according to the current microcycle number and some progression settings:

  • forceRIRMatching: increase RIR at the cost of reps to meet planned RIR
  • lastSetToFailure: the last set should always be 0 RIR

These progression settings are taken as preferences during mesocycle creation and can be customized (overridden) for each exercise.

That's about it for the current implementation, this is just based on my limited YouTube knowledge and personal experience, would appreciate your feedback on this @patrick-b-c

@WhyAsh5114
Copy link
Owner Author

I'll implement the new formula, it is indeed more elaborate than the Brzycki formula, but I can abstract that complexity away into a function as the variables required for both formulae are the same, so no worries about that.

@patrick-b-c
Copy link

Hi again
Regarding: Drop-off calculations
When I've played around with this myself, I ended up just increasing the set with the lowest amount of reps, but I feel your approach is better. Let me understand it.
So lets say the calculated increase is 1 rep per workout (we dont care about the weight increase yet, just how you add reps):
1st workout: 15 -> 10 -> 7 -> 6
We would increase set 2 as it had a drop off at 5?
2nd workout: 15 -> 11 -> 7 -> 6
We would increase set 3 as it had a drop off at 4 (set 2 also had a drop off at 4)
3rd workout: 15 -> 11 -> 8 -> 6
We would increase set 2 as it had a drop off at 4
4th workout: 15 -> 12 -> 8 -> 6
We would increase set 3 as it had a drop off at 4
5th workout: 15 -> 12 -> 9 -> 6
We would increase set 4 as it had a drop off at 3 (all sets have a drop off as 3)
6th workout: 15 -> 12 -> 9 -> 7
We would increase set 3 as it had a drop off at 3 (set 2 also have a drop off as 3)
Do I misunderstand it?

This problem is difficult (and subjective?) and I had a difficult time implementing this myself.
Lets say you have: 15 -> 10 -> 5
Then you calculate the 1RM for each set, lets say: 100 -> 80 -> 50
I imagine the goal would be to keep the ratios of the 1RM decreases, from set to set, the same over time. Maybe using exponential moving average, to update the ratios over time. Then you'd try to find the reps next time, which both keeps the ratios close to the target ratios and ensures we are as close to out goal progression rate as possible.

But again I kind of just gave up on that, and went with the set with lowest reps. As the user will try to hit a RIR target any way, they should over time correct itself, but at the cost of the target reps not always being correct.

I like your implementation of overload percentage adjustment

Regarding: Load adjustment
Does your algoritm look at the average reps for the sets, to determine if we're outside the target range. Or do you look at each set individually? Looking at each set individually would make sure all sets are within the range, but would lead to different weights for each set. Looking at the average makes sure all sets use the same weight, but could result in some sets being higher/lower than the rep range (but the average is thus within the desired range)

Set increases: Very awesome!.... Yeah using performance instead of proxies makes a ton of sense to me... However I'd probably have the option for the user to use performance or proxies or a combination to determine volume changes... But thats just me...

@WhyAsh5114
Copy link
Owner Author

WhyAsh5114 commented Sep 16, 2024

I didn't quite explain the drop-off thing well enough, let me try again. It starts taking effect only after 2 microcycles have been logged as we need averages.

Logged reps

12 > 11 > 10 (dropoffs: 1, 1)
13 > 12 > 10 (dropoffs: 1, 2)
?? > ?? > ?? (average dropoffs: 1, 1.5)

We have averages for the 3rd workout and the last workout's drop-offs. Now we can start to create priorities for each set, a higher priority set should be increased first in reps as that change would represent the best fit to the general rep drop-off for this exercise.

Drop-off differences for the new workout

dropoffDifference = lastDropoffs - averageDropoffs
dropoffDifference = [1, 2] - [1, 1.5] = [0, 0.5]

First set adjustment

We only got the priorities for 2nd and 3rd sets, to calculate the priority value of the first set, we use the following formula:

// It is better to increase reps of set 1 to best-fit drop-offs
if secondSetDropoffDifference < 0: 
    // Remove the negative sign and set firstSetPriority as the magnitude
    firstSetPriority = Math.abs(secondSetDropoffDifference)

// Increasing first-set reps is not a good move for best-fitting
else:
    firstSetPriority = 0

Now we finally get the full scores of all set priorities [0, 0, 0.5], we pass this to a function to perform increases in reps to the highest valued sets first and stop when we reach the desired overload percentage.

Regarding the other stuff

Load adjustment

  • For load adjustment in straight sets, I see if any reps of the set are beyond the rep range and adjust all the weights accordingly.
  • If it's not a straight set where the weights can be variable, then the algo changes them individually

Set increases

Yeah, the performance proxies just seemed easier to implement and more objective. Giving the option to the user to use other proxies is a good idea, but not something I want to add to v3-stable, maybe somewhere down the road in a feature release like v3.1

@patrick-b-c
Copy link

Thats a really nice algoritm for the drop off calculation. Very nice, and definetly the best I've seen in any app.

Regarding: Load adjustment
Thats a perfectly fine way to do it as well... Maybe (far down the line) one could have it based on the set with highest number of reps, the average reps or fewest reps.

Set increases
Yep, again this is really just icing on the cake, which the big fitness app companies doesnt even do. As said, performance is probably the best indicator any way.

Keep working, v3 seems miles better than v2 already, the progress is insane, keep it up :-)

@WhyAsh5114
Copy link
Owner Author

Thank you for the kind words ❤️

@WhyAsh5114 WhyAsh5114 linked a pull request Sep 18, 2024 that will close this issue
7 tasks
@WhyAsh5114 WhyAsh5114 changed the title Modify the Brzycki formula Move to the Berger formula Sep 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed v3-enhancement New feature or request for v3
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants