Training 8 min read

Your training week is a constraint problem

How gostrong turns volume landmarks and dose-response research into hard constraints and objective terms, and builds your training week with OR-Tools CP-SAT.

Most training programs are a table. Monday is chest and triceps, Wednesday is back and biceps, and every muscle gets “3 x 10” of something. The table is simple to publish and simple to follow. It is also blind to you: your recovery limits, your split, the exercises you refuse to do, and the sets you actually have time for.

We think a weekly training template is better described as a constraint problem. There are hard limits (do not exceed what you can recover from), there are preferences (pick the exercises that give the most stimulus for the least fatigue), and there is a finite budget of sets per session. That is exactly the shape of problem a constraint solver is built for. In gostrong, a stateless Python service built on Google’s OR-Tools CP-SAT solves it.

What the solver is given

The solver receives your profile (goal, experience, calibrated volume landmarks, diet phase), a candidate exercise catalog with stimulus-to-fatigue ratings (SFR) and your preferences, your weekly split, and a block type: hypertrophy, strength, peaking or active rest.

It returns a single-week template: which exercises, how many sets on which day, and the target RIR (reps in reserve). Load is prescribed afterwards from your logged performance.

The three landmarks

Everything starts with per-muscle volume landmarks, in weekly working sets:

LandmarkMeaning
MV (maintenance)Below this, size and strength are lost
MEV (minimum effective)Below this, little growth
MRV (maximum recoverable)Above this, recovery does not keep up

Defaults depend on experience level and live in one shared file that both the Go application and the solver read. As you train, the feedback loop adjusts your personal MEV and MRV between mesocycles, not mid-cycle, following the autoregulation framework of Helms 2020. Diet phase scales the MRV too: lower on a fat-loss phase, higher in a surplus.

Which landmark becomes which kind of rule

The interesting design question is not “what are the numbers” but “how firm is each one.” We give the three landmarks three different hardnesses, matching what each one means:

LandmarkRole in the model
MVHard floor, never violated
MRVHard ceiling
MEVHard floor when the week can afford it, otherwise a priced shortfall

The MEV case is the subtle one. If the sum of every muscle’s MEV exceeds what your schedule can physically hold (training days times the block’s per-session set target), a hard floor would make the problem infeasible and you would get no plan at all. So in that case the floor relaxes into a penalised shortfall, with a stated order of who gives way first: muscles under an active specialisation, then primary muscles, then the accessory tier (abs, neck, forearms). No muscle drops below MV at any priority.

Here is a simplified sketch of the envelope, close to what the model does:

for muscle, exercise_ids in muscles_to_exercises.items():
    mv, mev, mrv = adjusted_landmarks[muscle]
    total = sum(weekly[e] for e in exercise_ids)  # weekly sets per exercise

    model.add(total >= mv)    # hard floor
    model.add(total <= mrv)   # hard ceiling

    if over_subscribed:       # sum(MEV) cannot fit the week
        shortfall = model.new_int_var(0, mev - mv, f"mev_shortfall_{muscle}")
        model.add(shortfall >= mev - total)   # priced in the objective
    else:
        model.add(total >= mev)               # normal case: hard

The relaxation only triggers when the alternative is no plan, so a normal solve behaves exactly like the hard-floor version.

The V-shaped objective

Hard limits define the feasible region. Inside it, the solver needs to know where to aim. We aim each muscle at a target between its floor and ceiling:

target[m] = MEV[m] + alpha * (MRV[m] - MEV[m])

alpha is set per block. The documented worked example for a hypertrophy block is an intermediate lifter in a muscle-gain phase, with a target that lands near 12 sets per muscle per week.

The cost of missing that target is asymmetric:

W_UNDERSHOOT = 100_000
W_OVERSHOOT  = 1_000_000   # 10x: overshooting is dearer than undershooting

undershoot = model.new_int_var(0, max(target, 1), "undershoot")
model.add(undershoot >= target - actual)
terms.append(W_UNDERSHOOT * undershoot)

overshoot = model.new_int_var(0, max(upper - target, 1), "overshoot")
model.add(overshoot >= actual - target)
terms.append(W_OVERSHOOT * overshoot)

Plotted against weekly sets, that cost is a V with a steeper right arm. The reason is the dose-response literature. The meta-regression in Pelland 2025 found about 0.24% hypertrophy per additional weekly set at an average of roughly 12 sets per week, with clear diminishing returns beyond that point and a possible inverted U past about 25 sets per week. If extra sets buy little, then overshooting spends recovery for almost nothing, while a small miss from below costs little. The asymmetry encodes that shape.

The same logic caps a single exercise at 10 sets per week (MAX_WEEKLY_SETS_PER_EXERCISE), pushing the solver toward a different exercise.

Frequency, sessions and variety

