From: Steinar H. Gunderson Date: Thu, 25 Oct 2018 15:43:45 +0000 (+0200) Subject: Check in some scripts based on OR-tools to try to generate good group schedules. X-Git-Url: https://git.sesse.net/?p=ultimatescore;a=commitdiff_plain;h=b2f520916cf152c9d2c868f9181369127b485a12 Check in some scripts based on OR-tools to try to generate good group schedules. --- diff --git a/roster/test.py b/roster/test.py new file mode 100644 index 0000000..dd90b43 --- /dev/null +++ b/roster/test.py @@ -0,0 +1,160 @@ +from __future__ import print_function +import sys +from ortools.constraint_solver import pywrapcp + +def print_solution(sol): + for i in range(num_matches): + home_team = collector.Value(sol, home_teams[i]) + away_team = collector.Value(sol, away_teams[i]) + matchnum = collector.Value(sol, matchnums[i]) + #print("%2d. %d vs. %d (matchnum %d)" % (i, home_team, away_team, matchnum)) + print("%2d. %d vs. %d (matchnum %2d, excitedness %d)" % (i, home_team, away_team, matchnum, excitedness[matchnum])) + +# Creates the solver. +solver = pywrapcp.Solver("schedule_shifts") + +num_teams = 6 +num_matches = (num_teams * (num_teams - 1)) // 2 + +# Create supermatch variables. +matchnums = [] +for match_idx in range(num_matches): + matchnums.append(solver.IntVar(0, num_matches - 1, "matchnum(%i)" % (match_idx))) +solver.Add(solver.AllDifferent(matchnums)); + +# Create list of matches. +match_num = 0 +excitedness = [] +home_teams_for_match_num = [] +away_teams_for_match_num = [] +for team_idx_1 in range(num_teams): + for team_idx_2 in range(num_teams): + if team_idx_2 > team_idx_1: + home_teams_for_match_num.append(team_idx_1) + away_teams_for_match_num.append(team_idx_2) + if team_idx_2 - team_idx_1 == 1: + excitedness.append(5) + elif team_idx_2 - team_idx_1 == 2: + excitedness.append(2) + else: + excitedness.append(0) + print("matchnum ", match_num , " plays: ", team_idx_1, " - ", team_idx_2, " excited: " , excitedness[match_num]) + match_num = match_num + 1 + +# Create match variables. +home_teams = [] +away_teams = [] +for match_idx in range(num_matches): + home_teams.append(solver.IntVar(0, num_teams - 1, "home_team_on_match(%i)" % (match_idx))) + away_teams.append(solver.IntVar(0, num_teams - 1, "away_team_on_match(%i)" % (match_idx))) +matches_flat = home_teams + away_teams + +for match_num in range(num_matches): + # home_teams_var[i] = home_teams[match_num_var[i]] + #solver.Add(matchnums[match_num].IndexOf(home_teams) == team_idx_1) # home_teams[matchnums[match_num]] == team_idx_1 + #solver.Add(matchnums[match_num].IndexOf(away_teams) == team_idx_2) + solver.Add(home_teams[match_num] == solver.Element(home_teams_for_match_num, matchnums[match_num])) + solver.Add(away_teams[match_num] == solver.Element(away_teams_for_match_num, matchnums[match_num])) + +## A team can never play two matches in a row +for match_idx in range(num_matches - 1): + solver.Add(home_teams[match_idx] != home_teams[match_idx + 1]) + solver.Add(away_teams[match_idx] != home_teams[match_idx + 1]) + solver.Add(home_teams[match_idx] != away_teams[match_idx + 1]) + solver.Add(away_teams[match_idx] != away_teams[match_idx + 1]) + +# More waiting time is good +tired_matches = [] +for match_idx in range(num_matches - 2): + home_tired = ( + (home_teams[match_idx] == home_teams[match_idx + 2]) + + (home_teams[match_idx] == away_teams[match_idx + 2])) + away_tired = ( + (away_teams[match_idx] == home_teams[match_idx + 2]) + + (away_teams[match_idx] == away_teams[match_idx + 2])) + tired_matches.append(home_tired) + tired_matches.append(away_tired) + + # double-tired is not cool + if match_idx < num_matches - 4: + home_doubletired = home_tired * ( + (home_teams[match_idx] == home_teams[match_idx + 4]) + + (home_teams[match_idx] == away_teams[match_idx + 4])) + away_doubletired = away_tired * ( + (away_teams[match_idx] == home_teams[match_idx + 4]) + + (away_teams[match_idx] == away_teams[match_idx + 4])) + tired_matches.append(home_doubletired * 100) + tired_matches.append(away_doubletired * 100) + +sum_tiredness = solver.Sum(tired_matches) + +## TFK can not play the first match +solver.Add(home_teams[0] != 0) +solver.Add(away_teams[0] != 0) +solver.Add(home_teams[1] != 0) +solver.Add(away_teams[1] != 0) + +# Group final comes last +solver.Add(home_teams[num_matches - 1] == 0) +solver.Add(away_teams[num_matches - 1] == 1) + +# Put the more excitedness last +sum_excitedness = solver.Sum([(matchnums[match_num].IndexOf(excitedness) * match_num) for match_num in range(num_matches)]) +objective = solver.Maximize(sum_excitedness - 10 * sum_tiredness, 1) + +# TODO: AllowedAssignments +# TODO: multiple fields/groups, objectives of getting TV time (esp. for interesting matches) +# TODO: objective on getting more interesting matches last + +db = solver.Phase(matchnums, solver.CHOOSE_FIRST_UNBOUND, + solver.ASSIGN_MIN_VALUE) +search_log = solver.SearchLog(1000000, objective) +#global_limit = solver.TimeLimit(1000) +global_limit = solver.TimeLimit(100000000) + +## Create the solution collector. +#solution = solver.Assignment() +#solution.Add(matches_flat) +#solution.Add(matchnums) +# +##collector = solver.AllSolutionCollector(solution) +##collector = solver.LastSolutionCollector(solution) +#collector = solver.FirstSolutionCollector(solution) +# +#solver.Solve(db, [collector, search_log, objective, global_limit]) +#print("Solutions found:", collector.SolutionCount()) +#print("Time:", solver.WallTime(), "ms") +#print() +## Display a few solutions picked at random. +#a_few_solutions = [0] +#for sol in a_few_solutions: +# print_solution(sol) +# +#os.exit(0) + +###All +solver.NewSearch(db, [search_log, objective, global_limit]) +num_solutions = 0 +while solver.NextSolution(): + #print("objective = ", sum_excitedness.Value()) + for i in range(num_matches): + home_team = home_teams[i].Value() + away_team = away_teams[i].Value() + matchnum = matchnums[i].Value() + + tiredness = 0 + if i >= 2: + if home_team == home_teams[i - 2].Value(): + tiredness = tiredness + 1 + if home_team == away_teams[i - 2].Value(): + tiredness = tiredness + 1 + if away_team == home_teams[i - 2].Value(): + tiredness = tiredness + 1 + if away_team == away_teams[i - 2].Value(): + tiredness = tiredness + 1 + + print("%2d. %d vs. %d (matchnum %2d, excitedness %d, tiredness %d)" % (i, home_team, away_team, matchnum, excitedness[matchnum], tiredness)) + + print() + num_solutions += 1 +solver.EndSearch() diff --git a/roster/twofields.py b/roster/twofields.py new file mode 100644 index 0000000..faeb266 --- /dev/null +++ b/roster/twofields.py @@ -0,0 +1,169 @@ +from __future__ import print_function +import sys +from ortools.constraint_solver import pywrapcp + + +num_groups = 2 # NOTE: 2 is hard-coded in some places. +num_teams_per_group = 6 +num_teams = num_teams_per_group * num_teams_per_group +num_rounds = (num_teams_per_group * (num_teams_per_group - 1)) // 2 +num_matches = num_rounds * num_groups + +def excitedness_weight(match_idx): + field = match_idx % num_groups + match_order = match_idx // num_groups + if field == 0: + return match_order + 5 + else: + return match_order + +def team_name(team_idx): + if team_idx < num_teams_per_group: + return "A%d" % (team_idx) + else: + return "B%d" % (team_idx - num_teams_per_group) + +solver = pywrapcp.Solver("schedule_games") + +# Create match variables. +matchnums = [] +for match_idx in range(num_matches): + matchnums.append(solver.IntVar(0, num_matches - 1, "matchnum(%i)" % (match_idx))) +solver.Add(solver.AllDifferent(matchnums)); + +# Create list of matches. +match_num = 0 +excitedness = [] +home_teams_for_match_num = [] +away_teams_for_match_num = [] +for group in range(num_groups): + for team_idx_1 in range(num_teams_per_group): + for team_idx_2 in range(num_teams_per_group): + if team_idx_2 > team_idx_1: + real_team_idx_1 = team_idx_1 + num_teams_per_group * group + real_team_idx_2 = team_idx_2 + num_teams_per_group * group + home_teams_for_match_num.append(real_team_idx_1) + away_teams_for_match_num.append(real_team_idx_2) + if team_idx_2 - team_idx_1 == 1: + excitedness.append(5) + elif team_idx_2 - team_idx_1 == 2: + excitedness.append(2) + else: + excitedness.append(0) + print("matchnum %2d: %2d vs. %2d, excited: %d" % (match_num, real_team_idx_1, real_team_idx_2, excitedness[match_num])) + match_num = match_num + 1 + +# Create match variables. +home_teams = [] +away_teams = [] +for match_idx in range(num_matches): + home_teams.append(solver.IntVar(0, num_teams - 1, "home_team_on_match(%i)" % (match_idx))) + away_teams.append(solver.IntVar(0, num_teams - 1, "away_team_on_match(%i)" % (match_idx))) +matches_flat = home_teams + away_teams + +for match_num in range(num_matches): + solver.Add(home_teams[match_num] == solver.Element(home_teams_for_match_num, matchnums[match_num])) + solver.Add(away_teams[match_num] == solver.Element(away_teams_for_match_num, matchnums[match_num])) + +# Fields always play opposing groups (FIXME?) +for round_idx in range(num_rounds): + solver.Add((matchnums[round_idx * 2 + 0] >= num_rounds) != (matchnums[round_idx * 2 + 1] >= num_rounds)) + +# A team can never play on the same field at the same time +#for match_idx in range(num_rounds): +# solver.Add(home_teams[match_idx * 2 + 0] != home_teams[match_idx * 2 + 1]) +# solver.Add(home_teams[match_idx * 2 + 0] != away_teams[match_idx * 2 + 1]) +# solver.Add(away_teams[match_idx * 2 + 0] != home_teams[match_idx * 2 + 1]) +# solver.Add(away_teams[match_idx * 2 + 0] != away_teams[match_idx * 2 + 1]) + +plays_in_round = {} +for team_idx in range(num_teams): + plays_in_round[team_idx] = {} + for round_idx in range(num_rounds): + plays_in_round[team_idx][round_idx] = ( + (home_teams[round_idx * 2 + 0] == team_idx) + + (home_teams[round_idx * 2 + 1] == team_idx) + + (away_teams[round_idx * 2 + 0] == team_idx) + + (away_teams[round_idx * 2 + 1] == team_idx)) + +# A team can never play two matches in a row +for round_idx in range(num_rounds - 1): + for team_idx in range(num_teams): + solver.Add(plays_in_round[team_idx][round_idx] + plays_in_round[team_idx][round_idx + 1] <= 1) + +# More waiting time is good +tired_matches = [] +for round_idx in range(num_rounds - 2): + for team_idx in range(num_teams): + tired = plays_in_round[team_idx][round_idx] + plays_in_round[team_idx][round_idx + 2] >= 2 + tired_matches.append(tired) +sum_tiredness = solver.Sum(tired_matches) + +# Double-tired is not cool +for round_idx in range(num_rounds - 4): + for team_idx in range(num_teams): + #doubletired = plays_in_round[team_idx][round_idx] + plays_in_round[team_idx][round_idx + 2] + plays_in_round[team_idx][round_idx + 4] >= 3 + #tired_matches.append(doubletired * 100) + solver.Add(plays_in_round[team_idx][round_idx] + plays_in_round[team_idx][round_idx + 2] + plays_in_round[team_idx][round_idx + 4] < 3) + + +## TFK can not play the first two matches +solver.Add(home_teams[0] != 0) +solver.Add(away_teams[0] != 0) +solver.Add(home_teams[1] != 0) +solver.Add(away_teams[1] != 0) +solver.Add(home_teams[2] != 0) +solver.Add(away_teams[2] != 0) +solver.Add(home_teams[3] != 0) +solver.Add(away_teams[3] != 0) + +# Group finals come last, and on the stream field. +solver.Add((matchnums[num_matches - 2] == 0) + (matchnums[num_matches - 2] == num_rounds) >= 1) +solver.Add((matchnums[num_matches - 4] == 0) + (matchnums[num_matches - 4] == num_rounds) >= 1) + +# Put the more exciting games later, and on stream fields +sum_excitedness = solver.Sum([(matchnums[match_num].IndexOf(excitedness) * excitedness_weight(match_num)) for match_num in range(num_matches)]) +objective = solver.Maximize(sum_excitedness - 10 * sum_tiredness, 1) + +db = solver.Phase(matchnums, solver.CHOOSE_FIRST_UNBOUND, + solver.ASSIGN_MIN_VALUE) +search_log = solver.SearchLog(1000000, objective) +#global_limit = solver.TimeLimit(1000) +global_limit = solver.TimeLimit(100000000) + +solver.NewSearch(db, [search_log, objective, global_limit]) +while solver.NextSolution(): + recently_played_0 = [False for team_idx in range(num_teams)] + recently_played_1 = [False for team_idx in range(num_teams)] + recently_played_2 = [False for team_idx in range(num_teams)] + recently_played_3 = [False for team_idx in range(num_teams)] + for i in range(num_matches // num_groups): + recently_played_4 = [False for team_idx in range(num_teams)] + print("%2d. " % (i), end='') + for g in range(num_groups): + j = i * num_groups + g + home_team = home_teams[j].Value() + away_team = away_teams[j].Value() + matchnum = matchnums[j].Value() + + tiredness = 0 + if recently_played_2[home_team]: + tiredness = tiredness + 1 + if recently_played_0[home_team]: + tiredness = tiredness + 100 + if recently_played_2[away_team]: + tiredness = tiredness + 1 + if recently_played_0[away_team]: + tiredness = tiredness + 100 + recently_played_4[home_team] = True + recently_played_4[away_team] = True + + print("%s vs. %s (matchnum %2d, excitedness %d*%2d, tiredness %3d) " % (team_name(home_team), team_name(away_team), matchnum, excitedness[matchnum], excitedness_weight(j), tiredness), end='') + print() + recently_played_0 = recently_played_1 + recently_played_1 = recently_played_2 + recently_played_2 = recently_played_3 + recently_played_3 = recently_played_4 + + print() +solver.EndSearch() diff --git a/roster/twofields_sat.py b/roster/twofields_sat.py new file mode 100644 index 0000000..e95a8ba --- /dev/null +++ b/roster/twofields_sat.py @@ -0,0 +1,275 @@ +from __future__ import print_function +import sys +import time +from ortools.sat.python import cp_model + + + +num_groups = 2 # NOTE: 2 is hard-coded in some places. +num_teams_per_group = 6 +num_teams = num_teams_per_group * num_groups +num_rounds = (num_teams_per_group * (num_teams_per_group - 1)) // 2 +num_matches = num_rounds * num_groups + +class SolutionPrinterWithObjective(cp_model.CpSolverSolutionCallback): + def __init__(self, home_teams, away_teams, matchnums, objective): + cp_model.CpSolverSolutionCallback.__init__(self) + self.__solution_count = 0 + self.__start_time = time.time() + self.__home_teams = home_teams + self.__away_teams = away_teams + self.__matchnums = matchnums + self.__objective = objective + + def OnSolutionCallback(self): + current_time = time.time() + self.__solution_count += 1 + print('Solution %i, time = %f s, objective = %d' % + (self.__solution_count, current_time - self.__start_time, self.Value(self.__objective))) + num_times_on_stream = [0 for team_idx in range(num_teams)] + num_times_tired = [0 for team_idx in range(num_teams)] + recently_played_0 = [False for team_idx in range(num_teams)] + recently_played_1 = [False for team_idx in range(num_teams)] + recently_played_2 = [False for team_idx in range(num_teams)] + recently_played_3 = [False for team_idx in range(num_teams)] + for i in range(num_matches // num_groups): + recently_played_4 = [False for team_idx in range(num_teams)] + print("%2d. " % (i), end='') + for g in range(num_groups): + j = i * num_groups + g + home_team = self.Value(self.__home_teams[j]) + away_team = self.Value(self.__away_teams[j]) + matchnum = self.Value(self.__matchnums[j]) + + if g == 0: + num_times_on_stream[home_team] = num_times_on_stream[home_team] + 1 + num_times_on_stream[away_team] = num_times_on_stream[away_team] + 1 + + tiredness = 0 + if recently_played_2[home_team]: + tiredness = tiredness + 1 + num_times_tired[home_team] = num_times_tired[home_team] + 1 + if recently_played_0[home_team]: + tiredness = tiredness + 100 + if recently_played_2[away_team]: + tiredness = tiredness + 1 + num_times_tired[away_team] = num_times_tired[away_team] + 1 + if recently_played_0[away_team]: + tiredness = tiredness + 100 + recently_played_4[home_team] = True + recently_played_4[away_team] = True + + print("%s vs. %s (matchnum %2d, excitedness %d*%2d, tiredness %3d) " % (team_name(home_team), team_name(away_team), matchnum, excitedness[matchnum], excitedness_weight(j), tiredness), end='') + print() + recently_played_0 = recently_played_1 + recently_played_1 = recently_played_2 + recently_played_2 = recently_played_3 + recently_played_3 = recently_played_4 + print() + print("Number of times on stream: ", end='') + print(", ".join(["%s %d" % (team_name(team_idx), num_times_on_stream[team_idx]) for team_idx in range(num_teams)])) + print("Number of times tired: ", end='') + print(", ".join(["%s %d" % (team_name(team_idx), num_times_tired[team_idx]) for team_idx in range(num_teams)])) + print("Stream opponents for non-top-teams:") + for team_idx in (2, 3, 4, 5, 8, 9, 10, 11): + opp = [] + for i in range(num_matches // num_groups): + home_team = self.Value(self.__home_teams[i * 2]) + away_team = self.Value(self.__away_teams[i * 2]) + mark = "" + if abs(home_team - away_team) == 1: + mark = "*" + if team_idx == home_team: + opp.append(team_name(away_team) + mark) + if team_idx == away_team: + opp.append(team_name(home_team) + mark) + print(" %s: %s" % (team_name(team_idx), ", ".join(sorted(opp)))) + + +def excitedness_weight(match_idx): + field = match_idx % num_groups + match_order = match_idx // num_groups + if field == 0: + return match_order + 5 + else: + return match_order + +def team_name(team_idx): + if team_idx < num_teams_per_group: + return "A%d" % (team_idx) + else: + return "B%d" % (team_idx - num_teams_per_group) + +model = cp_model.CpModel() + +# Create match variables. +matchnums = [] +for match_idx in range(num_matches): + matchnums.append(model.NewIntVar(0, num_matches - 1, "matchnum(%i)" % (match_idx))) +model.AddAllDifferent(matchnums) + +# Create list of matches. +match_idx = 0 +excitedness = [] +home_teams_for_match_num = [] +away_teams_for_match_num = [] +for group in range(num_groups): + for team_idx_1 in range(num_teams_per_group): + for team_idx_2 in range(num_teams_per_group): + if team_idx_2 > team_idx_1: + real_team_idx_1 = team_idx_1 + num_teams_per_group * group + real_team_idx_2 = team_idx_2 + num_teams_per_group * group + home_teams_for_match_num.append(real_team_idx_1) + away_teams_for_match_num.append(real_team_idx_2) + if team_idx_2 - team_idx_1 == 1: + excitedness.append(5) + elif team_idx_2 - team_idx_1 == 2: + excitedness.append(2) + else: + excitedness.append(0) + print("matchnum %2d: %2d vs. %2d, excited: %d" % (match_idx, real_team_idx_1, real_team_idx_2, excitedness[match_idx])) + match_idx = match_idx + 1 + +# Create match variables. +home_teams = [] +away_teams = [] +for match_idx in range(num_matches): + home_teams.append(model.NewIntVar(0, num_teams - 1, "home_team_match%i" % (match_idx))) + away_teams.append(model.NewIntVar(0, num_teams - 1, "away_team_match%i" % (match_idx))) +matches_flat = home_teams + away_teams + +for match_idx in range(num_matches): + model.AddElement(matchnums[match_idx], home_teams_for_match_num, home_teams[match_idx]) + model.AddElement(matchnums[match_idx], away_teams_for_match_num, away_teams[match_idx]) + +# Boolean variables +home_team_in_match_x_is_y = [[ + model.NewBoolVar('home_team_in_match_%d_is_%d' % (match_idx, team_idx)) for team_idx in range(num_teams) +] for match_idx in range(num_matches)] + +away_team_in_match_x_is_y = [[ + model.NewBoolVar('away_team_in_match_%d_is_%d' % (match_idx, team_idx)) for team_idx in range(num_teams) +] for match_idx in range(num_matches)] + +match_x_has_num_y = [[ + model.NewBoolVar('match_%d_has_number_%d' % (a, b)) for a in range(num_matches) +] for b in range(num_matches)] + +for match_idx in range(num_matches): + model.AddMapDomain(matchnums[match_idx], match_x_has_num_y[match_idx]) + model.AddMapDomain(home_teams[match_idx], home_team_in_match_x_is_y[match_idx]) + model.AddMapDomain(away_teams[match_idx], away_team_in_match_x_is_y[match_idx]) + +# Fields always play opposing groups (FIXME?) +for round_idx in range(num_rounds): + field_0_is_group_0 = model.NewBoolVar('field_0_round_%d_is_group_0' % (round_idx)) + model.AddMaxEquality(field_0_is_group_0, [match_x_has_num_y[round_idx * 2 + 0][match_idx] for match_idx in range(num_rounds)]) + field_1_is_group_0 = model.NewBoolVar('field_1_round_%d_is_group_0' % (round_idx)) + model.AddMaxEquality(field_1_is_group_0, [match_x_has_num_y[round_idx * 2 + 1][match_idx] for match_idx in range(num_rounds)]) + model.AddBoolXOr([field_0_is_group_0, field_1_is_group_0]) + +# A team can never play on the same field at the same time +#for team_idx in range(num_teams): +# for round_idx in range(num_rounds): +# plays_on_field_0 = model.NewBoolVar('plays_on_field0_t%d_r%d' % (team_idx, round_idx)) +# model.AddMaxEquality(plays_on_field_0, [ +# home_team_in_match_x_is_y[round_idx * 2 + 0][team_idx], +# away_team_in_match_x_is_y[round_idx * 2 + 0][team_idx]]) +# plays_on_field_1 = model.NewBoolVar('plays_on_field1_t%d_r%d' % (team_idx, round_idx)) +# model.AddMaxEquality(plays_on_field_1, [ +# home_team_in_match_x_is_y[round_idx * 2 + 1][team_idx], +# away_team_in_match_x_is_y[round_idx * 2 + 1][team_idx]]) +# model.AddBoolOr([plays_on_field_0.Not(), plays_on_field_1.Not()]) + +plays_in_round = {} +for team_idx in range(num_teams): + plays_in_round[team_idx] = {} + for round_idx in range(num_rounds): + plays_in_round[team_idx][round_idx] = model.NewBoolVar('plays_in_round_t%d_r%d' % (team_idx, round_idx)) + model.AddMaxEquality(plays_in_round[team_idx][round_idx], [ + home_team_in_match_x_is_y[round_idx * 2 + 0][team_idx], + home_team_in_match_x_is_y[round_idx * 2 + 1][team_idx], + away_team_in_match_x_is_y[round_idx * 2 + 0][team_idx], + away_team_in_match_x_is_y[round_idx * 2 + 1][team_idx]]) + +# A team can never play two matches in a row +for round_idx in range(num_rounds - 1): + for team_idx in range(num_teams): + model.AddBoolOr([plays_in_round[team_idx][round_idx].Not(), plays_in_round[team_idx][round_idx + 1].Not()]) + +# Also, double-tired is not cool +#for round_idx in range(num_rounds - 4): +# for team_idx in range(num_teams): +# model.AddBoolOr([plays_in_round[team_idx][round_idx].Not(), plays_in_round[team_idx][round_idx + 2].Not(), plays_in_round[team_idx][round_idx + 4].Not()]) +# +# More waiting time is good +#tired_matches = [] +#for round_idx in range(num_rounds - 2): +# for team_idx in range(num_teams): +# tired = model.NewBoolVar('team_%d_is_tired_in_round_%d' % (team_idx, round_idx)) +# model.AddMinEquality(tired, [plays_in_round[team_idx][round_idx], plays_in_round[team_idx][round_idx + 2]]) +# tired_matches.append(tired) +#sum_tiredness = sum(tired_matches) + +# Each team gets play-rest-play exactly once, for fairness +for team_idx in range(num_teams): + tired_matches = [] + for round_idx in range(num_rounds - 2): + tired = model.NewBoolVar('team_%d_is_tired_in_round_%d' % (team_idx, round_idx)) + model.AddMinEquality(tired, [plays_in_round[team_idx][round_idx], plays_in_round[team_idx][round_idx + 2]]) + tired_matches.append(tired) + model.Add(sum(tired_matches) <= 1) +sum_tiredness = 0 + +# TFK can not play the first two matches +model.AddBoolAnd([plays_in_round[0][0].Not(), plays_in_round[0][1].Not()]) + +# Group finals come last, and on the stream field. +model.AddBoolOr([match_x_has_num_y[num_matches - 2][0], match_x_has_num_y[num_matches - 2][num_rounds]]) +model.AddBoolOr([match_x_has_num_y[num_matches - 4][0], match_x_has_num_y[num_matches - 4][num_rounds]]) + +# Count how many times each team has been on stream. +stream_penalties = [] +for team_idx in range(num_teams): + playing_on_stream = [] + for round_idx in range(num_rounds): + s = model.NewBoolVar('team_%d_plays_on_stream_in_round_%d' % (team_idx, round_idx)) + model.AddMaxEquality(s, [ + home_team_in_match_x_is_y[round_idx * 2 + 0][team_idx], + away_team_in_match_x_is_y[round_idx * 2 + 0][team_idx]]) + playing_on_stream.append(s) + times_on_stream_this_team = sum(playing_on_stream) + model.Add(times_on_stream_this_team >= 1) + + times_stream_var = model.NewIntVar(0, num_teams_per_group, "team_%d_stream_count" % (team_idx)) + model.Add(times_stream_var == times_on_stream_this_team) + #model.Add(times_on_stream_this_team <= 4) + + is_n_times_on_stream = [ + model.NewBoolVar('team_%d_is_%d_times_on_stream' % (team_idx, i)) for i in range(num_teams_per_group) + ] + model.AddMapDomain(times_stream_var, is_n_times_on_stream) + stream_penalties.append(is_n_times_on_stream[1] * -50) + stream_penalties.append(is_n_times_on_stream[4] * -10) + stream_penalties.append(is_n_times_on_stream[5] * -50) + +# Make sure each team has at least one exciting match on stream. +#for team_idx in range(team_idx): +# stream_matches_for_this_team = [] +# for round_idx in range(num_rounds): +# stream_matches_for_this_team.append(home_team_in_match_x_is_y[round_idx * 2 + 0][team_idx] * excitedness_weight(round_idx * 2 + 0)) +# stream_matches_for_this_team.append(away_team_in_match_x_is_y[round_idx * 2 + 0][team_idx] * excitedness_weight(round_idx * 2 + 0)) + +# Put the more exciting games later, and on stream fields +excitement = [] +for round_idx in range(num_rounds): + for match_idx in range(match_idx): + excitement.append(match_x_has_num_y[round_idx * 2 + 0][match_idx] * excitedness[match_idx] * excitedness_weight(round_idx * 2 + 0)) + excitement.append(match_x_has_num_y[round_idx * 2 + 1][match_idx] * excitedness[match_idx] * excitedness_weight(round_idx * 2 + 1)) +sum_excitement = sum(excitement) +objective = sum_excitement - 30 * sum_tiredness + 3 * sum(stream_penalties) +model.Maximize(objective) + +solver = cp_model.CpSolver() +solution_printer = SolutionPrinterWithObjective(home_teams, away_teams, matchnums, objective) +status = solver.SolveWithSolutionCallback(model, solution_printer)