Build your optimal circuit training workout via linear programming

The days are getting longer and it is time to prepare for summer again. All the months spent indoors, waiting for the warm weather, eating cookies, and drinking hot chocolate could make you nervous going back to the beach. Imagine a turbocharged workout routine that mixes cardio and strength training and let you train everywhere. Plus, you don’t even need expensive equipment and it’s easily customized to help you to get your best Beach Body ever. Sound too good to be true? It’s not! It’s called circuit training.

Scheveningen, Netherlands

Circuit Training

Circuit training lets you alternate between 4 to 10 exercises that target different muscle groups. The whole idea is to train different muscles all at the same time in a minimum amount of rest. Usually, you train between 15 and 40 minutes. During this time, you will make multiple circuits, with each course containing all exercises. It is a good idea to make 2-3 minute breaks per circuit, 30-90 seconds per exercise, and a 3-5 minute warm-up. The exercises should be arranged so that different muscle groups are trained alternately.

First tries to make a human flag, Germany

To create your perfect circuit training routine you could hire a personal trainer. But let’s be honest: trainers are very expensive. For this task, we will just replace the trainer with an artificial intelligence agent. The agent will receive our training goals (e.g. a bigger biceps or a six-pack) as input and then tell us our personalized circuit training routine.

Linear Programming

To solve this kind of optimization problem we will use linear programming. Linear programming finds an optimum of a linear function that is subjected to various constraints. You can model a lot of problems as linear functions, finding the perfect circuit training is one of them. To solve our problem we have to do the following steps of the optimization workflow:

  1. Identify the exact problem
  2. Model the problem
  3. Choose a tool to deal with the model
  4. Retrieve the solution
  5. Analysis
One of the first applications of linear programming was in the second world war. But it can also be used for diet optimization, in sports and much more.

In our case, we want to build a circuit training that is as compact as possible and trains all of our muscles. So we want to build a linear function/objective function that describes the total amount of time for the workout. We want to optimize this objective function by modifying the design variables \(x_{i} \in \{0,1\}\).

minimize: \( \sum_{i=1}^{n} t_i \cdot x_{i} \)

The design variable \(x_{i} \in \{0,1\}\) is an exercise and the given coefficents \(t_i\) is the needed time for exercise \(x_i\). \(n\) is the number of exercises.

Each exercise has got its own intensity per muscle group. So pull-ups train for example the biceps, the back and also a little bit abs. But how can we measure the intensity? I define intensity as a value between 0 and 1. 0 means it doesn’t train the specific muscle group at all. 1 means it totally trains the specific muscle group and you don’t need to train this muscle group anymore in the current circle. So I collected a bunch of exercises and defined for each muscle group its intensity and saved it into a CSV-file as a table:

pull ups0.51010.50.5000
push ups00100.50.25000

I recommend that you define your own intensities for each exercise. Currently, these intensity values are a little bit relative, if you know a better measurement unit please let me know.

So now we can choose for every circle training how much we want to train at least the specific muscle group. The only thing which is left for our LP-solver is to define these intensities as constraints so that the LP-solver takes the intensities into account. For that reason we create for each muscle group a constraint:

\( \sum_{i=1}^{n} y_i \cdot x_{i} \ge intensity_{shoulders}\)

\(y_i \in [0,1]\) is the intensity for exercise \(x_i\).


To solve LP-problems you can use different kinds of LP-solvers. A very nice Open Source LP-solver is PULP. I personally prefer the Gurobi LP-solver, it is a very powerful solver that has got a huge variety of features. If you are in academic you can receive an academic license for free. You can find the full code in my GitHub-Repository.

if __name__ == "__main__":
    # Define your intensities manually
    categories, min_intensity, max_intensity = gp.multidict({
        'shoulders' : [0,GRB.INFINITY],
        'back' : [0,GRB.INFINITY],
        'breast' : [0,GRB.INFINITY],
        'biceps' : [0,GRB.INFINITY],
        'triceps' : [0,GRB.INFINITY],
        'abs' : [0,GRB.INFINITY],
        'butt' : [0.5,0.5],
        'legs' : [2,2]

    # Read exercises and their intensity
    exercises_intensities = build_dict_from_csv_file("exercises.csv", "name")
    # Read exercises and their needed time
    exercises, time = gp.multidict(exercise_and_time_dict(exercises_intensities))
    # Build model
    m = gp.Model("circle_training")
    # Create trainings variables (each exercise is a decision variable)
    training = m.addVars(exercises, vtype=GRB.BINARY, name="training")
    # Objective Function
    m.setObjective(, GRB.MINIMIZE)
    # Constraint:
    # shoulders * push ups + shoulders * biceps + ...
    # others * push ups + ....
    m.addConstrs((gp.quicksum(exercises_intensities[e, c] * training[e] for e in exercises)
        == [min_intensity[c], max_intensity[c]]
        for c in categories), "_"
    # Find Solution
    # if there is a solution
    if m.status == GRB.OPTIMAL:
        print("Your training plan:")
        print('\nTime: %g' % m.objVal)
        trainingx = m.getAttr('x', training)
        for f in exercises:
            if trainingx[f] > 0:
                print('%s %g' % (f, trainingx[f]))
        print('No solution')


If you want to learn more: