Multi-Objective Optimization using Gurobi Optimization Solver: Selecting the optimal playing eleven of a cricket team

Background

Multi-objective optimization (aka Pareto Optimization) involves optimizing more than one objective function simultaneously. The playing eleven for a cricket team requires selecting the best from an available pool of fifteen to nineteen players that might be a part of the squad. In the era of T20 cricket specialist batsmen, specialist bowlers and a wicket keeper are invariably augmented by bowling and/or batting all rounders. A playing eleven is selected considering the pitch, the length of boundaries in various directions, the time of the day the game is played, presence of dew for a night game, composition of the batting lineup of the opposition, number of left and right hander batsmen in the opposition etc. The selected team requires a certain number of batsmen, between five to seven bowlers and a specialist wicket keeper to form the right balance. Selecting the playing eleven for a game is an assignment problem in operations research. The assignment problem is a special case of a transportation problem. The assignment problem is also a fundamental combinatorial optimization problem in the field of Operations Research.

This article demonstrates the use of Binary Integer Linear Programming (BILP) for selecting the playing eleven of a cricket team. However, given that selecting a playing eleven for a cricket team requires several considerations, a multi-objective optimization approach is taken to balance out all roles that the playing eleven of a cricket team are required to fulfill given a defined, limited but sufficiently large squad.

Gurobi Optimization provides a powerful optimization solver for decision intelligence. The assignment problem being tackled is small enough and would allow the use of the free non-production software provided by Gurobi on their website. Any other optimization solver that supports multi-optimization would have worked. However, I have been impressed by this article that discusses and presents a video about how the NFL utilizes Gurobi Optimization to solve one of the most complex and scrutinized scheduling problems in existence.

Problem Statement

This was the squad sent by the BCCI to play a five match T20I series in Zimbabwe from July 6 to July 14, 2024. Let's say the entire squad was available for selection and we are to objectively pick the best possible squad. The coach and captain have decided that there would be four specialist batsmen, one wicket keeper batsman, two all rounders, three fast bowlers and a spinner. This then becomes an assignment problem wherein we are required to fill the eleven slots with individuals performing certain role from the available pool of nineteen players available for selection.

In order to keep this article short, I will now describe the solution with code snippets and a brief explanation where necessary. All statistics shown are taken from this site and are current as of the end of the final match of the India tour of Zimbabwe 2024. Since a few players have played little to no T20I matches the statistics taken are for T20 games which also includes the T20I match data.

Solution

import gurobipy as gp
from gurobipy import *        

Define Players Available for Selection and their Roles

player, role = multidict({'Shubman Gill' : ['Batsman/Captain'],
                          'Ruturaj Gaikwad' : ['Batsman'],
                          'Yashaswi Jaiswal' : ['Batsman'],
                          'Riyan Parag' : ['Batsman'],
                          'Rinku Singh' : ['Batsman'],
                          'Sai Sudarshan' : ['Batsman'],
                          'Shivam Dube' : ['All-rounder'],
                          'Abhishek Sharma' : ['All-rounder'],
                          'Washington Sundar' : ['All-rounder'],
                          'Nitish Reddy' : ['All-rounder'],
                          'Sanju Samson': ['Wicket Keeper / Batsman'],
                          'Dhruv Jurel': ['Wicket Keeper / Batsman'],
                          'Jitesh Sharma': ['Wicket Keeper / Batsman'],
                          'Ravi Bishnoi' : ['Bowler'],
                          'Avesh Khan' : ['Bowler'],
                          'Mukesh Kumar' : ['Bowler'],
                          'Tushar Deshpande' : ['Bowler'],
                          'Harshit Rana' : ['Bowler'],
                          'Khaleel Ahmed' : ['Bowler']
                         })        

Define Batsmen Selection Criteria

Strike rate, ability to hit fours and sixes are very important traits of a batsman in T20 cricket but the batsman also needs to show the ability to stay on the crease to put his hitting ability on display.

batsmen, average, strikeRate, foursPerBall, sixesPerBall = 

multidict({ 'Shubman Gill': [36.65, 136.71, 0.1334, 0.0441], 
                 'Ruturaj Gaikwad': [40.26, 139.65, 0.1343, 0.0526], 
                 'Yashaswi Jaiswal': [31.84, 149.61, 0.1802, 0.0635], 
                 'Riyan Parag': [32.59, 142.85, 0.0952, 0.0828], 
                 'Rinku Singh': [34.77, 148.68, 0.1169, 0.0724], 
                 'Sai Sudarshan': [40.62, 130.69, 0.1209, 0.0304]})        

Define Bowler Selection Criteria

A bowler must display the ability to take wickets with the least number of balls bowled and the least number of runs scored but should give away minimal runs in his spell.

bowler, bowler_bowlingAverage, bowler_bowlingEconomy, bowler_bowlingStrikeRate = multidict({ 'Khaleel Ahmed': [24.47, 8.36, 17.5], 
                     'Tushar Deshpande': [21.39, 8.54, 15.0], 
                     'Mukesh Kumar': [27.43, 8.86, 18.5], 
                     'Avesh Khan': [24.72, 8.46, 17.5], 
                     'Harshit Rana' : [23.64, 8.94, 15.8], 
                     'Ravi Bishnoi' : [22.98, 7.26, 18.9]})        

Define All-Rounder Selection Criteria

An all rounder needs to excel in both batting and bowling. In reality, the all rounder might have bowling as his strong forte with good skill at batting or vice-versa. An all rounder who excels at both batting and bowling is relatively rare.

allrounder, all_average, all_strikeRate, all_foursPerBall, all_sixesPerBall, all_bowlingAverage, all_bowlingEconomy, all_bowlingStrikeRate = 
multidict({ 'Shivam Dube': [30.09, 141.52, 0.0860, 0.0860, 31.02, 8.82, 21.0], 
                                                                                                                                             'Abhishek Sharma': [30.05, 154.41, 0.1373, 0.0867, 28.32, 7.29, 23.2], 
                                                                                                                                                        'Washington Sundar': [19.03, 118.82, 0.0951, 0.0365, 29.4, 6.98, 25.2], 
                                                                                                                                                        'Nitish Reddy': [30.38, 128.24, 0.0682, 0.0779, 78.33, 11.65, 40.3]})        

Define Keeper Selection Criteria

A keeper cannot keep his position in the team unless he displays the ability to bat and score runs similar to a specialist batsmen. At the same time, taking catches and initiating stumpings that are on offer is equally important. Dropped catches and missed stumpings would have been another useful statistic to consider but that data was not readily available and hence was not used.

keeper, keeper_average, keeper_strikeRate, keeper_foursPerBall, keeper_sixesPerBall, keeper_catchesPerMatch, keeper_stumpingsPerMatch = 
multidict({ 'Sanju Samson': [29.39, 134.68, 0.1095, 0.0599, 0.4964, 0.1087], 
                                                                                                                                                 'Dhruv Jurel': [21.19, 133.63, 0.0991, 0.0631, 0.5500, 0.0500], 
                                                                                                                                                   'Jitesh Sharma': [26.21, 147.59, 0.1381, 0.0694, 0.6333, 0.1333]})        

Define the model and its variables

# Model
model = gp.Model("cricketTeamBatsmen")
# Maximize the objective
model.ModelSense = GRB.MAXIMIZE

# Define a binary variable for each batsmen 
bat_shubman = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Shubman Gill")
bat_ruturaj = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Ruturaj Gaikwad")
bat_yashaswi = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Yashaswi Jaiswal")
bat_riyan = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Riyan Parag")
bat_rinku = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Rinku Singh")
bat_sudarshan = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Sai Sudarshan")

# Define a binary variable for each all-rounder 
all_shivam = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Shivam Dube")
all_abhishek = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Abhishek Sharma")
all_washington = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Washington Sundar")
all_nitish = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Nitish Reddy")

# Define a binary variable for each wicket keeper / batsman
keeper_sanju = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Sanju Samson")
keeper_dhruv = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Dhruv Jurel")
keeper_jitesh = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Jitesh Sharma")

# Define a binary variable for each bowler
bowler_khaleel = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Khaleel Ahmed")
bowler_mukesh = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Mukesh Kumar")
bowler_avesh = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Avesh Khan")
bowler_tushar = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Tushar Deshpande")
bowler_harshit = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Harshit Rana")
bowler_ravi = model.addVar(vtype=GRB.BINARY, obj=0.0, name="Ravi Bishnoi")        

Define the model constraints

# Define the constraints

# Only 5 batsmen play
model.addConstr(bat_shubman + bat_ruturaj + bat_yashaswi + bat_riyan + bat_rinku + bat_sudarshan == 4)

# Team can have only 11 players
model.addConstr(bat_shubman + bat_ruturaj + bat_yashaswi + bat_riyan + bat_rinku + bat_sudarshan + bowler_ravi + all_shivam + all_abhishek + all_washington + all_nitish + keeper_sanju + keeper_dhruv + keeper_jitesh + bowler_khaleel + bowler_mukesh + bowler_avesh + bowler_tushar + bowler_harshit == 11)

# Shubman Gill is the captain and has to play
model.addConstr(bat_shubman == 1)

# Ravi Bishnoi is the only specialist spinner and has to play
model.addConstr(bowler_ravi == 1)

# Only one keeper plays
model.addConstr(keeper_sanju + keeper_dhruv + keeper_jitesh == 1)

# Only two all-rounders play
model.addConstr(all_shivam + all_abhishek + all_washington + all_nitish == 2)

# Only three fast bowlers can play
model.addConstr(bowler_khaleel + bowler_mukesh + bowler_avesh + bowler_tushar + bowler_harshit == 3)        

Set the model objectives

# Set Objective functions

# Batsmen
# Sum of strike rate - a better batting strike rate assures a larger score
model.setObjectiveN((strikeRate['Shubman Gill'] + strikeRate['Ruturaj Gaikwad'] + strikeRate['Yashaswi Jaiswal'] + strikeRate['Riyan Parag'] + strikeRate['Rinku Singh'] + strikeRate['Sai Sudarshan']), index=0, priority=4, weight=0.4, name="strikeRate")
# Sum of average - a better batting average indicates the ability to stay on the crease and thereby ensure a larger score
model.setObjectiveN((average['Shubman Gill'] + average['Ruturaj Gaikwad'] + average['Yashaswi Jaiswal'] + average['Riyan Parag'] + average['Rinku Singh'] + average['Sai Sudarshan']), index=1, priority=3, weight=0.3, name="average")
# Sum of fours per ball - larger the number of fours per ball the better the prospects of a large score and better runs per over target
model.setObjectiveN((foursPerBall['Shubman Gill'] + foursPerBall['Ruturaj Gaikwad'] + foursPerBall['Yashaswi Jaiswal'] + foursPerBall['Riyan Parag'] + foursPerBall['Rinku Singh'] + foursPerBall['Sai Sudarshan']), index=2, priority=2, weight=0.15, name="foursPerBall")
# Sum of sixes per ball - larger the number of sixes per ball the better the prospects of a large score and better runs per over target
model.setObjectiveN((sixesPerBall['Shubman Gill'] + sixesPerBall['Ruturaj Gaikwad'] + sixesPerBall['Yashaswi Jaiswal'] + sixesPerBall['Riyan Parag'] + sixesPerBall['Rinku Singh'] + sixesPerBall['Sai Sudarshan']), index=3, priority=1, weight=0.15, name="sixesPerBall")

