Using Linear Programming to strategize for a climbing comp

I recently competed in a endurance climbing comp at my local gym - “Sufferfest”. The premise is simple, climb as many routes as you can in a 6 hour period, where each climb is awarded points based on it’s grade. With harder climbs giving more points. The winning team climbed an astonishing 142 routes with routes up to 5.12d totaling 130,850 points. While we only managed 67 routes at 48,450 points. Which still managed us 9th place overall out of 33 teams.

Strategy was a common topic before and during the competition. Some teams opted to climb harder routes for more points while others prioritized volume. While harder climbs do reward more points, they come at the cost of more effort expended. Based on the final standings, it’s clear that the teams that went for more volume ended up scoring more points. Consider the examples of 5.10c and 5.11d, a 5.10c climb rewards 500 points and 5.11d is 1000 points. Therefore 2 5.10cs are worth roughly a single 5.11d. If we attempted to assign an effort to grade ratio we could theoretically come up with the perfect strategy to maximize points. That’s what I’ve attempted to do in the below Linear Programming problem.

The concept of Linear Programming is simple, using a python package called Pulp we can convert problems like the above into linear or mixed integer programming models.

First we’ll create our problem

import pulp as p

Lp_prob = p.LpProblem('Climbing-comp', p.LpMaximize)

Next we need to create our decision variables using the below charts. In this problem, the decision variables are how many climbs of each grade you should complete. The points per climb chart is provided by the comp, but the climbs_hour chart is somewhat subjective to the individual and climb. Based on our efforts I’ve estimated what I believe is reasonable. Although even making minor adjustments to these values can sway the results of the model quite a bit.

# Points awarded for each climb grade.
climbs = {
    '5.10a': 300,
    '5.10b': 400,
    '5.10c': 500,
    '5.10d': 600,
    '5.11a': 750,
    '5.11b': 850,
    '5.11c': 950,
    '5.11d': 1000,
    '5.12a': 1250,
    '5.12b': 1350,
    '5.12c': 1450,
    '5.12d': 1550,
}

# Number of climbs of each grade that can be completed per hour.
climbs_hour = {
    '5.10a': 18,
    '5.10b': 15,
    '5.10c': 14,
    '5.10d': 12,
    '5.11a': 8,
    '5.11b': 6,
    '5.11c': 5,
    '5.11d': 3,
    '5.12a': 3,
    '5.12b': 2,
    '5.12c': 1,
    '5.12d': 1,
}

# Decision variables: number of climbs completed at each grade.
climb_count = p.LpVariable.dicts(
    'climb_count', climbs.keys(), lowBound=0, cat=p.LpInteger
)

Next we’ll create the objective function. I.e. what our goal is. For this problem, we’ll want to maximize the points obtained.

# Objective function: maximize total points.
Lp_prob += p.lpSum(climbs[grade] * climb_count[grade] for grade in climbs)

Without adding any constraints, the model will simply tell us to climb an infinite number of 5.12ds for infinite points. Let’s add in our constraints to help refine our model.

The last two constraints are again somewhat subjective. Capping it at 65 climbs is to help keep it reasonable. Another rule in the comp is that for each specific climb in the gym, you may only count it up to 3 times. So capping the total number of climbs at x grade at 30 is to simulate there only being but so many climbs of a certain grade in the gym.

# Constraint: Each climb takes 1 / climbs_hour[grade] hours.
total_hours = p.lpSum(
    climb_count[grade] / climbs_hour[grade] for grade in climbs
)

# Constraint: complete all climbs within 6 hours.
Lp_prob += total_hours <= 6, 'Time_limit'

# Constraint: complete no more than 65 climbs in total.
Lp_prob += p.lpSum(climb_count.values()) <= 65, 'Climb_limit'

# Constraint: complete no more than x climbs of each grade.
for grade, count in climb_count.items():
    Lp_prob += count <= 30, f'Grade_limit_{grade}'

Let’s solve it!

# Solve problem
Lp_prob.solve(p.PULP_CBC_CMD(msg=False))

Results:

5.10c: 18 climbs
5.10d: 28 climbs
5.11a: 19 climbs
Total points: 40,050
Total hours: 5.99
Total climbs: 65.00

Unsurprisingly the optimal strategy given the inputs and constraints is volume at the upper 5.10 range. If we adopted this strategy in the competition, we would have totaled 80,100 points getting us 4th overall (2nd in our category).

As I said before, the results can vary depending on the adjustments made to the climbs_hour chart above. But I think the overall takeaway is that climbing volume at a grade you can safely repeat is the best strategy.