I am organizing a tournament with over 200 teams divided into 40+ groups. Each group will play for four rounds. The teams are placed into groups of 6 based on their age and strength. The final goal is to minimize the overall driving distance for the teams.
@Laurent Perron suggested me to look at the problem using only boolean values and I manage to get a little further. Check here
My code now places teams into groups where they are allowed. The teams stays in the same group and play another team in the group in every round.
My next problem is to ensure a team never plays another team twice. I have a feeling there should be a quite simple solution, but I cannot see it.
Any suggestions of how to add this constraint?
My print solution looks like this. (here only shown for Group 1 in Round 1 and 2)
In Group 1 Round 1 (Pos 0-1) Team 6 vs. 3
In Group 1 Round 1 (Pos 2-3) Team 2 vs. 8
In Group 1 Round 1 (Pos 4-5) Team 5 vs. 1
In Group 1 Round 2 (Pos 0-1) Team 6 vs. 3
In Group 1 Round 2 (Pos 2-3) Team 2 vs. 8
In Group 1 Round 2 (Pos 4-5) Team 5 vs. 1
And I need Team 6 vs 3 (including 3 vs 6) only can occure one time
from ortools.sat.python import cp_model
def main():
#GroupTypeList = [[0,1,2,4,5,6,7], # 3 different groups, Team 0,1,2 ... can be placed in Group 0
# [5,6,7,8,9,10,11,12], # Team 5,6...,11,12 can be placed in Group 1
# [3,10,11,12,13,14,15,16,17,0]]
#GroupTypeList in Boolan version
GroupTypeListBool = [[True,True,True,False,True,True,True,True,False,False,False,False,False,False,False,False,False,False],
[False,False,False,False,False,True,True,True,True,True,True,True,True,False,False,False,False,False],
[False,False,False,True,False,False,False,False,False,False,True,True,True,True,True,True,True,True]]
num_teams = 18 #18 teams
Groups = 3 # 3 different Groups
GroupSize = 6 # 6 teams in each group
Rounds = 4 # 4 Round tournement
# Model
model = cp_model.CpModel()
# Variables
Teams = {}
for r in range(Rounds):
for g in range(Groups):
for s in range(GroupSize):
for t in range(num_teams+1): #+1 as the first team is [0]
Teams[r,g,s,t] = model.NewBoolVar(f'x[{r},{g},{s},{t}')
#Constraints
# Ensure only 1 team can be in the "boolean" of teams
for r in range(Rounds):
for g in range(Groups):
for s in range(GroupSize):
model.AddExactlyOne(Teams[r,g,s,t] for t in range(num_teams))
# Ensure Teams are in correct groups
for r in range(Rounds):
for g in range(Groups):
for s in range(GroupSize):
for t in range(num_teams):
if GroupTypeListBool[g][t] == False:
model.Add(Teams[r,g,s,t] == 0)
#Ensure teams stays in same group as in Round 1
for r in range(1,Rounds):
for g in range(Groups):
for s in range(GroupSize):
for t in range(num_teams):
model.Add(Teams[r,g,s,t] == 1).OnlyEnforceIf(Teams[0,g,s,t])
#Ensure all team plays 4 times in 4 rounds
for t in range(num_teams):
model.Add(sum(Teams[r,g,s,t] for g in range(Groups) for s in range(GroupSize) for r in range(Rounds)) == Rounds)
# Solve
solver = cp_model.CpSolver()
status = solver.Solve(model)
# Print solution.
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
print(f'Total cost = {solver.ObjectiveValue()}')
for g in range(Groups):
for r in range(Rounds):
print()
for s in range(GroupSize):
for t in range(num_teams):
if solver.BooleanValue(Teams[r,g,s,t]):
if (s% 2)==0:
print(f'In Group {g+1} Round {r+1} (Pos {s}-{s+1}) Team {t+1} vs. ', end="")
else:
print(f'{t+1}')
else:
print('No solution found.')
if __name__ == '__main__':
main()