# All-rounders
# Sum of strike rate - a better strike rate assures a larger score
model.setObjectiveN((all_strikeRate['Shivam Dube'] + all_strikeRate['Abhishek Sharma'] + all_strikeRate['Washington Sundar'] + all_strikeRate['Nitish Reddy']), index=4, priority=11, weight=0.4, name="all_strikeRate")
# Sum of average - a better average indicates the ability to stay on the crease and thereby ensure a larger score
model.setObjectiveN((all_average['Shivam Dube'] + all_average['Abhishek Sharma'] + all_average['Washington Sundar'] + all_average['Nitish Reddy']), index=5, priority=10, weight=0.3, name="all_average")
# Sum of fours per ball - larger the number of fours per ball the better the prospects of a large score and better runs per over target
model.setObjectiveN((all_foursPerBall['Shivam Dube'] + all_foursPerBall['Abhishek Sharma'] + all_foursPerBall['Washington Sundar'] + all_foursPerBall['Nitish Reddy']), index=6, priority=9, weight=0.15, name="all_foursPerBall")
# Sum of sixes per ball - larger the number of sixes per ball the better the prospects of a large score and better runs per over target
model.setObjectiveN((all_sixesPerBall['Shivam Dube'] + all_sixesPerBall['Abhishek Sharma'] + all_sixesPerBall['Washington Sundar'] + all_sixesPerBall['Nitish Reddy']), index=7, priority=8, weight=0.15, name="all_sixesPerBall")
# Sum of the inverse of bowling strike rate. A bowler needs to demonstrate a lower bowling strike rate 
model.setObjectiveN((1/all_bowlingStrikeRate['Shivam Dube'] + 1/all_bowlingStrikeRate['Abhishek Sharma'] + 1/all_bowlingStrikeRate['Washington Sundar'] + 1/all_bowlingStrikeRate['Nitish Reddy']), index=9, priority=7, weight=0.35, name="all_bowlingStrikeRate") 
# Sum of the inverse of bowling average. A bowler needs to demonstrate a lower bowling average
model.setObjectiveN((1/all_bowlingAverage['Shivam Dube'] + 1/all_bowlingAverage['Abhishek Sharma'] + 1/all_bowlingAverage['Washington Sundar'] + 1/all_bowlingAverage['Nitish Reddy']), index=8, priority=5, weight=0.25, name="all_bowlingAverage")   
# Sum of the inverse of bowling economy. A bowler needs to demonstrate a lower bowling economy
model.setObjectiveN((1/all_bowlingEconomy['Shivam Dube'] + 1/all_bowlingEconomy['Abhishek Sharma'] + 1/all_bowlingEconomy['Washington Sundar'] + 1/all_bowlingEconomy['Nitish Reddy']), index=10, priority=6, weight=0.4, name="all_bowlingEconomy")                     

# Bowlers
# Sum of the inverse of bowling strike rate. A bowler needs to demonstrate a lower bowling strike rate 
model.setObjectiveN((1/bowler_bowlingStrikeRate['Khaleel Ahmed'] + 1/bowler_bowlingStrikeRate['Mukesh Kumar'] + 1/bowler_bowlingStrikeRate['Avesh Khan'] + 1/bowler_bowlingStrikeRate['Ravi Bishnoi'] + 1/bowler_bowlingStrikeRate['Tushar Deshpande'] + 1/bowler_bowlingStrikeRate['Harshit Rana']), index=11, priority=14, weight=0.35, name="bowler_bowlingStrikeRate") 
# Sum of the inverse of bowling average. A bowler needs to demonstrate a lower bowling average
model.setObjectiveN((1/bowler_bowlingAverage['Khaleel Ahmed'] + 1/bowler_bowlingAverage['Mukesh Kumar'] + 1/bowler_bowlingAverage['Avesh Khan'] + 1/bowler_bowlingAverage['Ravi Bishnoi'] + 1/bowler_bowlingAverage['Tushar Deshpande'] + 1/bowler_bowlingAverage['Harshit Rana']), index=12, priority=12, weight=0.25, name="bowler_bowlingAverage")   
# Sum of the inverse of bowling economy. A bowler needs to demonstrate a lower bowling economy
model.setObjectiveN((1/bowler_bowlingEconomy['Khaleel Ahmed'] + 1/bowler_bowlingEconomy['Mukesh Kumar'] + 1/bowler_bowlingEconomy['Avesh Khan'] + 1/bowler_bowlingEconomy['Ravi Bishnoi'] + 1/bowler_bowlingEconomy['Tushar Deshpande'] + 1/bowler_bowlingEconomy['Harshit Rana']), index=13, priority=13, weight=0.4, name="bowler_bowlingEconomy")                     

