I am trying to build a fantasy team where the goal is to maximize its total expected points based on expected points of individual players subject to certain constraints.
Here's the relevant part of my code:
players = [player for player in input_dict]
prices = {player: input_dict[player]['price'] for player in players}
player_teams = {player: input_dict[player]['team'] for player in players}
player_positions = {player: input_dict[player]['position'] for player in players}
points = {player: input_dict[player]['expected_points'] for player in players}
league_teams = list(set(team for team in player_teams.values()))
prob = LpProblem('Fantasy_team', LpMaximize)
player_var = LpVariable.dicts('Players', players, 0, 1, cat='Integer')
# maximize points
prob += lpSum([player_var[player] * points[player] for player in players])
# number of players to be selected
prob += lpSum([player_var[player] for player in players]) == 11
# max budget
prob += lpSum([player_var[player] * prices[player] for player in players]) <= 100
# position constraints
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'goalkeeper']) == 1
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'defender']) >= 2
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'defender']) <= 5
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'midfielder']) >= 2
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'midfielder']) <= 5
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'forward']) >= 0
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'forward']) <= 3
# max players from one team
for league_team in league_teams:
team_players = []
for player, lp_var in player_var.items():
if player_teams[player] == league_team:
team_players.append(lp_var)
prob += lpSum(team_players) <= 3
However, what if I want to add one additional constraint: minimum defenders (or any other position) from one team = 2. In other words, at least two defenders have to come from the same team.
It seems to me that another decision variable would be required but I do not understand how this should be done.
EDIT:
I made some progress but I am now stuck with this error. Apparently Pulp is not happy with max function. Is there any way to get around this?
positions = ['defender',]
team_hires = LpVariable.dicts('count', (positions, league_teams), cat='Integer')
for league_team in league_teams:
player_vars = []
for player in player_var:
if player_teams[player] == league_team:
player_vars.append(player_var[player])
team_hires['defender'].update({league_team: lpSum(player_vars)})
prob += max([team_hires['defender'][team] for team in league_teams]) >= 2
TypeError: '>' not supported between instances of 'LpAffineExpression' and 'LpAffineExpression'
I needed to re-post this answer as my previous one (deleted) is confusing and I think I led you astray...
In order to write a constraint that forces at least 1 team to support the minimum number of hires per position, you need to be able to iterate over the teams (so you need a set of teams) and you need to be able to iterate over the positions that have a minimum-hires constraint. The example below does that with a "multiple choice" constraint that is a binary variable which implies a source team is supporting the min hires for a particular position. So if a particular team is "selected" by the solver to support the min hires, that variable is "1" and then the number of hires from that team must be greater than the min requirement (that is the first part of the constraint). The second part is that you must force the sum of that variable over all teams to be at least 1 to ensure that at least one team is forced to support the requirement. That is the second part of the constraint.
Note the syntax on this could probably be a bit cleaner if you changed your decision variable to be triple-indexed [player, pos, source_team], but this still works fine!
player,price,team,position,expected_points
bob,24,wolves,defender,2.1
sam,23,wolves,defender,2.4
tommy,20,wolves,forward,3.4
bill,20,wolves,forward,3.6
eddie,22,wolves,forward,3.1
tim,23,bears,defender,2.2
earl,23,bears,defender,1.0
dorf,24,bears,forward,3.5
bennie,30,bears,forward,3.6
from pulp import *
import pandas as pd
df = pd.read_csv('data.csv')
df = df.set_index('player')
input_dict = df.to_dict('index')
players = [player for player in input_dict]
prices = {player: input_dict[player]['price'] for player in players}
player_teams = {player: input_dict[player]['team'] for player in players}
player_positions = {player: input_dict[player]['position'] for player in players}
points = {player: input_dict[player]['expected_points'] for player in players}
league_teams = list(set(team for team in player_teams.values()))
# min numbers per position
pos_mins = {'defender':2,
'forward':2}
# min numbers from single team by position
team_pos_mins = {'defender':2} # must hire at least 2 defenders from same source team
positions = player_positions.values()
pos_team_combos = {(pos, team) for pos in positions for team in league_teams}
prob = LpProblem('Fantasy_team', LpMaximize)
hire = LpVariable.dicts('Players', players, cat='Binary') # this is binary decision...
support_min_hires = LpVariable.dicts('team hires', pos_team_combos, cat='Binary') # 1 if that team supports the min for position
# maximize points
prob += lpSum([hire[player] * points[player] for player in players])
# number of players to be selected
prob += lpSum([hire[player] for player in players]) == 4
# max budget
prob += lpSum([hire[player] * prices[player] for player in players]) <= 100
# position constraints
for pos in pos_mins:
# this pattern could be replicated for all of your max/mins
prob += lpSum(hire[player] for player in players if player_positions[player] == pos) >= pos_mins[pos]
# hires by team constraint
for pos in team_pos_mins:
# the min number hired from team must be greater than the requirement, if that team is selected to support the min...
for team in league_teams:
prob += lpSum(hire[player] for player in players
if player_positions[player] == pos
and player_teams[player] == team) >= support_min_hires[pos, team] * team_pos_mins[pos]
# force at least one team to suppoprt the minimum hires
prob += lpSum(support_min_hires[pos,team] for team in league_teams) >= 1
#print(prob)
status = prob.solve()
print(status)
for p in players:
print(p, hire[p].value())
# for QA...
for pos in team_pos_mins:
for team in league_teams:
print(f'{team} covers min hires for {pos}: {support_min_hires[pos,team].value()>0}')
Result - Optimal solution found
Objective value: 11.70000000
Enumerated nodes: 0
Total iterations: 0
Time (CPU seconds): 0.00
Time (Wallclock seconds): 0.00
Option for printingOptions changed from normal to all
Total time (CPU seconds): 0.00 (Wallclock seconds): 0.01
1
bob 1.0
sam 1.0
tommy 0.0
bill 1.0
eddie 0.0
tim 0.0
earl 0.0
dorf 0.0
bennie 1.0
wolves covers min hires for defender: True
bears covers min hires for defender: False
[Finished in 0.9s]