The remaining rules shape how the weekly dose is distributed:

  • Frequency floor. Each muscle on a scheduled day gets at least 2 sets that session, and no more than 10. Schoenfeld 2019 found no independent frequency effect when weekly volume is equated, so frequency’s job in the model is to spread the dose so no session is unproductively large, not to chase a magic number of days.
  • Balance. For a muscle trained on two days, the set counts on those days may differ by at most 3.
  • Per-exercise minimum. An exercise that is used gets at least 3 sets in hypertrophy and strength blocks, so you never see “1 set of bench press.”
  • Movement-pattern coverage. If a muscle gets two or more exercises, they must span at least two movement patterns where the catalog allows it. Two vertical pulls are one stimulus twice.
  • Direct isolation floor. Lateral delts, rear delts, biceps and calves get a minimum of direct isolation work, because compounds stimulate them poorly.
  • Variant dedup. At most one implement variant of a movement per day.

The session length is a soft target, not a wall. In a hypertrophy block it is 20 sets per session, and every set above it costs 10,000 in the objective. Making it soft matters: a hard session cap could force a muscle below its growth threshold only because no session had room left, when running one session slightly long would have avoided that. A set of MEV shortfall is priced at 1,000,000 or more, so a session runs long before a muscle goes without.

Why a solver beats a fixed template

A fixed template describes a typical lifter. A solver finds the best feasible week for this one.

  • Schedule. The split is an input. The solver builds its calendar from the cadence you chose (ppl_x2, ul_x3 or ulppl_5 are offered), and every constraint reads that calendar. Variables are only created where a muscle is rostered on a day, so forbidden assignments do not exist.
  • Preferences and exclusions. Blacklisted exercises never enter the candidate set. A candidate that cannot carry the volume is simply not chosen.
  • Recovery. MRV is a hard ceiling, adjusted by diet phase and your calibrated history. Squat and deadlift are kept off the same leg day on the two six-day cadences, because both carry comparable systemic fatigue.
  • Specialisation. Choose one or two lagging muscles and the solver aims them at MRV and everything else at MV for that block. The trade is the mechanism: recovery and session time are finite, so raising one muscle means lowering the rest. It is not offered in a real calorie deficit, since extra volume only converts to growth with the energy to support it.
  • Variety without chaos. The four tracked barbell lifts stay anchored so progression is measurable, while non-anchor slots rotate across mesocycles through a novelty reward. The reward only breaks near-ties; it never beats a clearly better movement.

The objective is a weighted sum with separated tiers: volume cost first, then SFR preference, then novelty, then session-flow nudges such as clustering a session onto fewer equipment types, then a tiebreaker. Each tier is sized so it cannot distort the one above it.

What is not in the model

No weekly RIR ramp in hypertrophy. The hypertrophy schedule is a flat RIR of 2. Refalo 2023 found that hypertrophy drops meaningfully past about 4 RIR while gains are roughly equivalent across 0 to 2 RIR. Strength blocks descend from 3 to 1 and peaking from 2 to 0, and the active-rest block is flat at 4.

No calendar deload. Coleman 2024 found that scheduled deloads at the midpoint of a 9-week cycle worsened strength gains and had no effect on hypertrophy. So a deload fires only when performance signals say so, and it is applied in the Go domain without a solver round trip.

No week dimension. With flat RIR, flat weekly volume and load as the progression variable, a multi-week model buys nothing. The template is solved once and replicated across the mesocycle, which makes a whole class of week-to-week bugs (exercises swapping, sets jumping, isolation work dropping out) structurally impossible.

Where the literature is thin, the code labels a constant a “defensible default” instead of dressing it up as a finding.

Your week, solved

gostrong does this for you: you pick a cadence, set your preferences, and the solver assembles a week that respects your limits and aims at the dose the evidence supports. See how gostrong builds your plan.

References

  • Pelland JC, et al. (2025). The Resistance Training Dose-Response: Meta-Regressions Exploring the Effects of Weekly Volume and Frequency on Muscle Hypertrophy and Strength Gain. Sports Medicine. https://pubmed.ncbi.nlm.nih.gov/41343037/
  • Refalo MC, et al. (2023). Influence of Resistance Training Proximity-to-Failure on Skeletal Muscle Hypertrophy: A Systematic Review with Meta-analysis. Sports Medicine. https://pmc.ncbi.nlm.nih.gov/articles/PMC9935748/
  • Schoenfeld BJ, et al. (2019). How many times per week should a muscle be trained to maximize muscle hypertrophy? Journal of Sports Sciences.
  • Coleman M, et al. (2024). Gaining more from doing less? The effects of a one-week deload period during supervised resistance training on muscular adaptations. PeerJ. https://peerj.com/articles/16777/
  • Helms ER, et al. (2020). Autoregulation in Resistance Training: Addressing the Inconsistencies. Sports Medicine. https://pmc.ncbi.nlm.nih.gov/articles/PMC7575491/