# Keepers
# Sum of strike rate - a better batting strike rate assures a larger score
model.setObjectiveN((keeper_strikeRate['Sanju Samson'] + keeper_strikeRate['Dhruv Jurel'] + keeper_strikeRate['Jitesh Sharma']), index=14, priority=18, weight=0.25, name="keeper_strikeRate")
# Sum of average - a better batting average indicates the ability to stay on the crease and thereby ensure a larger score
model.setObjectiveN((keeper_average['Sanju Samson'] + keeper_average['Dhruv Jurel'] + keeper_average['Jitesh Sharma']), index=15, priority=17, weight=0.15, name="keeper_average")
# Sum of fours per ball - larger the number of fours per ball the better the prospects of a large score and better runs per over target
model.setObjectiveN((keeper_foursPerBall['Sanju Samson'] + keeper_foursPerBall['Dhruv Jurel'] + keeper_foursPerBall['Jitesh Sharma']), index=16, priority=16, weight=0.15, name="keeper_foursPerBall")
# Sum of sixes per ball - larger the number of sixes per ball the better the prospects of a large score and better runs per over target
model.setObjectiveN((keeper_sixesPerBall['Sanju Samson'] + keeper_sixesPerBall['Dhruv Jurel'] + keeper_sixesPerBall['Jitesh Sharma']), index=17, priority=15, weight=0.15, name="keeper_sixesPerBall")
# Sum of catches per ball - larger the better
model.setObjectiveN((keeper_catchesPerMatch['Sanju Samson'] + keeper_catchesPerMatch['Dhruv Jurel'] + keeper_catchesPerMatch['Jitesh Sharma']), index=18, priority=20, weight=0.15, name="keeper_sixesPerBall")
# Sum of stumpings per ball - larger the better
model.setObjectiveN((keeper_stumpingsPerMatch['Sanju Samson'] + keeper_stumpingsPerMatch['Dhruv Jurel'] + keeper_stumpingsPerMatch['Jitesh Sharma']), index=19, priority=19, weight=0.15, name="keeper_sixesPerBall")

# Update the model
model.update()        

Print the solution

def printSolution():
    if model.status == GRB.OPTIMAL:
        print("Optimal solution found!")

        # Print objective value
        print("Objective value:", model.objVal)

        # Print decision variable values that are non-zero
        print("Playing eleven")
        for v in model.getVars():
            if v.x != 0.0:
                print("\t", end="")
                print(v.varName, " : ", role[v.varName])
    else:
        print("Optimal solution not found.")        

Find the optimal solution

# Find the optimal solution to the problem
model.optimize()
printSolution()        

Output

The goal of optimization is to find the best possible objective value for an optimal solution. The objective value is a number tied to the optimal solution quantifying what is being optimized.

We see the team has been selected as per the input criteria. The captain and the specialist spinner are irreplaceable, need to definitely play and are a part of the chosen team. There are three other specialist batsmen, two all rounders, one wicket keeper / batsman and three other bowlers. The team has four specialist bowlers and the responsibility of the fifth bowler will be performed by the two all rounders.

Optimal solution found!
Objective value: 848.19
Playing eleven
	Shubman Gill  :  Batsman/Captain
	Ruturaj Gaikwad  :  Batsman
	Yashaswi Jaiswal  :  Batsman
	Riyan Parag  :  Batsman
	Shivam Dube  :  All-rounder
	Abhishek Sharma  :  All-rounder
	Sanju Samson  :  Wicket Keeper / Batsman
	Khaleel Ahmed  :  Bowler
	Mukesh Kumar  :  Bowler
	Avesh Khan  :  Bowler
	Ravi Bishnoi  :  Bowler        

Callouts

It is possible that some may say team selection is a rather simple problem and does not require applying mathematical optimization. This may or may not be necessarily true if one considers this to be a tool to aid human decision making. The objectives in this article can be further enhanced by applying more complex constraints and adding more pertinent objectives. When the parameters, constraints and objectives are rather large human comprehension and discernment diminishes significantly. Others may say Rinku Singh deserves a place over Riyan Parag. This may be true but the data says otherwise. Opinions may differ but this article just demonstrates applying mathematical concepts to a team selection problem.

Another criticism could be that such mathematical team selection may deny certain players the opportunity to play thereby providing players who have had a good record to get more chances to play. This may be happening even without applying such an approach to team selection. Coaches and managers seldom change a winning team and alternate options are looked at only when the performance of the collective team drops or if certain players are horribly out of form and do not perform to their ability. Further, performance of players dips and rises, team composition needs change based on playing conditions or composition of the opposition team etc. There is always a possibility that some fringe players may end up getting a chance due to bad form of certain incumbent players. Teams and coaches are cognizant of players in the pool getting opportunities since some in form players may become unavailable for personal reasons or due to fitness concerns. Recent form and performance if considered in the analysis may result in more relevant team selection which needs to be an iterative exercise.

Finally, this must be considered a tool to justify/augment human decision making. It can be used to catch or override any perception flaws or inherent bias in favor or against a certain player.

Please send in your comments, suggestions and reactions.



Dharmesh Sampat

Senior Leader - Technology, Product and Engineering | Value creation for platforms

3 个月

Nice!!

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了