706 lines
31 KiB
Python
Executable File
706 lines
31 KiB
Python
Executable File
# %%
|
|
PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'
|
|
import os, sys
|
|
sys.path.insert(0, PROJECT_PATH)
|
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "leagues.settings")
|
|
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
|
|
|
from leagues import settings
|
|
# settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'
|
|
settings.DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
|
|
settings.DATABASES['default']['HOST'] = '0.0.0.0'
|
|
settings.DATABASES['default']['PORT'] = '5432'
|
|
settings.DATABASES['default']['USER'] = 'postgres'
|
|
settings.DATABASES['default']['PASSWORD'] = 'secret123'
|
|
settings.DATABASES['default']['NAME'] = 'mypgsqldb'
|
|
settings.DATABASES['default']['ATOMIC_REQUESTS'] = False
|
|
settings.DATABASES['default']['AUTOCOMMIT'] = True
|
|
settings.DATABASES['default']['CONN_MAX_AGE'] = 0
|
|
settings.DATABASES['default']['CONN_HEALTH_CHECKS'] = False
|
|
settings.DATABASES['default']['OPTIONS'] = {}
|
|
|
|
os.environ["XPRESSDIR"] = "/opt/xpressmp"
|
|
os.environ["XPRESS"] = "/opt/xpressmp/bin"
|
|
os.environ["LD_LIBRARY_PATH"] = os.environ["XPRESSDIR"] + "/lib"
|
|
os.environ["DYLD_LIBRARY_PATH"] = os.environ["XPRESSDIR"] + "/lib"
|
|
os.environ["SHLIB_PATH"] = os.environ["XPRESSDIR"] + "/lib"
|
|
os.environ["LIBPATH"] = os.environ["XPRESSDIR"] + "/lib"
|
|
os.environ["PYTHONPATH"] = os.environ["XPRESSDIR"] + "/lib"
|
|
os.environ["CLASSPATH"] = os.environ["XPRESSDIR"] + "/lib/xprs.jar"
|
|
os.environ["CLASSPATH"] = os.environ["XPRESSDIR"] + "/lib/xprb.jar" + os.pathsep + os.environ["CLASSPATH"]
|
|
os.environ["CLASSPATH"] = os.environ["XPRESSDIR"] + "/lib/xprm.jar" + os.pathsep + os.environ["CLASSPATH"]
|
|
os.environ["PATH"] = os.environ["XPRESSDIR"] + "/bin" + os.pathsep + os.environ["PATH"]
|
|
|
|
|
|
import django
|
|
django.setup()
|
|
|
|
from scheduler.models import *
|
|
import pulp
|
|
from pulp import lpSum, value, XPRESS, GUROBI, PULP_CBC_CMD
|
|
from django.db.models import Q
|
|
from django.template.loader import render_to_string
|
|
|
|
from qualifiers.models import *
|
|
from common.models import GlobalTeam, GlobalCountry
|
|
from scheduler.models import Season, Scenario, Team, DayObj, CountryClash, Country
|
|
|
|
from qualifiers.draws import groupTeams, optimize_inversions4
|
|
from scheduler.solver.tasks.optimize import optimize
|
|
from draws.solver.optimize_draws import ucl24_ha_matrix, ucl24_opponent_matrix
|
|
|
|
|
|
import random
|
|
import time
|
|
import json
|
|
import csv
|
|
import networkx as nx
|
|
import matplotlib.pyplot as plt
|
|
from datetime import timedelta
|
|
|
|
from django.contrib.sessions.models import Session
|
|
|
|
def getVal(v):
|
|
if type(v) == int :
|
|
return v
|
|
else:
|
|
return v.value()
|
|
|
|
|
|
scenario = Scenario.objects.get(id=9607)
|
|
|
|
|
|
hawishes = HAWish.objects.filter(scenario=scenario,prio__in=['Hard','A'])
|
|
encwishes = EncWish.objects.filter(scenario=scenario,prio__in=['Hard','A'])
|
|
|
|
# %%
|
|
|
|
all_rounds = []
|
|
getDateByIso = {}
|
|
|
|
season = scenario.season
|
|
teams = Team.objects.filter(season=scenario.season,active=True)
|
|
higherHomeGames = defaultdict(lambda: [])
|
|
higherAwayGames = defaultdict(lambda: [])
|
|
higherDates = scenario.season.higherDates()
|
|
for game in scenario.season.higherGames():
|
|
higherHomeGames[game[1]].append(game)
|
|
higherAwayGames[game[2]].append(game)
|
|
higherRound = f"{higherDates[game[0]].isocalendar().year}-{higherDates[game[0]].isocalendar().week}"
|
|
all_rounds.append(higherRound)
|
|
getDateByIso[higherRound] = higherDates[game[0]]
|
|
|
|
higherTeams = set(list(higherHomeGames.keys()) + list(higherAwayGames.keys()))
|
|
|
|
gameRequirements = GameRequirement.objects.filter(scenario=scenario)
|
|
stadium_clashes = Pairing.objects.filter(scenario=scenario,type="Home",prio="Hard",active=True,team2__in=teams)
|
|
pairings = Pairing.objects.filter(scenario=scenario,active=True,team2__in=teams).exclude(id__in=stadium_clashes.values_list('id'))
|
|
encwishes = EncWish.objects.filter(scenario=scenario,prio__in=['Hard','A'])
|
|
hawishes = HAWish.objects.filter(scenario=scenario,prio__in=['Hard','A'])
|
|
pairings_dependencies = Pairing.objects.filter(scenario=scenario,active=True,team2__in=higherTeams)
|
|
days = Day.objects.filter(season=scenario.season, maxGames__gt=0)
|
|
blockings = Blocking.objects.filter(scenario=scenario)
|
|
uclpatterns = SpecialWish.objects.filter(scenario=scenario,name='UCL24Patterns',active=True).exists()
|
|
alternatestart = SpecialWish.objects.filter(scenario=scenario,name='AlternateStart',active=True).exists()
|
|
maxTourLength = season.maxTourLength
|
|
groups = Conference.objects.filter(scenario=scenario).filter(name__in=["UEL","UECL"])
|
|
uel_teams = list(groups.filter(name="UEL").first().teams.order_by('pot','country').values_list('id',flat=True))
|
|
uecl_teams = list(groups.filter(name="UECL").first().teams.order_by('pot','country').values_list('id',flat=True))
|
|
|
|
prioVal ={'A': 25 , 'B': 5 , 'C': 1, 'Hard' : 1000}
|
|
pairings_prio = {
|
|
p.id:prioVal[p.prio] for p in pairings
|
|
}
|
|
dependency_prio = {
|
|
p.id:prioVal[p.prio] for p in pairings_dependencies
|
|
}
|
|
|
|
daysPerRound = defaultdict(lambda:[])
|
|
# getRoundByDay = {}
|
|
# for d in days:
|
|
# daysPerRound[d.round].append(d)
|
|
# getRoundByDay[d.id] = d.round
|
|
|
|
getRoundByIso = {}
|
|
getIsoByRound = {}
|
|
getDateByDay = {}
|
|
for d in days:
|
|
getDateByDay[d] = datetime.datetime.strptime(d.day,"%Y-%m-%d")
|
|
iso = datetime.datetime.strptime(d.day,"%Y-%m-%d").isocalendar()
|
|
getRoundByIso[f"{iso.year}-{iso.week}"] = d.round
|
|
getIsoByRound[d.round] = f"{iso.year}-{iso.week}"
|
|
all_rounds.append(f"{iso.year}-{iso.week}")
|
|
getDateByIso[f"{iso.year}-{iso.week}"] = getDateByDay[d]
|
|
daysPerRound[f"{iso.year}-{iso.week}"].append(d)
|
|
|
|
all_rounds = sorted(list(set(all_rounds)))
|
|
|
|
|
|
single_day_rounds = [r for r in daysPerRound if len(daysPerRound[r]) == 1]
|
|
two_day_rounds = [r for r in daysPerRound if len(daysPerRound[r]) == 2 ]
|
|
multi_day_rounds = [r for r in daysPerRound if len(daysPerRound[r]) >= 3 ]
|
|
team_ids = list(teams.values_list('id',flat=True))
|
|
getTeamByID = {
|
|
t.id:t for t in teams
|
|
}
|
|
# list all rounds where each day is blocked for a team
|
|
blockedRounds = {(t.id,r):True for r in all_rounds for t in teams}
|
|
for t in teams:
|
|
tblocks = blockings.filter(team=t)
|
|
for r in all_rounds:
|
|
for d in daysPerRound[r]:
|
|
if not tblocks.filter(day=d).exists():
|
|
blockedRounds[(t.id,r)] = False
|
|
break
|
|
|
|
|
|
gamereqs = []
|
|
opponents_from_pot = defaultdict(lambda:defaultdict(lambda:[]))
|
|
for req in gameRequirements:
|
|
gamereqs.append((req.team1.id,req.team2.id))
|
|
opponents_from_pot[req.team1.id][req.team2.pot].append(req.team2.id)
|
|
opponents_from_pot[req.team2.id][req.team1.pot].append(req.team1.id)
|
|
pot = {}
|
|
teams_in_pot = {}
|
|
for i in teams.values_list('pot',flat=True).distinct():
|
|
pot[i] = teams.filter(pot=i)
|
|
teams_in_pot[i] = list(teams.filter(pot=i).values_list('id',flat=True))
|
|
|
|
topTeams = teams.filter(attractivity__gte=4)
|
|
topGames = [(t1.id,t2.id) for t1 in topTeams for t2 in topTeams if (t1.id,t2.id) in gamereqs]
|
|
topTeam_clashes = {t.id:[] for t in topTeams}
|
|
for t in topTeams:
|
|
for pair in stadium_clashes.filter(team1=t):
|
|
topTeam_clashes[t.id].append(pair.team2.id)
|
|
for pair in stadium_clashes.filter(team2=t):
|
|
topTeam_clashes[t.id].append(pair.team1.id)
|
|
|
|
|
|
rounds = all_rounds
|
|
ucl_rounds = [r for i,r in enumerate(rounds) if i in [0,2,3,4,5,6,8,9]]
|
|
uel_rounds = [r for i,r in enumerate(rounds) if i in [1,2,3,4,5,6,8,9]]
|
|
uecl_rounds = [r for i,r in enumerate(rounds) if i in [2,3,4,5,6,7]]
|
|
|
|
|
|
|
|
|
|
model = pulp.LpProblem(f"test", pulp.LpMinimize)
|
|
dummy = pulp.LpVariable("Dummy", lowBound=1, cat=pulp.LpContinuous)
|
|
x = { (r,t1,t2) : pulp.LpVariable('x_'+str(r)+'_'+str(t1)+'_'+str(t2) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for r in rounds for t1 in team_ids for t2 in team_ids if (t1,t2) in gamereqs or (t2,t1) in gamereqs}
|
|
home = {(r,t) : pulp.LpVariable('home_'+str(r)+'_'+str(t) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for r in rounds for t in team_ids}
|
|
away = {(r,t) : pulp.LpVariable('away_'+str(r)+'_'+str(t) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for r in rounds for t in team_ids}
|
|
y = {(r,t) : pulp.LpVariable('y_'+str(r)+'_'+str(t) , lowBound = 0, upBound = 1, cat = pulp.LpInteger) for r in rounds for t in team_ids}
|
|
team_viol = {t : pulp.LpVariable('team_viol_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in team_ids}
|
|
dependencyViolation = {(r,t,t2) : pulp.LpVariable('dependencyViol_'+str(r)+"_"+str(t)+"_"+str(t2) , lowBound = 0, cat = pulp.LpContinuous) for r in rounds for t in team_ids for t2 in team_ids+list(higherHomeGames.keys())}
|
|
blockingViolation = { (r,t) : pulp.LpVariable('blockingViolation_'+ str(r) +'_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in team_ids for r in rounds}
|
|
|
|
# %%
|
|
|
|
# model += home['2024-51',42286] == 1
|
|
|
|
# model += x[('2024-50', 42279, 42278)] == 0
|
|
|
|
# for key in x.keys():
|
|
# # if key[0] >= '2024-45':
|
|
# x[key].cat = pulp.LpInteger
|
|
|
|
# each game has to be played
|
|
for (t1,t2) in gamereqs:
|
|
model += lpSum([x[r,t1,t2] for r in rounds]) == 1
|
|
|
|
|
|
for r in uel_rounds:
|
|
model += lpSum(home[r,t] for t in uel_teams) == lpSum(away[r,t] for t in uel_teams)
|
|
|
|
for r in uecl_rounds:
|
|
model += lpSum(home[r,t] for t in uecl_teams) == lpSum(away[r,t] for t in uecl_teams)
|
|
|
|
for t in team_ids:
|
|
|
|
for r in rounds:
|
|
model += y[r,t] >= home[r,t] - 0.8
|
|
model += y[r,t] >= away[r,t] - 0.8
|
|
|
|
|
|
if t in uecl_teams:
|
|
|
|
model += lpSum(home[r,t] for r in rounds) == lpSum(away[r,t] for r in rounds)
|
|
|
|
for r in [i for i in rounds if i not in uecl_rounds]:
|
|
model += home[r,t] + away[r,t] <= 0
|
|
|
|
for r in uecl_rounds:
|
|
# each team plays once in each round
|
|
model += lpSum([x[r,t,t2]+x[r,t2,t] for t2 in team_ids if (r,t,t2) in x.keys()]) == 1
|
|
|
|
model += home[r,t] + away[r,t] == 1
|
|
# blockings are hard restrictions
|
|
if blockedRounds[(t,r)]:
|
|
model += home[r,t] == blockingViolation[r,t]
|
|
|
|
for r in rounds:
|
|
# couple homes
|
|
# model += home[r,t] == lpSum([x[r,t,t2] for t2 in team_ids if (r,t,t2) in x.keys()])
|
|
# model += away[r,t] == lpSum([x[r,t2,t] for t2 in team_ids if (r,t2,t) in x.keys()])
|
|
for t2 in team_ids:
|
|
if (r,t,t2) in x.keys():
|
|
model += home[r,t] >= x[r,t,t2]
|
|
model += away[r,t] >= x[r,t2,t]
|
|
|
|
|
|
# always play alternating end of season
|
|
model += lpSum([home[r2,t] for r2 in list(uecl_rounds)[-2:]]) <= 1
|
|
model += lpSum([away[r2,t] for r2 in list(uecl_rounds)[-2:]]) <= 1
|
|
|
|
# alternating start of season
|
|
model += lpSum([home[r2,t] for r2 in uecl_rounds[:2]]) <= 1
|
|
model += lpSum([away[r2,t] for r2 in uecl_rounds[:2]]) <= 1
|
|
# no more than 2 consecutive homegames/awaygames
|
|
for r in range(1,len(uecl_rounds)-1):
|
|
model += lpSum([away[r2,t] for r2 in uecl_rounds[r:r+3]]) <= 2
|
|
model += lpSum([home[r2,t] for r2 in uecl_rounds[r:r+3]]) <= 2
|
|
|
|
elif t in uel_teams:
|
|
|
|
model += lpSum(home[r,t] for r in rounds) == lpSum(away[r,t] for r in rounds)
|
|
|
|
for r in [i for i in rounds if i not in uel_rounds]:
|
|
model += home[r,t] + away[r,t] <= 0
|
|
|
|
for r in uel_rounds:
|
|
# each team plays once in each round
|
|
model += lpSum([x[r,t,t2]+x[r,t2,t] for t2 in team_ids if (r,t,t2) in x.keys()]) == 1
|
|
|
|
model += home[r,t] + away[r,t] == 1
|
|
# blockings are hard restrictions
|
|
if blockedRounds[(t,r)]:
|
|
model += home[r,t] == blockingViolation[r,t]
|
|
|
|
for r in rounds:
|
|
# couple homes
|
|
# model += home[r,t] == lpSum([x[r,t,t2] for t2 in team_ids if (r,t,t2) in x.keys()])
|
|
# model += away[r,t] == lpSum([x[r,t2,t] for t2 in team_ids if (r,t2,t) in x.keys()])
|
|
for t2 in team_ids:
|
|
if (r,t,t2) in x.keys():
|
|
model += home[r,t] >= x[r,t,t2]
|
|
model += away[r,t] >= x[r,t2,t]
|
|
|
|
# always play alternating end of season
|
|
model += lpSum([home[r2,t] for r2 in list(uel_rounds)[-2:]]) <= 1
|
|
model += lpSum([away[r2,t] for r2 in list(uel_rounds)[-2:]]) <= 1
|
|
|
|
# alternating start of season
|
|
model += lpSum([home[r2,t] for r2 in uel_rounds[:2]]) <= 1
|
|
model += lpSum([away[r2,t] for r2 in uel_rounds[:2]]) <= 1
|
|
# no more than 2 consecutive homegames/awaygames
|
|
for r in range(1,len(uel_rounds)-1):
|
|
model += lpSum([home[r2,t] for r2 in uel_rounds[r:r+3]]) <= 2
|
|
model += lpSum([away[r2,t] for r2 in uel_rounds[r:r+3]]) <= 2
|
|
|
|
|
|
team_pairing = defaultdict(lambda:[])
|
|
|
|
# PAIRINGDIST = ((0, _('do not play both on same day')), (1, _('do not play both within two successive days')), (8, _('do not play both within three successive days')),
|
|
# (2, _('do not play both at the same time')), (3, _('do not play both in the same round')),(10, _('do not play both on same weekend (Fr.-Mo.)')),
|
|
# (4, _('play both on same day')), (5, _('play both within two successive days')), (9, _('play both within three successive days')),
|
|
# (6, _('play both at the same time')), (7, _('play both in the same round')), (11, _('play both on same weekend (Fr.-Mo.)')),
|
|
|
|
# stadium/city clashes + pairings
|
|
for r in single_day_rounds:
|
|
for pair in stadium_clashes.filter(dist__in=[0,1,3,8]):
|
|
team_pairing[pair.team1.id].append(pair.team2.shortname)
|
|
team_pairing[pair.team2.id].append(pair.team1.shortname)
|
|
fullseason = True
|
|
iso1 = all_rounds[0]
|
|
iso2 = all_rounds[-1]
|
|
if pair.first_day:
|
|
iso1 = pair.first_day.getDate().isocalendar()
|
|
iso1 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if pair.last_day:
|
|
iso2 = pair.last_day.getDate().isocalendar()
|
|
iso2 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if fullseason or (iso1 <= r and r <= iso2):
|
|
if pair.team1.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team1.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
if pair.team2.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team2.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
print(r, f"ADDED STADIUM single {pair}")
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) <= 1 + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
model += home[r,pair.team1.id] <= away[r,pair.team2.id] + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
|
|
for pair in pairings.filter(dist__in=[0,1,3,8]):
|
|
team_pairing[pair.team1.id].append(pair.team2.shortname)
|
|
team_pairing[pair.team2.id].append(pair.team1.shortname)
|
|
# print(f"ADDED PAIRING single {pair}")
|
|
fullseason = True
|
|
iso1 = all_rounds[0]
|
|
iso2 = all_rounds[-1]
|
|
if pair.first_day:
|
|
iso1 = pair.first_day.getDate().isocalendar()
|
|
iso1 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if pair.last_day:
|
|
iso2 = pair.last_day.getDate().isocalendar()
|
|
iso2 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if fullseason or (iso1 <= r and r <= iso2):
|
|
if pair.team1.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team1.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
if pair.team2.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team2.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
print(r, f"ADDED PAIRING single {pair}")
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) <= 1 + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
model += home[r,pair.team1.id] <= away[r,pair.team2.id] + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
if pair.type == 'Home and Away':
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) >= 1 - dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
|
|
|
|
for r in two_day_rounds:
|
|
for pair in stadium_clashes.filter(dist__in=[1,3,8]):
|
|
team_pairing[pair.team1.id].append(pair.team2.shortname)
|
|
team_pairing[pair.team2.id].append(pair.team1.shortname)
|
|
fullseason = True
|
|
iso1 = all_rounds[0]
|
|
iso2 = all_rounds[-1]
|
|
if pair.first_day:
|
|
iso1 = pair.first_day.getDate().isocalendar()
|
|
iso1 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if pair.last_day:
|
|
iso2 = pair.last_day.getDate().isocalendar()
|
|
iso2 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if fullseason or (iso1 <= r and r <= iso2):
|
|
if pair.team1.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team1.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
if pair.team2.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team2.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
print(r, f"ADDED STADIUM twoday {pair}")
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) <= 1 + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
model += home[r,pair.team1.id] <= away[r,pair.team2.id] + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
for pair in pairings.filter(dist__in=[1,3,8]):
|
|
team_pairing[pair.team1.id].append(pair.team2.shortname)
|
|
team_pairing[pair.team2.id].append(pair.team1.shortname)
|
|
fullseason = True
|
|
iso1 = all_rounds[0]
|
|
iso2 = all_rounds[-1]
|
|
if pair.first_day:
|
|
iso1 = pair.first_day.getDate().isocalendar()
|
|
iso1 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if pair.last_day:
|
|
iso2 = pair.last_day.getDate().isocalendar()
|
|
iso2 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if fullseason or (iso1 <= r and r <= iso2):
|
|
if pair.team1.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team1.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
if pair.team2.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team2.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
print(r, f"ADDED PAIRING multi {pair}")
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) <= 1 + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
model += home[r,pair.team1.id] <= away[r,pair.team2.id] + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
if pair.type == 'Home and Away':
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) >= 1 - dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
for r in multi_day_rounds:
|
|
for pair in stadium_clashes.filter(dist__in=[3,8]):
|
|
team_pairing[pair.team1.id].append(pair.team2.shortname)
|
|
team_pairing[pair.team2.id].append(pair.team1.shortname)
|
|
fullseason = True
|
|
iso1 = all_rounds[0]
|
|
iso2 = all_rounds[-1]
|
|
if pair.first_day:
|
|
iso1 = pair.first_day.getDate().isocalendar()
|
|
iso1 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if pair.last_day:
|
|
iso2 = pair.last_day.getDate().isocalendar()
|
|
iso2 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if fullseason or (iso1 <= r and r <= iso2):
|
|
if pair.team1.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team1.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
if pair.team2.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team2.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
print(r, f"ADDED STADIUM multi {pair}")
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) <= 1 + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
model += home[r,pair.team1.id] <= away[r,pair.team2.id] + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
for pair in pairings.filter(dist__in=[3,8]):
|
|
team_pairing[pair.team1.id].append(pair.team2.shortname)
|
|
team_pairing[pair.team2.id].append(pair.team1.shortname)
|
|
fullseason = True
|
|
iso1 = all_rounds[0]
|
|
iso2 = all_rounds[-1]
|
|
if pair.first_day:
|
|
iso1 = pair.first_day.getDate().isocalendar()
|
|
iso1 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if pair.last_day:
|
|
iso2 = pair.last_day.getDate().isocalendar()
|
|
iso2 = f"{iso.year}-{iso.week}"
|
|
fullseason = False
|
|
if fullseason or (iso1 <= r and r <= iso2):
|
|
if pair.team1.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team1.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
if pair.team2.id in uel_teams and (r not in uel_rounds):
|
|
continue
|
|
if pair.team2.id in uecl_teams and (r not in uecl_rounds):
|
|
continue
|
|
print(r, f"ADDED PAIRING multi {pair}")
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) <= 1 + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
model += home[r,pair.team1.id] <= away[r,pair.team2.id] + dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
if pair.type == 'Home and Away':
|
|
model += lpSum(home[r,pair.team1.id]+home[r,pair.team2.id]) >= 1 - dependencyViolation[r,pair.team1.id,pair.team2.id]
|
|
|
|
ucl_home = defaultdict(lambda:[])
|
|
ucl_away = defaultdict(lambda:[])
|
|
# Season Dependencies
|
|
for p in pairings_dependencies.filter(dist__in=[3,8]): # do not play same round
|
|
team_pairing[p.team1.id].append(p.team2.shortname)
|
|
team_pairing[p.team2.id].append(p.team1.shortname)
|
|
for game in higherHomeGames[p.team2.id]:
|
|
higherRound = f"{higherDates[game[0]].isocalendar().year}-{higherDates[game[0]].isocalendar().week}"
|
|
ucl_home[p.team2].append(higherRound)
|
|
if higherRound in single_day_rounds:
|
|
if p.team1.id in uel_teams and higherRound in uel_rounds:
|
|
model += away[higherRound,p.team1.id] >= 1 - dependencyViolation[higherRound,p.team1.id,p.team2.id]
|
|
elif p.team1.id in uecl_teams and higherRound in uecl_rounds:
|
|
model += away[higherRound,p.team1.id] >= 1 - dependencyViolation[higherRound,p.team1.id,p.team2.id]
|
|
for game in higherAwayGames[p.team2.id]:
|
|
higherRound = f"{higherDates[game[0]].isocalendar().year}-{higherDates[game[0]].isocalendar().week}"
|
|
ucl_away[p.team2].append(higherRound)
|
|
|
|
for p in pairings_dependencies.filter(dist__in=[0,1]): # do not play same day/two days
|
|
team_pairing[p.team1.id].append(p.team2.shortname)
|
|
team_pairing[p.team2.id].append(p.team1.shortname)
|
|
for game in higherHomeGames[p.team2.id]:
|
|
higherRound = f"{higherDates[game[0]].isocalendar().year}-{higherDates[game[0]].isocalendar().week}"
|
|
ucl_home[p.team2].append(higherRound)
|
|
if p.dist == 0:
|
|
forbidden_days = [higherDates[game[0]].date()]
|
|
elif p.dist == 1:
|
|
forbidden_days = [higherDates[game[0]].date() - timedelta(days=1),higherDates[game[0]].date(),higherDates[game[0]].date() + timedelta(days=1)]
|
|
available_days = [d for d in daysPerRound[higherRound] if getDateByDay[d].date() not in forbidden_days]
|
|
if not available_days:
|
|
if p.team1.id in uel_teams and higherRound in uel_rounds:
|
|
model += away[higherRound,p.team1.id] >= 1 - dependencyViolation[higherRound,p.team1.id,p.team2.id]
|
|
elif p.team1.id in uecl_teams and higherRound in uecl_rounds:
|
|
model += away[higherRound,p.team1.id] >= 1 - dependencyViolation[higherRound,p.team1.id,p.team2.id]
|
|
for game in higherAwayGames[p.team2.id]:
|
|
higherRound = f"{higherDates[game[0]].isocalendar().year}-{higherDates[game[0]].isocalendar().week}"
|
|
ucl_away[p.team2].append(higherRound)
|
|
|
|
|
|
|
|
|
|
|
|
# model += home['2024-40',42339] == 1
|
|
# model += home['2024-43',42339] == 0
|
|
# model += home['2024-45',42339] == 1
|
|
# model += home['2024-48',42339] == 0
|
|
# model += home['2024-50',42339] == 1
|
|
# model += home['2024-51',42339] == 0
|
|
|
|
# model += home['2024-40',42278] == 0
|
|
# model += x[('2024-43',42278,42339)] == 1
|
|
# model += x[('2024-40',42339,42340)] == 1
|
|
|
|
|
|
# model += home['2024-51',42339] == 0
|
|
# model += home["2024-51",42286] <= 0
|
|
|
|
|
|
|
|
# minimize pairing violation
|
|
objective_function = dummy +\
|
|
lpSum(10*y[key] for key in y.keys()) +\
|
|
lpSum(home[key] for key in home.keys()) +\
|
|
lpSum(away[key] for key in away.keys()) +\
|
|
10000*lpSum(dependencyViolation[key] for key in dependencyViolation.keys()) +\
|
|
10000*lpSum(blockingViolation[key] for key in blockingViolation.keys())
|
|
|
|
|
|
model += objective_function #+ 0.001*random_seed
|
|
|
|
with open ("basicmodel.txt", "w") as f:
|
|
f.write(model.__repr__())
|
|
|
|
model.solve(XPRESS(msg=1,timeLimit=300, gapRel=0))
|
|
|
|
|
|
# FOR DEBUGGING
|
|
nDays = len(rounds)
|
|
|
|
home_dict = {}
|
|
away_dict = {}
|
|
viol_dict = {}
|
|
for key in home.keys():
|
|
if getVal(home[key]) and getVal(home[key]) > 0.95:
|
|
home_dict[key[0],key[1]] = "H"
|
|
|
|
for key in away.keys():
|
|
if getVal(away[key]) and getVal(away[key]) > 0.95:
|
|
away_dict[key[0],key[1]] = "A"
|
|
|
|
|
|
for key in dependencyViolation.keys():
|
|
if getVal(dependencyViolation[key]) and getVal(dependencyViolation[key]) > 0:
|
|
print("VIOLATED DEPENDENCY",key,getVal(dependencyViolation[key]))
|
|
viol_dict[key[0],key[1]] = Team.objects.get(id=key[2])
|
|
|
|
for key in blockingViolation.keys():
|
|
if getVal(blockingViolation[key]) and getVal(blockingViolation[key]) > 0:
|
|
print("VIOLATED BLOCKING",key,getVal(blockingViolation[key]))
|
|
viol_dict[key[0],key[1]] = key[0]
|
|
|
|
|
|
|
|
for key in x.keys():
|
|
if getVal(x[key]) and getVal(x[key]) > 0.95:
|
|
print(key,getVal(x[key]))
|
|
print(r[0],teams.get(id=key[1]),teams.get(id=key[2]))
|
|
home_dict[key[0],key[1]] = f"{teams.get(id=key[2])}"
|
|
away_dict[key[0],key[2]] = f"@{teams.get(id=key[1])}"
|
|
|
|
for k,v in team_pairing.items():
|
|
team_pairing[k] = ",".join(set(v))
|
|
|
|
|
|
|
|
sol = " \
|
|
<style> \
|
|
table, th, td { \
|
|
border: 1px solid black; \
|
|
border-collapse: collapse; \
|
|
} \
|
|
</style> \
|
|
"
|
|
sol += "<table style='border:1px solid black'>\n"
|
|
sol += "<thead>\n"
|
|
sol += "<tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th>"
|
|
for d in all_rounds:
|
|
sol += f"<th>{d} - {getDateByIso[d].date()}</th>"
|
|
sol += "</tr>"
|
|
sol += "</thead>\n"
|
|
sol += "<tbody>\n"
|
|
for t in ucl_home:
|
|
sol += f"<tr><td>UCL</t><td>{t.id}</td><td>Pot {t.pot}</td><td>{t.country}</td><td>{t.name}</td><td>{t.shortname}</td><td>{team_pairing[t.id]}</td>"
|
|
for d in all_rounds:
|
|
if d in ucl_rounds:
|
|
if d in ucl_home[t]:
|
|
fontcolor = 'black'
|
|
bgcolor = 'lightsteelblue'
|
|
sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>H</td>"
|
|
elif d in ucl_away[t]:
|
|
fontcolor = 'black'
|
|
bgcolor = 'lightyellow'
|
|
sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>A</td>"
|
|
else:
|
|
sol += "<td></td>"
|
|
else:
|
|
sol += "<td style='background-color:lightgrey'></td>"
|
|
sol += "</tr>"
|
|
sol += "<tr style='background-color:lightgrey' ><th colspan='7'>UEL</th><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th></th><th>7</th><th>8</th></tr>"
|
|
for t in uel_teams:
|
|
tname = getTeamByID[t].name
|
|
tshortname = getTeamByID[t].shortname
|
|
tcountry = getTeamByID[t].country
|
|
tpot = getTeamByID[t].pot
|
|
sol += f"<tr><td>UEL</t><td>{t}</td><td>Pot {tpot}</td><td>{tcountry}</td><td>{tname}</td><td>{tshortname}</td><td>{team_pairing[t]}</td>"
|
|
for d in all_rounds:
|
|
if d in uel_rounds:
|
|
|
|
if (d,t) in home_dict.keys():
|
|
fontcolor = 'black'
|
|
bgcolor = 'lightsteelblue'
|
|
if (d,t) in viol_dict.keys():
|
|
fontcolor = 'white'
|
|
bgcolor = 'darkred'
|
|
sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>{home_dict[(d,t)]}</td>"
|
|
elif (d,t) in away_dict.keys():
|
|
fontcolor = 'black'
|
|
bgcolor = 'lightyellow'
|
|
if (d,t) in viol_dict.keys():
|
|
fontcolor = 'white'
|
|
bgcolor = 'darkred'
|
|
sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>{away_dict[(d,t)]}</td>"
|
|
elif (d,t) in viol_dict.keys():
|
|
fontcolor = 'white'
|
|
bgcolor = 'darkred'
|
|
sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>{viol_dict[(d,t)]}</td>"
|
|
else:
|
|
sol += "<td></td>"
|
|
else:
|
|
sol += "<td style='background-color:lightgrey'></td>"
|
|
sol += "</tr>"
|
|
sol += "<tr style='background-color:lightgrey'><th colspan='7'>UECL</th><th></th><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th></th><th></th></tr>"
|
|
for t in uecl_teams:
|
|
tname = getTeamByID[t].name
|
|
tshortname = getTeamByID[t].shortname
|
|
tcountry = getTeamByID[t].country
|
|
tpot = getTeamByID[t].pot
|
|
sol += f"<tr><td>UECL</t><td>{t}</td><td>Pot {tpot}</td><td>{tcountry}</td><td>{tname}</td><td>{tshortname}</td><td>{team_pairing[t]}</td>"
|
|
for d in all_rounds:
|
|
if d in uecl_rounds:
|
|
# if (d,t) in viol_dict.keys():
|
|
# fontcolor = 'white'
|
|
# bgcolor = 'darkred'
|
|
# sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>{viol_dict[(d,t)]}</td>"
|
|
if (d,t) in home_dict.keys():
|
|
fontcolor = 'black'
|
|
bgcolor = 'lightsteelblue'
|
|
if (d,t) in viol_dict.keys():
|
|
fontcolor = 'white'
|
|
bgcolor = 'darkred'
|
|
sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>{home_dict[(d,t)]}</td>"
|
|
elif (d,t) in away_dict.keys():
|
|
fontcolor = 'black'
|
|
bgcolor = 'lightyellow'
|
|
if (d,t) in viol_dict.keys():
|
|
fontcolor = 'white'
|
|
bgcolor = 'darkred'
|
|
sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>{away_dict[(d,t)]}</td>"
|
|
elif (d,t) in viol_dict.keys():
|
|
fontcolor = 'white'
|
|
bgcolor = 'darkred'
|
|
sol += f"<td style='min-width:80px;background-color:{bgcolor};color:{fontcolor}'>{viol_dict[(d,t)]}</td>"
|
|
else:
|
|
sol += "<td></td>"
|
|
else:
|
|
sol += "<td style='background-color:lightgrey'></td>"
|
|
sol += "</tr>"
|
|
sol += "</tbody>\n"
|
|
sol += "</table>\n"
|
|
|
|
with open(f'ueluecl24_debug.html', 'w') as f:
|
|
f.write(sol)
|
|
|
|
|
|
|
|
|
|
# %%
|